/** * Workflow Service * * Integrates with external AI platform workflow to generate documents. * The workflow connects to the local backend export records API and returns document URLs. * * API Endpoint: http://114.242.25.27:3000/api/v2/chat/completions * Authentication: Bearer token in Authorization header * Workflow: Generates document → Calls local backend → Returns export record URL * * @module services/workflowService */ import type { ExportRecordInfo } from '../types/chat'; /** * Workflow chat message */ interface WorkflowMessage { role: 'user' | 'assistant' | 'system'; content: string; } /** * Workflow request */ interface WorkflowRequest { chatId: string; stream?: boolean; detail?: boolean; messages: WorkflowMessage[]; } /** * Workflow response from AI platform * The workflow internally calls http://192.168.0.195:8000/api/v1/export/records * and embeds the export record information in the response */ interface WorkflowResponse { content?: string; choices?: Array<{ message?: { content: string; }; }>; // Export record data returned by workflow exportRecord?: { recordId: string; userId: string; fileName: string; fileSize: number; downloadUrl: string; documentId: string; styleId: string; createdAt: number; }; // Or records array format records?: Array<{ recordId: string; userId: string; fileName: string; fileSize: number; downloadUrl: string; documentId: string; styleId: string; createdAt: number; }>; } /** * Configuration for Workflow API */ interface WorkflowConfig { apiUrl: string; apiKey: string; } /** * Get Workflow configuration from environment variables */ const getWorkflowConfig = (): WorkflowConfig => { const apiUrl = import.meta.env.VITE_WORKFLOW_API_URL; const apiKey = import.meta.env.VITE_WORKFLOW_API_KEY; if (!apiUrl) { throw new Error('VITE_WORKFLOW_API_URL is not configured in environment variables'); } if (!apiKey || apiKey === 'your_workflow_api_key_here') { // Workflow API key not configured } return { apiUrl, apiKey }; }; /** * Check if user input should trigger document generation workflow * * @param input - User's text input * @returns true if input matches document generation patterns */ export const shouldTriggerWorkflow = (input: string): boolean => { const lowerInput = input.toLowerCase().trim(); // Patterns that indicate document generation requests const generatePatterns = [ '生成', '创建', '制作', '编写', 'generate', 'create', ]; const documentPatterns = [ '报告', '文档', '方案', '总结', '分析', 'report', 'document', 'doc', ]; // Check if input contains both a generate action and document type const hasGenerateAction = generatePatterns.some((pattern) => lowerInput.includes(pattern) ); const hasDocumentType = documentPatterns.some((pattern) => lowerInput.includes(pattern) ); return hasGenerateAction && hasDocumentType; }; /** * Call workflow to generate document and get export record URL * * This function calls the external AI platform workflow API which: * 1. Processes the user's document generation request * 2. Generates the document internally * 3. Calls the local backend API (http://192.168.0.195:8000/api/v1/export/records) * 4. Returns the export record with downloadUrl * * @param userInput - User's text input (e.g., "生成一个地质报告") * @param chatId - Chat session ID * @returns Promise resolving to AI response text and optional export record * @throws {Error} When workflow call fails */ export const triggerDocumentWorkflow = async ( userInput: string, chatId: string ): Promise<{ content: string; exportRecord?: ExportRecordInfo }> => { try { const config = getWorkflowConfig(); // If API key is not configured, skip workflow if (!config.apiKey || config.apiKey === 'your_workflow_api_key_here') { return { content: '工作流未配置,无法生成文档。请配置 VITE_WORKFLOW_API_KEY。', }; } // Call the workflow API const response = await fetch(config.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.apiKey}`, }, body: JSON.stringify({ chatId, stream: false, detail: false, messages: [ { role: 'user', content: userInput, }, ], } as WorkflowRequest), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Workflow API error (${response.status}): ${errorText}`); } const data: WorkflowResponse = await response.json(); // Extract content from response let content = data.content || data.choices?.[0]?.message?.content || '文档已生成,请查看下方链接'; // Extract export record from response let exportRecord: ExportRecordInfo | undefined; // Try to parse content as JSON if it looks like JSON if (content.trim().startsWith('{') || content.trim().startsWith('[')) { try { const parsedContent = JSON.parse(content); // Handle nested response format: { code: 0, data: { records: [...] } } if (parsedContent.code === 0 && parsedContent.data?.records && Array.isArray(parsedContent.data.records)) { const records = parsedContent.data.records; if (records.length > 0) { const record = records[0]; exportRecord = { recordId: record.recordId, fileName: record.fileName || '导出文档.docx', downloadUrl: record.downloadUrl, documentId: record.documentId || record.recordId, }; // Update content to be more user-friendly content = '✅ 文档已生成,点击下方卡片预览或下载'; } } // Handle direct records array format else if (parsedContent.records && Array.isArray(parsedContent.records) && parsedContent.records.length > 0) { const record = parsedContent.records[0]; exportRecord = { recordId: record.recordId, fileName: record.fileName || '导出文档.docx', downloadUrl: record.downloadUrl, documentId: record.documentId || record.recordId, }; // Update content to be more user-friendly content = '✅ 文档已生成,点击下方卡片预览或下载'; } // Handle single record object format (not wrapped in array) else if (parsedContent.recordId && parsedContent.downloadUrl) { exportRecord = { recordId: parsedContent.recordId, fileName: parsedContent.fileName || '导出文档.docx', downloadUrl: parsedContent.downloadUrl, documentId: parsedContent.documentId || parsedContent.recordId, }; // Update content to be more user-friendly content = '✅ 文档已生成,点击下方卡片预览或下载'; } // Handle nested data format with single record else if (parsedContent.data && parsedContent.data.recordId) { const record = parsedContent.data; exportRecord = { recordId: record.recordId, fileName: record.fileName || '导出文档.docx', downloadUrl: record.downloadUrl, documentId: record.documentId || record.recordId, }; // Update content to be more user-friendly content = '✅ 文档已生成,点击下方卡片预览或下载'; } } catch (e) { // Content looks like JSON but failed to parse, log for debugging console.warn('Failed to parse JSON content:', e); console.warn('Content:', content); } } // Check for export record in top-level response if (!exportRecord && data.exportRecord) { // Single export record format exportRecord = { recordId: data.exportRecord.recordId, fileName: data.exportRecord.fileName, downloadUrl: data.exportRecord.downloadUrl, documentId: data.exportRecord.documentId, }; content = '✅ 文档已生成,点击下方卡片预览或下载'; } else if (!exportRecord && data.records && data.records.length > 0) { // Records array format (take the first one) const record = data.records[0]; exportRecord = { recordId: record.recordId, fileName: record.fileName, downloadUrl: record.downloadUrl, documentId: record.documentId, }; content = '✅ 文档已生成,点击下方卡片预览或下载'; } return { content, exportRecord, }; } catch (error) { // Return error message instead of throwing // This allows the chat to continue even if workflow fails if (error instanceof Error && error.message.includes('not configured')) { return { content: '⚠️ 工作流未配置,无法生成文档', }; } return { content: '⚠️ 文档生成失败: ' + (error instanceof Error ? error.message : '未知错误'), }; } }; /** * Extract report type from user input * * @param input - User's text input * @returns Report type string (e.g., "地质报告", "技术报告") */ export const extractReportType = (input: string): string => { const lowerInput = input.toLowerCase(); if (lowerInput.includes('地质')) { return '地质报告'; } else if (lowerInput.includes('技术')) { return '技术报告'; } else if (lowerInput.includes('分析')) { return '分析报告'; } else if (lowerInput.includes('项目')) { return '项目报告'; } else if (lowerInput.includes('总结')) { return '总结报告'; } else { return '报告'; } }; /** * Export the workflow service */ export const workflowService = { shouldTriggerWorkflow, triggerDocumentWorkflow, extractReportType, }; export default workflowService;