| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- /**
- * 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;
|