diff --git a/frontend/src/utils/chat/agent.js b/frontend/src/utils/chat/agent.js index 4c1ae9e9b..9f3bb479d 100644 --- a/frontend/src/utils/chat/agent.js +++ b/frontend/src/utils/chat/agent.js @@ -159,7 +159,6 @@ export default function handleSocketResponse(socket, event, setChatHistory) { } if (type === "textResponseChunk") { - console.log("textResponseChunk", data.content); return prev .map((msg) => msg.uuid === uuid diff --git a/server/utils/agents/aibitat/plugins/create-files/pptx/create-presentation.js b/server/utils/agents/aibitat/plugins/create-files/pptx/create-presentation.js index 9c4cebf9f..bfdc3ea59 100644 --- a/server/utils/agents/aibitat/plugins/create-files/pptx/create-presentation.js +++ b/server/utils/agents/aibitat/plugins/create-files/pptx/create-presentation.js @@ -219,6 +219,7 @@ module.exports.CreatePptxPresentation = { section, presentationTitle: title, conversationContext, + sectionPrefix: `${i + 1}/${totalSections}`, }); const slideCount = sectionResult.slides?.length || 0; diff --git a/server/utils/agents/aibitat/plugins/create-files/pptx/section-agent.js b/server/utils/agents/aibitat/plugins/create-files/pptx/section-agent.js index 250825ae4..3ea76ee45 100644 --- a/server/utils/agents/aibitat/plugins/create-files/pptx/section-agent.js +++ b/server/utils/agents/aibitat/plugins/create-files/pptx/section-agent.js @@ -1,9 +1,8 @@ const AIbitat = require("../../../index.js"); -const { safeJsonParse } = require("../../../../../http"); -const SECTION_BUILDER_PROMPT = `You are a focused presentation section builder. Your ONLY task is to create detailed, well-researched slides for ONE section of a PowerPoint presentation. +const SECTION_BUILDER_PROMPT = `You are a focused presentation section builder. Your ONLY task is to create detailed slides for ONE section of a PowerPoint presentation. -You have access to web search and web scraping tools. Use them when you need current data, statistics, or specific information to strengthen your slides. Only research what is directly relevant. +You have access to web search and web scraping tools, but only use them when the topic genuinely requires up-to-date information you don't already know (e.g., current statistics, recent events, specific company data). For general knowledge topics, create slides directly from your existing knowledge. RULES: - Create 2-5 slides for this section (no more) @@ -12,30 +11,13 @@ RULES: - Include speaker notes with key talking points - Do NOT add a title slide - only section content -When finished, respond with ONLY a valid JSON object in this exact format: -{ - "slides": [ - { - "layout": "section", - "title": "Section Title", - "subtitle": "Optional subtitle" - }, - { - "layout": "content", - "title": "Slide Title", - "content": ["Bullet point 1", "Bullet point 2", "Bullet point 3"], - "notes": "Speaker notes here" - } - ] -} +When finished, you MUST call the submit-section-slides tool with your slides. Do not respond with raw JSON - always use the tool. Available slide layouts: - "section": Divider slide with title + optional subtitle - "content": Bullet points with title + content array + optional notes - May include "table": { "headers": ["Col1", "Col2"], "rows": [["a", "b"]] } -- "blank": Empty slide - -Your ENTIRE response must be valid parseable JSON. No markdown fences, no explanation, no text outside the JSON.`; +- "blank": Empty slide`; /** * Spawns a focused child AIbitat agent to build slides for a single presentation section. @@ -47,6 +29,7 @@ Your ENTIRE response must be valid parseable JSON. No markdown fences, no explan * @param {Object} options.section - Section definition { title, keyPoints?, instructions? } * @param {string} options.presentationTitle - Overall presentation title for context * @param {string} [options.conversationContext] - Recent conversation history for context + * @param {string} [options.sectionPrefix] - Progress indicator like "1/5" for UI display * @returns {Promise<{slides: Object[], citations: Object[]}>} Parsed section slides and accumulated citations */ async function runSectionAgent({ @@ -54,6 +37,7 @@ async function runSectionAgent({ section, presentationTitle, conversationContext = "", + sectionPrefix = "", }) { const log = parentAibitat.handlerProps?.log || console.log; @@ -69,7 +53,7 @@ async function runSectionAgent({ childAibitat.introspect = parentAibitat.introspect; // Filtered socket: pass through introspection but suppress reportStreamEvent - // so the raw JSON slide output doesn't render in the UI as a chat message. + // so sub-agent chatter doesn't render in the UI as a chat message. childAibitat.socket = { send: (type, content) => { if (type === "reportStreamEvent") return; @@ -83,6 +67,75 @@ async function runSectionAgent({ childAibitat.use(webBrowsing.plugin()); childAibitat.use(webScraping.plugin()); + // Internal tool for structured slide submission - not exposed as a public plugin + childAibitat.function({ + super: childAibitat, + name: "submit-section-slides", + description: + "Submit the completed slides for this presentation section. Call this tool when you have finished creating all slides.", + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + slides: { + type: "array", + description: "Array of slide objects for this section", + items: { + type: "object", + properties: { + layout: { + type: "string", + enum: ["section", "content", "blank"], + description: "The slide layout type", + }, + title: { + type: "string", + description: "The slide title", + }, + subtitle: { + type: "string", + description: "Optional subtitle (for section layout)", + }, + content: { + type: "array", + items: { type: "string" }, + description: "Bullet points (for content layout)", + }, + notes: { + type: "string", + description: "Speaker notes for this slide", + }, + table: { + type: "object", + description: "Optional table data", + properties: { + headers: { + type: "array", + items: { type: "string" }, + }, + rows: { + type: "array", + items: { + type: "array", + items: { type: "string" }, + }, + }, + }, + }, + }, + required: ["layout", "title"], + }, + }, + }, + required: ["slides"], + additionalProperties: false, + }, + handler: function ({ slides }) { + this.super._submittedSlides = slides; + return "Slides submitted successfully. Section complete."; + }, + }); + const functions = Array.from(childAibitat.functions.values()); const messages = [ { role: "system", content: SECTION_BUILDER_PROMPT }, @@ -105,21 +158,22 @@ async function runSectionAgent({ `[SectionAgent] Running sub-agent for section: "${section.title}" with ${functions.length} tools` ); - let responseText; + let agentName = `@section-builder`; + if (sectionPrefix) agentName = `[${sectionPrefix}] ${agentName}`; try { if (provider.supportsAgentStreaming) { - responseText = await childAibitat.handleAsyncExecution( + await childAibitat.handleAsyncExecution( provider, messages, functions, - "@section-builder" + agentName ); } else { - responseText = await childAibitat.handleExecution( + await childAibitat.handleExecution( provider, messages, functions, - "@section-builder" + agentName ); } } catch (error) { @@ -130,18 +184,19 @@ async function runSectionAgent({ // Collect any citations the child accumulated (from web-search, web-scrape, etc.) const citations = childAibitat._pendingCitations || []; - const parsed = parseSectionResponse(responseText, log); - if (!parsed || !Array.isArray(parsed.slides) || parsed.slides.length === 0) { + // Retrieve slides from the tool call (structured data, no parsing needed) + const slides = childAibitat._submittedSlides; + if (!Array.isArray(slides) || slides.length === 0) { log( - `[SectionAgent] Failed to parse structured response for "${section.title}", using fallback` + `[SectionAgent] No slides submitted for "${section.title}", using fallback` ); return { ...buildFallbackSlides(section), citations }; } log( - `[SectionAgent] Section "${section.title}" produced ${parsed.slides.length} slides, ${citations.length} citations` + `[SectionAgent] Section "${section.title}" produced ${slides.length} slides, ${citations.length} citations` ); - return { ...parsed, citations }; + return { slides, citations }; } function buildSectionPrompt({ @@ -169,42 +224,12 @@ function buildSectionPrompt({ } parts.push( - `\nResearch the topic using your tools if needed, then create 2-5 detailed slides. Respond with ONLY valid JSON.` + `\nCreate 2-5 detailed slides and submit them using the submit-section-slides tool. Only use web search/scraping if you genuinely lack the information needed.` ); return parts.join("\n"); } -/** - * Attempts multiple strategies to extract a JSON slides object from the agent response. - * Local models often wrap JSON in markdown fences or add preamble text. - */ -function parseSectionResponse(text, log) { - if (!text) return null; - - let parsed = safeJsonParse(text.trim(), null); - if (parsed?.slides) return parsed; - - // Strip markdown code fences - const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/); - if (fenceMatch) { - parsed = safeJsonParse(fenceMatch[1].trim(), null); - if (parsed?.slides) return parsed; - } - - // Extract the outermost JSON object containing "slides" - const objectMatch = text.match(/\{[\s\S]*"slides"\s*:\s*\[[\s\S]*\]\s*\}/); - if (objectMatch) { - parsed = safeJsonParse(objectMatch[0], null); - if (parsed?.slides) return parsed; - } - - log?.( - `[SectionAgent] Could not parse JSON from response: ${text.substring(0, 300)}...` - ); - return null; -} - /** * Generates basic slides from the section definition when the sub-agent fails. */