mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-08 17:22:28 +08:00
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
|
|
// Shared helper functions for chat route
|
||
|
|
// Exported for testing
|
||
|
|
|
||
|
|
// File upload limits (must match client-side)
|
||
|
|
export const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
|
||
|
|
export const MAX_FILES = 5
|
||
|
|
|
||
|
|
// Helper function to validate file parts in messages
|
||
|
|
export function validateFileParts(messages: any[]): {
|
||
|
|
valid: boolean
|
||
|
|
error?: string
|
||
|
|
} {
|
||
|
|
const lastMessage = messages[messages.length - 1]
|
||
|
|
const fileParts =
|
||
|
|
lastMessage?.parts?.filter((p: any) => p.type === "file") || []
|
||
|
|
|
||
|
|
if (fileParts.length > MAX_FILES) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
error: `Too many files. Maximum ${MAX_FILES} allowed.`,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const filePart of fileParts) {
|
||
|
|
// Data URLs format: data:image/png;base64,<data>
|
||
|
|
// Base64 increases size by ~33%, so we check the decoded size
|
||
|
|
if (filePart.url?.startsWith("data:")) {
|
||
|
|
const base64Data = filePart.url.split(",")[1]
|
||
|
|
if (base64Data) {
|
||
|
|
const sizeInBytes = Math.ceil((base64Data.length * 3) / 4)
|
||
|
|
if (sizeInBytes > MAX_FILE_SIZE) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
error: `File exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { valid: true }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper function to check if diagram is minimal/empty
|
||
|
|
export function isMinimalDiagram(xml: string): boolean {
|
||
|
|
const stripped = xml.replace(/\s/g, "")
|
||
|
|
return !stripped.includes('id="2"')
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper function to replace historical tool call XML with placeholders
|
||
|
|
// This reduces token usage and forces LLM to rely on the current diagram XML (source of truth)
|
||
|
|
// Also fixes invalid/undefined inputs from interrupted streaming
|
||
|
|
export function replaceHistoricalToolInputs(messages: any[]): any[] {
|
||
|
|
return messages.map((msg) => {
|
||
|
|
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
||
|
|
return msg
|
||
|
|
}
|
||
|
|
const replacedContent = msg.content
|
||
|
|
.map((part: any) => {
|
||
|
|
if (part.type === "tool-call") {
|
||
|
|
const toolName = part.toolName
|
||
|
|
// Fix invalid/undefined inputs from interrupted streaming
|
||
|
|
if (
|
||
|
|
!part.input ||
|
||
|
|
typeof part.input !== "object" ||
|
||
|
|
Object.keys(part.input).length === 0
|
||
|
|
) {
|
||
|
|
// Skip tool calls with invalid inputs entirely
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
if (
|
||
|
|
toolName === "display_diagram" ||
|
||
|
|
toolName === "edit_diagram"
|
||
|
|
) {
|
||
|
|
return {
|
||
|
|
...part,
|
||
|
|
input: {
|
||
|
|
placeholder:
|
||
|
|
"[XML content replaced - see current diagram XML in system context]",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return part
|
||
|
|
})
|
||
|
|
.filter(Boolean) // Remove null entries (invalid tool calls)
|
||
|
|
return { ...msg, content: replacedContent }
|
||
|
|
})
|
||
|
|
}
|