fix(chart): improve error handling and color variety

- Add quotaExceeded flag to response for better error UX
- Show dedicated quota exceeded state with upgrade button
- Improve AI prompt to better detect data patterns
- Add chart type-specific colors (blue, indigo, emerald, violet, etc.)
- Replace generic primary/10 colors with varied accent colors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Antigravity
2026-05-23 09:19:52 +00:00
parent a122a0eade
commit 18ffd76c1e
16 changed files with 1042 additions and 134 deletions

View File

@@ -1027,6 +1027,38 @@ You MUST use the task_extract tool. Do NOT respond with text, call the tool dire
},
}
function extractJsonFromText(text: string): any {
if (!text) return null
// Try direct parsing first
try {
const parsed = JSON.parse(text.trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
// Try extracting markdown code block
const jsonBlockRegex = /```json\s*([\s\S]*?)\s*```/i
const match = text.match(jsonBlockRegex)
if (match && match[1]) {
try {
const parsed = JSON.parse(match[1].trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
}
// Try extracting any { ... } or [ ... ] block
const braceRegex = /(\{[\s\S]*\}|\[[\s\S]*\])/
const braceMatch = text.match(braceRegex)
if (braceMatch && braceMatch[1]) {
try {
const parsed = JSON.parse(braceMatch[1].trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
}
return null
}
// --- Tool-Use Agent ---
async function executeToolUseAgent(
@@ -1354,6 +1386,15 @@ async function executeToolUseAgent(
const duration = Date.now() - startTime
// Check if AI already created a note via note_create tool
// Or if excalidraw/slide generator created a canvas
let existingNoteId: string | null = null
let canvasId: string | null = null
const scrapedUrls: string[] = []
let specificToolCalled = false
let fallbackSuccess = false
let parsedFallbackJson: any = null
// Détecte si le modèle ne supporte pas le function calling
// (il retourne le JSON de l'outil comme texte brut au lieu de l'exécuter)
const totalToolCallsCheck = result.steps.reduce((acc, s) => acc + s.toolCalls.length, 0)
@@ -1371,16 +1412,96 @@ async function executeToolUseAgent(
}
if (agentType === 'slide-generator' || agentType === 'excalidraw-generator') {
const toolName = agentType === 'slide-generator' ? 'generate_slides' : 'generate_excalidraw'
await prisma.agentAction.update({
where: { id: actionId },
data: {
status: 'failure',
log: lang === 'fr'
? `L'IA n'a pas appelé l'outil ${toolName}. Le modèle a répondu avec du texte au lieu de générer le fichier. Modèle: "${sysConfig.AI_MODEL_CHAT}". Essayez un modèle compatible avec le function calling.`
: `The AI did not call the ${toolName} tool. The model responded with text instead of generating the file. Model: "${sysConfig.AI_MODEL_CHAT}". Try a model that supports function calling.`,
parsedFallbackJson = extractJsonFromText(result.text)
if (parsedFallbackJson) {
try {
if (agentType === 'slide-generator') {
let slides: any[] = []
let title = agent.name || "Présentation"
let theme = agent.slideTheme || "architectural-saas"
if (Array.isArray(parsedFallbackJson)) {
slides = parsedFallbackJson
} else if (parsedFallbackJson && typeof parsedFallbackJson === 'object') {
if (Array.isArray(parsedFallbackJson.slides)) {
slides = parsedFallbackJson.slides
} else if (parsedFallbackJson.slides && typeof parsedFallbackJson.slides === 'object') {
// nested structure support
} else {
if (parsedFallbackJson.type) {
slides = [parsedFallbackJson]
} else {
const arrays = Object.values(parsedFallbackJson).filter(val => Array.isArray(val))
if (arrays.length > 0) {
slides = arrays[0] as any[]
}
}
}
if (typeof parsedFallbackJson.title === 'string') title = parsedFallbackJson.title
if (typeof parsedFallbackJson.theme === 'string') theme = parsedFallbackJson.theme
}
const registered = toolRegistry.get('generate_slides')
if (registered) {
console.log('[AgentExecutor] Running manual fallback execution for generate_slides')
const slideTool = registered.buildTool(ctx)
const executionResult = await slideTool.execute({ title, theme, slides })
if (executionResult && executionResult.success && executionResult.canvasId) {
canvasId = executionResult.canvasId
specificToolCalled = true
fallbackSuccess = true
}
}
} else {
let diagramStr = ""
let title = agent.name || "Diagramme"
if (parsedFallbackJson && typeof parsedFallbackJson === 'object') {
if (typeof parsedFallbackJson.diagram === 'string') {
diagramStr = parsedFallbackJson.diagram
if (typeof parsedFallbackJson.title === 'string') title = parsedFallbackJson.title
} else if (parsedFallbackJson.diagram && typeof parsedFallbackJson.diagram === 'object') {
diagramStr = JSON.stringify(parsedFallbackJson.diagram)
if (typeof parsedFallbackJson.title === 'string') title = parsedFallbackJson.title
} else {
diagramStr = JSON.stringify(parsedFallbackJson)
if (typeof parsedFallbackJson.title === 'string') title = parsedFallbackJson.title
}
} else if (typeof parsedFallbackJson === 'string') {
diagramStr = parsedFallbackJson
}
if (diagramStr) {
const registered = toolRegistry.get('generate_excalidraw')
if (registered) {
console.log('[AgentExecutor] Running manual fallback execution for generate_excalidraw')
const excalidrawTool = registered.buildTool(ctx)
const executionResult = await excalidrawTool.execute({ title, diagram: diagramStr })
if (executionResult && executionResult.success && executionResult.canvasId) {
canvasId = executionResult.canvasId
specificToolCalled = true
fallbackSuccess = true
}
}
}
}
} catch (err) {
console.error('[AgentExecutor] Fallback execution failed:', err)
}
})
return { success: false, actionId, error: `AI did not call ${toolName} tool` }
}
if (!fallbackSuccess) {
await prisma.agentAction.update({
where: { id: actionId },
data: {
status: 'failure',
log: lang === 'fr'
? `L'IA n'a pas appelé l'outil ${toolName}. Le modèle a répondu avec du texte au lieu de générer le fichier. Modèle: "${sysConfig.AI_MODEL_CHAT}". Essayez un modèle compatible avec le function calling.`
: `The AI did not call the ${toolName} tool. The model responded with text instead of generating the file. Model: "${sysConfig.AI_MODEL_CHAT}". Try a model that supports function calling.`,
}
})
return { success: false, actionId, error: `AI did not call ${toolName} tool` }
}
}
}
@@ -1395,12 +1516,27 @@ async function executeToolUseAgent(
})),
}))
// Check if AI already created a note via note_create tool
// Or if excalidraw/slide generator created a canvas
let existingNoteId: string | null = null
let canvasId: string | null = null
const scrapedUrls: string[] = []
let specificToolCalled = false
if (fallbackSuccess) {
toolLog.push({
step: toolLog.length + 1,
text: "Manual JSON parsing & fallback execution succeeded.",
toolCalls: [{
id: "fallback",
type: "function",
function: {
name: agentType === 'slide-generator' ? 'generate_slides' : 'generate_excalidraw',
arguments: JSON.stringify(parsedFallbackJson),
}
}] as any,
toolResults: [{
toolCallId: "fallback",
toolName: agentType === 'slide-generator' ? 'generate_slides' : 'generate_excalidraw',
type: "tool-result",
result: { success: true, canvasId }
}] as any
})
}
const requiredTool = isFileGenerator
? (agentType === 'slide-generator' ? ['generate_slides'] : ['generate_excalidraw'])
: null