mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
feat: generates coordinates for bounding boxes
This commit is contained in:
@ -135,6 +135,10 @@ NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
|
|||||||
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
# OPTIONAL: Leave blank to allow users to signup through /signup page.
|
||||||
NEXT_PUBLIC_DISABLE_SIGNUP=
|
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||||
|
|
||||||
|
# [[AI]]
|
||||||
|
# OPTIONAL: API key for Google Generative AI (Gemini). Get your key from https://ai.google.dev
|
||||||
|
GOOGLE_GENERATIVE_AI_API_KEY=""
|
||||||
|
|
||||||
# [[E2E Tests]]
|
# [[E2E Tests]]
|
||||||
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
|
||||||
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
"with:env": "dotenv -e ../../.env -e ../../.env.local --"
|
"with:env": "dotenv -e ../../.env -e ../../.env.local --"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ai-sdk/google": "^2.0.25",
|
||||||
|
"@ai-sdk/react": "^2.0.82",
|
||||||
"@cantoo/pdf-lib": "^2.5.2",
|
"@cantoo/pdf-lib": "^2.5.2",
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
"@documenso/assets": "*",
|
"@documenso/assets": "*",
|
||||||
@ -38,6 +40,7 @@
|
|||||||
"@react-router/serve": "^7.6.0",
|
"@react-router/serve": "^7.6.0",
|
||||||
"@simplewebauthn/browser": "^9.0.1",
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
"@simplewebauthn/server": "^9.0.3",
|
"@simplewebauthn/server": "^9.0.3",
|
||||||
|
"ai": "^5.0.82",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
@ -68,6 +71,7 @@
|
|||||||
"remix-themes": "^2.0.4",
|
"remix-themes": "^2.0.4",
|
||||||
"satori": "^0.12.1",
|
"satori": "^0.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
|
"skia-canvas": "^3.0.8",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.15",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
|
|||||||
373
apps/remix/server/api/ai.ts
Normal file
373
apps/remix/server/api/ai.ts
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
import { google } from '@ai-sdk/google';
|
||||||
|
import { sValidator } from '@hono/standard-validator';
|
||||||
|
import { generateObject, generateText } from 'ai';
|
||||||
|
import { readFile, writeFile } from 'fs/promises';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { cors } from 'hono/cors';
|
||||||
|
import { join } from 'path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { Canvas, Image } from 'skia-canvas';
|
||||||
|
|
||||||
|
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
|
||||||
|
import type { HonoEnv } from '../router';
|
||||||
|
import {
|
||||||
|
type TDetectObjectsAndDrawResponse,
|
||||||
|
type TDetectObjectsResponse,
|
||||||
|
type TGenerateTextResponse,
|
||||||
|
ZDetectObjectsAndDrawRequestSchema,
|
||||||
|
ZDetectObjectsRequestSchema,
|
||||||
|
ZDetectObjectsResponseSchema,
|
||||||
|
ZGenerateTextRequestSchema,
|
||||||
|
} from './ai.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize and compress image for better Gemini API accuracy.
|
||||||
|
* Resizes to max width of 1000px (maintaining aspect ratio) and compresses to JPEG at 70% quality.
|
||||||
|
* This preprocessing improves bounding box detection accuracy.
|
||||||
|
*/
|
||||||
|
async function resizeAndCompressImage(imageBuffer: Buffer): Promise<Buffer> {
|
||||||
|
const metadata = await sharp(imageBuffer).metadata();
|
||||||
|
const originalWidth = metadata.width || 0;
|
||||||
|
|
||||||
|
if (originalWidth > 1000) {
|
||||||
|
return await sharp(imageBuffer)
|
||||||
|
.resize({ width: 1000, withoutEnlargement: true })
|
||||||
|
.jpeg({ quality: 70 })
|
||||||
|
.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await sharp(imageBuffer).jpeg({ quality: 70 }).toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const aiRoute = new Hono<HonoEnv>()
|
||||||
|
.use(
|
||||||
|
'*',
|
||||||
|
cors({
|
||||||
|
origin: 'http://localhost:3000',
|
||||||
|
allowMethods: ['POST', 'OPTIONS'],
|
||||||
|
allowHeaders: ['Content-Type', 'Authorization'],
|
||||||
|
credentials: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
.post('/generate', sValidator('json', ZGenerateTextRequestSchema), async (c) => {
|
||||||
|
try {
|
||||||
|
await getSession(c.req.raw);
|
||||||
|
|
||||||
|
const { prompt } = c.req.valid('json');
|
||||||
|
|
||||||
|
const result = await generateText({
|
||||||
|
model: google('gemini-2.0-flash-exp'),
|
||||||
|
prompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json<TGenerateTextResponse>({ text: result.text });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI generation failed:', error);
|
||||||
|
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||||
|
message: 'Failed to generate text',
|
||||||
|
userMessage: 'An error occurred while generating the text. Please try again.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.post('/detect-objects', sValidator('json', ZDetectObjectsRequestSchema), async (c) => {
|
||||||
|
try {
|
||||||
|
await getSession(c.req.raw);
|
||||||
|
|
||||||
|
const { imagePath } = c.req.valid('json');
|
||||||
|
|
||||||
|
const imageBuffer = await readFile(imagePath);
|
||||||
|
const compressedImageBuffer = await resizeAndCompressImage(imageBuffer);
|
||||||
|
const base64Image = compressedImageBuffer.toString('base64');
|
||||||
|
|
||||||
|
const result = await generateObject({
|
||||||
|
model: google('gemini-2.5-pro'),
|
||||||
|
schema: ZDetectObjectsResponseSchema,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
image: `data:image/jpeg;base64,${base64Image}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `You are analyzing a form document image to detect fillable fields for the Documenso document signing platform.
|
||||||
|
|
||||||
|
IMPORTANT RULES:
|
||||||
|
1. Only detect EMPTY/UNFILLED fields (ignore boxes that already contain text or data)
|
||||||
|
2. Analyze nearby text labels to determine the field type
|
||||||
|
3. Return bounding boxes for the fillable area only, NOT the label text
|
||||||
|
4. Each bounding box must be in the format [ymin, xmin, ymax, xmax] where all coordinates are NORMALIZED to a 0-1000 scale
|
||||||
|
|
||||||
|
FIELD TYPES TO DETECT:
|
||||||
|
• SIGNATURE - Signature lines, boxes labeled 'Signature', 'Sign here', 'Authorized signature', 'X____'
|
||||||
|
• INITIALS - Small boxes labeled 'Initials', 'Initial here', typically smaller than signature fields
|
||||||
|
• NAME - Boxes labeled 'Name', 'Full name', 'Your name', 'Print name', 'Printed name'
|
||||||
|
• EMAIL - Boxes labeled 'Email', 'Email address', 'E-mail', 'Email:'
|
||||||
|
• DATE - Boxes labeled 'Date', 'Date signed', "Today's date", or showing date format placeholders like 'MM/DD/YYYY', '__/__/____'
|
||||||
|
• CHECKBOX - Empty checkbox squares (☐) with or without labels, typically small square boxes
|
||||||
|
• RADIO - Empty radio button circles (○) in groups, typically circular selection options
|
||||||
|
• NUMBER - Boxes labeled with numeric context: 'Amount', 'Quantity', 'Phone', 'Phone number', 'ZIP', 'ZIP code', 'Age', 'Price', '#'
|
||||||
|
• DROPDOWN - Boxes with dropdown indicators (▼, ↓) or labeled 'Select', 'Choose', 'Please select'
|
||||||
|
• TEXT - Any other empty text input boxes, general input fields, unlabeled boxes, or when field type is uncertain
|
||||||
|
|
||||||
|
DETECTION GUIDELINES:
|
||||||
|
- Read text located near the box (above, to the left, or inside the box boundary) to infer the field type
|
||||||
|
- If you're uncertain which type fits best, default to TEXT
|
||||||
|
- For checkboxes and radio buttons: Detect each individual box/circle separately, not the label
|
||||||
|
- Signature fields are often longer horizontal lines or larger boxes
|
||||||
|
- Date fields often show format hints or date separators (slashes, dashes)
|
||||||
|
- Look for visual patterns: underscores (____), horizontal lines, box outlines
|
||||||
|
- Return coordinates for the fillable area, not the descriptive label text
|
||||||
|
|
||||||
|
COORDINATE SYSTEM:
|
||||||
|
- [ymin, xmin, ymax, xmax] normalized to 0-1000 scale
|
||||||
|
- Top-left corner: ymin and xmin close to 0
|
||||||
|
- Bottom-right corner: ymax and xmax close to 1000
|
||||||
|
- Coordinates represent positions on a 1000x1000 grid overlaid on the image`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json<TDetectObjectsResponse>(result.object);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Object detection failed:', error);
|
||||||
|
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||||
|
message: 'Failed to detect objects',
|
||||||
|
userMessage: 'An error occurred while detecting objects. Please try again.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.post(
|
||||||
|
'/detect-object-and-draw',
|
||||||
|
sValidator('json', ZDetectObjectsAndDrawRequestSchema),
|
||||||
|
async (c) => {
|
||||||
|
try {
|
||||||
|
await getSession(c.req.raw);
|
||||||
|
|
||||||
|
const { imagePath } = c.req.valid('json');
|
||||||
|
|
||||||
|
console.log(`[detect-object-and-draw] Reading image from: ${imagePath}`);
|
||||||
|
|
||||||
|
const imageBuffer = await readFile(imagePath);
|
||||||
|
const metadata = await sharp(imageBuffer).metadata();
|
||||||
|
const imageWidth = metadata.width;
|
||||||
|
const imageHeight = metadata.height;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[detect-object-and-draw] Original image dimensions: ${imageWidth}x${imageHeight}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!imageWidth || !imageHeight) {
|
||||||
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
|
message: 'Unable to extract image dimensions',
|
||||||
|
userMessage: 'The image file appears to be invalid or corrupted.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[detect-object-and-draw] Compressing image for Gemini API...');
|
||||||
|
const compressedImageBuffer = await resizeAndCompressImage(imageBuffer);
|
||||||
|
const base64Image = compressedImageBuffer.toString('base64');
|
||||||
|
|
||||||
|
console.log('[detect-object-and-draw] Calling Gemini API for form field detection...');
|
||||||
|
const result = await generateObject({
|
||||||
|
model: google('gemini-2.5-pro'),
|
||||||
|
schema: ZDetectObjectsResponseSchema,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
image: `data:image/jpeg;base64,${base64Image}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `You are analyzing a form document image to detect fillable fields for the Documenso document signing platform.
|
||||||
|
|
||||||
|
IMPORTANT RULES:
|
||||||
|
1. Only detect EMPTY/UNFILLED fields (ignore boxes that already contain text or data)
|
||||||
|
2. Analyze nearby text labels to determine the field type
|
||||||
|
3. Return bounding boxes for the fillable area only, NOT the label text
|
||||||
|
4. Each bounding box must be in the format [ymin, xmin, ymax, xmax] where all coordinates are NORMALIZED to a 0-1000 scale
|
||||||
|
|
||||||
|
FIELD TYPES TO DETECT:
|
||||||
|
• SIGNATURE - Signature lines, boxes labeled 'Signature', 'Sign here', 'Authorized signature', 'X____'
|
||||||
|
• INITIALS - Small boxes labeled 'Initials', 'Initial here', typically smaller than signature fields
|
||||||
|
• NAME - Boxes labeled 'Name', 'Full name', 'Your name', 'Print name', 'Printed name'
|
||||||
|
• EMAIL - Boxes labeled 'Email', 'Email address', 'E-mail', 'Email:'
|
||||||
|
• DATE - Boxes labeled 'Date', 'Date signed', "Today's date", or showing date format placeholders like 'MM/DD/YYYY', '__/__/____'
|
||||||
|
• CHECKBOX - Empty checkbox squares (☐) with or without labels, typically small square boxes
|
||||||
|
• RADIO - Empty radio button circles (○) in groups, typically circular selection options
|
||||||
|
• NUMBER - Boxes labeled with numeric context: 'Amount', 'Quantity', 'Phone', 'Phone number', 'ZIP', 'ZIP code', 'Age', 'Price', '#'
|
||||||
|
• DROPDOWN - Boxes with dropdown indicators (▼, ↓) or labeled 'Select', 'Choose', 'Please select'
|
||||||
|
• TEXT - Any other empty text input boxes, general input fields, unlabeled boxes, or when field type is uncertain
|
||||||
|
|
||||||
|
DETECTION GUIDELINES:
|
||||||
|
- Read text located near the box (above, to the left, or inside the box boundary) to infer the field type
|
||||||
|
- If you're uncertain which type fits best, default to TEXT
|
||||||
|
- For checkboxes and radio buttons: Detect each individual box/circle separately, not the label
|
||||||
|
- Signature fields are often longer horizontal lines or larger boxes
|
||||||
|
- Date fields often show format hints or date separators (slashes, dashes)
|
||||||
|
- Look for visual patterns: underscores (____), horizontal lines, box outlines
|
||||||
|
- Return coordinates for the fillable area, not the descriptive label text
|
||||||
|
|
||||||
|
COORDINATE SYSTEM:
|
||||||
|
- [ymin, xmin, ymax, xmax] normalized to 0-1000 scale
|
||||||
|
- Top-left corner: ymin and xmin close to 0
|
||||||
|
- Bottom-right corner: ymax and xmax close to 1000
|
||||||
|
- Coordinates represent positions on a 1000x1000 grid overlaid on the image`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
console.log('[detect-object-and-draw] Gemini API call completed');
|
||||||
|
|
||||||
|
const detectedObjects = result.object;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[detect-object-and-draw] Detected ${detectedObjects.length} objects, starting to draw...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const padding = { left: 80, top: 20, right: 20, bottom: 40 };
|
||||||
|
const canvas = new Canvas(
|
||||||
|
imageWidth + padding.left + padding.right,
|
||||||
|
imageHeight + padding.top + padding.bottom,
|
||||||
|
);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.src = imageBuffer;
|
||||||
|
ctx.drawImage(img, padding.left, padding.top);
|
||||||
|
|
||||||
|
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i <= 1000; i += 100) {
|
||||||
|
const x = padding.left + (i / 1000) * imageWidth;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, padding.top);
|
||||||
|
ctx.lineTo(x, imageHeight + padding.top);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal grid lines (every 100 units on 0-1000 scale)
|
||||||
|
for (let i = 0; i <= 1000; i += 100) {
|
||||||
|
const y = padding.top + (i / 1000) * imageHeight;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, y);
|
||||||
|
ctx.lineTo(imageWidth + padding.left, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
|
||||||
|
|
||||||
|
detectedObjects.forEach((obj, index) => {
|
||||||
|
const [ymin, xmin, ymax, xmax] = obj.box_2d.map((coord) => coord / 1000);
|
||||||
|
|
||||||
|
const x = xmin * imageWidth + padding.left;
|
||||||
|
const y = ymin * imageHeight + padding.top;
|
||||||
|
const width = (xmax - xmin) * imageWidth;
|
||||||
|
const height = (ymax - ymin) * imageHeight;
|
||||||
|
|
||||||
|
ctx.strokeStyle = colors[index % colors.length];
|
||||||
|
ctx.lineWidth = 5;
|
||||||
|
ctx.strokeRect(x, y, width, height);
|
||||||
|
|
||||||
|
ctx.fillStyle = colors[index % colors.length];
|
||||||
|
ctx.font = '20px Arial';
|
||||||
|
ctx.fillText(obj.label, x, y - 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.strokeStyle = '#000000';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.font = '26px Arial';
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, padding.top);
|
||||||
|
ctx.lineTo(padding.left, imageHeight + padding.top);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
for (let i = 0; i <= 1000; i += 100) {
|
||||||
|
const y = padding.top + (i / 1000) * imageHeight;
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillText(i.toString(), padding.left - 5, y);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left - 5, y);
|
||||||
|
ctx.lineTo(padding.left, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, imageHeight + padding.top);
|
||||||
|
ctx.lineTo(imageWidth + padding.left, imageHeight + padding.top);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
for (let i = 0; i <= 1000; i += 100) {
|
||||||
|
const x = padding.left + (i / 1000) * imageWidth;
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillText(i.toString(), x, imageHeight + padding.top + 5);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, imageHeight + padding.top);
|
||||||
|
ctx.lineTo(x, imageHeight + padding.top + 5);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const timestamp = now
|
||||||
|
.toISOString()
|
||||||
|
.replace(/[-:]/g, '')
|
||||||
|
.replace(/\..+/, '')
|
||||||
|
.replace('T', '_');
|
||||||
|
const outputFilename = `detected_objects_${timestamp}.png`;
|
||||||
|
const outputPath = join(process.cwd(), outputFilename);
|
||||||
|
|
||||||
|
console.log('[detect-object-and-draw] Converting canvas to PNG buffer...');
|
||||||
|
const pngBuffer = await canvas.toBuffer('png');
|
||||||
|
console.log(`[detect-object-and-draw] Saving to: ${outputPath}`);
|
||||||
|
await writeFile(outputPath, pngBuffer);
|
||||||
|
|
||||||
|
console.log('[detect-object-and-draw] Image saved successfully!');
|
||||||
|
return c.json<TDetectObjectsAndDrawResponse>({
|
||||||
|
outputPath,
|
||||||
|
detectedObjects,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Object detection and drawing failed:', error);
|
||||||
|
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||||
|
message: 'Failed to detect objects and draw',
|
||||||
|
userMessage: 'An error occurred while detecting and drawing objects. Please try again.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
56
apps/remix/server/api/ai.types.ts
Normal file
56
apps/remix/server/api/ai.types.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZGenerateTextRequestSchema = z.object({
|
||||||
|
prompt: z.string().min(1, 'Prompt is required').max(5000, 'Prompt is too long'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGenerateTextResponseSchema = z.object({
|
||||||
|
text: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGenerateTextRequest = z.infer<typeof ZGenerateTextRequestSchema>;
|
||||||
|
export type TGenerateTextResponse = z.infer<typeof ZGenerateTextResponseSchema>;
|
||||||
|
|
||||||
|
export const ZDetectedObjectSchema = z.object({
|
||||||
|
box_2d: z
|
||||||
|
.array(z.number())
|
||||||
|
.length(4)
|
||||||
|
.describe('Bounding box [ymin, xmin, ymax, xmax] in normalized 0-1000 range'),
|
||||||
|
label: z
|
||||||
|
.enum([
|
||||||
|
'SIGNATURE',
|
||||||
|
'INITIALS',
|
||||||
|
'NAME',
|
||||||
|
'EMAIL',
|
||||||
|
'DATE',
|
||||||
|
'TEXT',
|
||||||
|
'NUMBER',
|
||||||
|
'RADIO',
|
||||||
|
'CHECKBOX',
|
||||||
|
'DROPDOWN',
|
||||||
|
])
|
||||||
|
.describe('Documenso field type inferred from nearby label text or visual characteristics'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDetectObjectsRequestSchema = z.object({
|
||||||
|
imagePath: z.string().min(1, 'Image path is required'),
|
||||||
|
// TODO: Replace with file upload - reference files.ts pattern
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDetectObjectsResponseSchema = z.array(ZDetectedObjectSchema);
|
||||||
|
|
||||||
|
export type TDetectedObject = z.infer<typeof ZDetectedObjectSchema>;
|
||||||
|
export type TDetectObjectsRequest = z.infer<typeof ZDetectObjectsRequestSchema>;
|
||||||
|
export type TDetectObjectsResponse = z.infer<typeof ZDetectObjectsResponseSchema>;
|
||||||
|
|
||||||
|
export const ZDetectObjectsAndDrawRequestSchema = z.object({
|
||||||
|
imagePath: z.string().min(1, 'Image path is required'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZDetectObjectsAndDrawResponseSchema = z.object({
|
||||||
|
outputPath: z.string().describe('Path to the generated image with bounding boxes'),
|
||||||
|
detectedObjects: z.array(ZDetectedObjectSchema).describe('Array of detected objects'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDetectObjectsAndDrawRequest = z.infer<typeof ZDetectObjectsAndDrawRequestSchema>;
|
||||||
|
export type TDetectObjectsAndDrawResponse = z.infer<typeof ZDetectObjectsAndDrawResponseSchema>;
|
||||||
@ -14,6 +14,7 @@ import { getIpAddress } from '@documenso/lib/universal/get-ip-address';
|
|||||||
import { logger } from '@documenso/lib/utils/logger';
|
import { logger } from '@documenso/lib/utils/logger';
|
||||||
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
||||||
|
|
||||||
|
import { aiRoute } from './api/ai';
|
||||||
import { filesRoute } from './api/files';
|
import { filesRoute } from './api/files';
|
||||||
import { type AppContext, appContext } from './context';
|
import { type AppContext, appContext } from './context';
|
||||||
import { appMiddleware } from './middleware';
|
import { appMiddleware } from './middleware';
|
||||||
@ -83,6 +84,9 @@ app.route('/api/auth', auth);
|
|||||||
// Files route.
|
// Files route.
|
||||||
app.route('/api/files', filesRoute);
|
app.route('/api/files', filesRoute);
|
||||||
|
|
||||||
|
// AI route.
|
||||||
|
app.route('/api/ai', aiRoute);
|
||||||
|
|
||||||
// API servers.
|
// API servers.
|
||||||
app.use(`/api/v1/*`, cors());
|
app.use(`/api/v1/*`, cors());
|
||||||
app.route('/api/v1', tsRestHonoApp);
|
app.route('/api/v1', tsRestHonoApp);
|
||||||
|
|||||||
211
package-lock.json
generated
211
package-lock.json
generated
@ -16,12 +16,13 @@
|
|||||||
"@documenso/prisma": "^0.0.0",
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/conf": "^5.2.0",
|
"@lingui/conf": "^5.2.0",
|
||||||
"@lingui/core": "^5.2.0",
|
"@lingui/core": "^5.2.0",
|
||||||
|
"ai": "^5.0.82",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"mupdf": "^1.0.0",
|
"mupdf": "^1.0.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"typescript": "5.6.2",
|
"typescript": "5.6.2",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.7.1",
|
"@commitlint/cli": "^17.7.1",
|
||||||
@ -91,6 +92,8 @@
|
|||||||
"name": "@documenso/remix",
|
"name": "@documenso/remix",
|
||||||
"version": "1.13.1",
|
"version": "1.13.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ai-sdk/google": "^2.0.25",
|
||||||
|
"@ai-sdk/react": "^2.0.82",
|
||||||
"@cantoo/pdf-lib": "^2.5.2",
|
"@cantoo/pdf-lib": "^2.5.2",
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
"@documenso/assets": "*",
|
"@documenso/assets": "*",
|
||||||
@ -115,6 +118,7 @@
|
|||||||
"@react-router/serve": "^7.6.0",
|
"@react-router/serve": "^7.6.0",
|
||||||
"@simplewebauthn/browser": "^9.0.1",
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
"@simplewebauthn/server": "^9.0.3",
|
"@simplewebauthn/server": "^9.0.3",
|
||||||
|
"ai": "^5.0.82",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
@ -145,6 +149,7 @@
|
|||||||
"remix-themes": "^2.0.4",
|
"remix-themes": "^2.0.4",
|
||||||
"satori": "^0.12.1",
|
"satori": "^0.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
|
"skia-canvas": "^3.0.8",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.15",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
@ -187,6 +192,92 @@
|
|||||||
"integrity": "sha512-EGs8PguQqAAUIcQfK4E9xdXxB6s2GK4sJfT/vcc9V1ELIvC4LH/zXu2t/5fajtv6oiRCxdv7BgtVK3vWgROxag==",
|
"integrity": "sha512-EGs8PguQqAAUIcQfK4E9xdXxB6s2GK4sJfT/vcc9V1ELIvC4LH/zXu2t/5fajtv6oiRCxdv7BgtVK3vWgROxag==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@ai-sdk/gateway": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-/vCoMKtod+A74/BbkWsaAflWKz1ovhX5lmJpIaXQXtd6gyexZncjotBTbFM8rVJT9LKJ/Kx7iVVo3vh+KT+IJg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@ai-sdk/provider-utils": "3.0.14",
|
||||||
|
"@vercel/oidc": "3.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/google": {
|
||||||
|
"version": "2.0.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.25.tgz",
|
||||||
|
"integrity": "sha512-tH2rA3428jnY6COoPfKB/BoQMs57sv9t+PEdyIB9ePtlV9dnVUbfKcdKoEcAaVffNZ6pzk8otrQYnu67pyn8TQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@ai-sdk/provider-utils": "3.0.14"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/provider": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema": "^0.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "3.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.14.tgz",
|
||||||
|
"integrity": "sha512-CYRU6L7IlR7KslSBVxvlqlybQvXJln/PI57O8swhOaDIURZbjRP2AY3igKgUsrmWqqnFFUHP+AwTN8xqJAknnA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"eventsource-parser": "^3.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/react": {
|
||||||
|
"version": "2.0.82",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.82.tgz",
|
||||||
|
"integrity": "sha512-InaGqykKGFq/XA6Vhh2Hyy38nzeMpqp8eWxjTNEQA5Gwcal0BVNuZyTbTIL5t5VNXV+pQPDhe9ak1+mc9qxjog==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider-utils": "3.0.14",
|
||||||
|
"ai": "5.0.82",
|
||||||
|
"swr": "^2.2.5",
|
||||||
|
"throttleit": "2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
@ -11178,6 +11269,12 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swagger-api/apidom-ast": {
|
"node_modules/@swagger-api/apidom-ast": {
|
||||||
"version": "1.0.0-beta.39",
|
"version": "1.0.0-beta.39",
|
||||||
"resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.39.tgz",
|
"resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.39.tgz",
|
||||||
@ -11866,6 +11963,15 @@
|
|||||||
"zod": "3.22.4"
|
"zod": "3.22.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@team-plain/typescript-sdk/node_modules/zod": {
|
||||||
|
"version": "3.22.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
||||||
|
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@theguild/remark-mermaid": {
|
"node_modules/@theguild/remark-mermaid": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@theguild/remark-mermaid/-/remark-mermaid-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@theguild/remark-mermaid/-/remark-mermaid-0.0.5.tgz",
|
||||||
@ -13130,6 +13236,15 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@vercel/oidc": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vvo/tzdb": {
|
"node_modules/@vvo/tzdb": {
|
||||||
"version": "6.161.0",
|
"version": "6.161.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.161.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vvo/tzdb/-/tzdb-6.161.0.tgz",
|
||||||
@ -13271,6 +13386,33 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ai": {
|
||||||
|
"version": "5.0.82",
|
||||||
|
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.82.tgz",
|
||||||
|
"integrity": "sha512-wmZZfsU40qB77umrcj3YzMSk6cUP5gxLXZDPfiSQLBLegTVXPUdSJC603tR7JB5JkhBDzN5VLaliuRKQGKpUXg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/gateway": "2.0.3",
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@ai-sdk/provider-utils": "3.0.14",
|
||||||
|
"@opentelemetry/api": "1.9.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ai/node_modules/@opentelemetry/api": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.17.1",
|
"version": "8.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
@ -18595,6 +18737,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/eventsource-parser": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||||
@ -21307,6 +21458,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inngest/node_modules/zod": {
|
||||||
|
"version": "3.22.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz",
|
||||||
|
"integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/input-otp": {
|
"node_modules/input-otp": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
|
||||||
@ -22405,6 +22565,12 @@
|
|||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-schema": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||||
|
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
@ -32722,6 +32888,19 @@
|
|||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/swr": {
|
||||||
|
"version": "2.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz",
|
||||||
|
"integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwind-merge": {
|
"node_modules/tailwind-merge": {
|
||||||
"version": "1.14.0",
|
"version": "1.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||||
@ -33059,6 +33238,18 @@
|
|||||||
"real-require": "^0.2.0"
|
"real-require": "^0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/throttleit": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/through": {
|
"node_modules/through": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
@ -36088,9 +36279,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.24.1",
|
"version": "3.25.76",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
@ -36163,7 +36354,7 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.21.0",
|
"swagger-ui-react": "^5.21.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/api/node_modules/ts-pattern": {
|
"packages/api/node_modules/ts-pattern": {
|
||||||
@ -36384,7 +36575,7 @@
|
|||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/auth/node_modules/ts-pattern": {
|
"packages/auth/node_modules/ts-pattern": {
|
||||||
@ -36404,7 +36595,7 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/ee/node_modules/ts-pattern": {
|
"packages/ee/node_modules/ts-pattern": {
|
||||||
@ -36734,7 +36925,7 @@
|
|||||||
"skia-canvas": "^3.0.8",
|
"skia-canvas": "^3.0.8",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.52.0",
|
"@playwright/browser-chromium": "1.52.0",
|
||||||
@ -37164,7 +37355,7 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"trpc-to-openapi": "2.0.4",
|
"trpc-to-openapi": "2.0.4",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/trpc/node_modules/ts-pattern": {
|
"packages/trpc/node_modules/ts-pattern": {
|
||||||
@ -37235,7 +37426,7 @@
|
|||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@ -71,15 +71,13 @@
|
|||||||
"@documenso/prisma": "^0.0.0",
|
"@documenso/prisma": "^0.0.0",
|
||||||
"@lingui/conf": "^5.2.0",
|
"@lingui/conf": "^5.2.0",
|
||||||
"@lingui/core": "^5.2.0",
|
"@lingui/core": "^5.2.0",
|
||||||
|
"ai": "^5.0.82",
|
||||||
"inngest-cli": "^0.29.1",
|
"inngest-cli": "^0.29.1",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"mupdf": "^1.0.0",
|
"mupdf": "^1.0.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"typescript": "5.6.2",
|
"typescript": "5.6.2",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"zod": "3.24.1"
|
|
||||||
},
|
},
|
||||||
"trigger.dev": {
|
"trigger.dev": {
|
||||||
"endpointId": "documenso-app"
|
"endpointId": "documenso-app"
|
||||||
|
|||||||
@ -25,6 +25,6 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"swagger-ui-react": "^5.21.0",
|
"swagger-ui-react": "^5.21.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +20,6 @@
|
|||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,6 +19,6 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@
|
|||||||
"skia-canvas": "^3.0.8",
|
"skia-canvas": "^3.0.8",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.52.0",
|
"@playwright/browser-chromium": "1.52.0",
|
||||||
|
|||||||
@ -21,6 +21,6 @@
|
|||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
"trpc-to-openapi": "2.0.4",
|
"trpc-to-openapi": "2.0.4",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/tsconfig/process-env.d.ts
vendored
5
packages/tsconfig/process-env.d.ts
vendored
@ -80,6 +80,11 @@ declare namespace NodeJS {
|
|||||||
NEXT_PRIVATE_INNGEST_APP_ID?: string;
|
NEXT_PRIVATE_INNGEST_APP_ID?: string;
|
||||||
NEXT_PRIVATE_INNGEST_EVENT_KEY?: string;
|
NEXT_PRIVATE_INNGEST_EVENT_KEY?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Generative AI (Gemini)
|
||||||
|
*/
|
||||||
|
GOOGLE_GENERATIVE_AI_API_KEY?: string;
|
||||||
|
|
||||||
POSTGRES_URL?: string;
|
POSTGRES_URL?: string;
|
||||||
DATABASE_URL?: string;
|
DATABASE_URL?: string;
|
||||||
POSTGRES_PRISMA_URL?: string;
|
POSTGRES_PRISMA_URL?: string;
|
||||||
|
|||||||
@ -78,6 +78,6 @@
|
|||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"zod": "3.24.1"
|
"zod": "3.25.76"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user