import { useMemo, useCallback } from 'react' import { request } from '@/http/request' import type { TaskArtifact } from '../types' import type { PlatformConfig } from './useNovaEvents' export interface ConversationInfo { conversation_id: string title?: string task_status?: string is_read?: boolean is_favourite?: boolean } interface UseNovaServiceProps { platformConfig?: PlatformConfig getToken?: () => string | undefined getTenantId?: () => string | undefined conversationId?: string } interface DirFileItem { desc?: string file_name?: string file_type?: string last_modified?: number path?: string } const toAbsoluteHttpUrl = (value: unknown): string | null => { if (typeof value !== 'string' || !value) return null if (value.startsWith('http://') || value.startsWith('https://')) return value return null } const extractDirFileList = (payload: unknown): DirFileItem[] => { if (Array.isArray(payload)) return payload if (!payload || typeof payload !== 'object') return [] const obj = payload as Record if (Array.isArray(obj.data)) return obj.data as DirFileItem[] return [] } const normalizeFileName = (name: string): string => { const raw = name.trim() if (!raw) return '' const withoutPrefix = raw.startsWith('/upload/') ? raw.slice('/upload/'.length) : raw return withoutPrefix } const resolvePathByName = (files: DirFileItem[], fileName?: string): string | null => { if (!fileName) return null const normalizedName = normalizeFileName(fileName) if (!normalizedName) return null const expectedSegment = `upload/${normalizedName}` const matched = files.find(item => { if (typeof item.file_name !== 'string' || !item.file_name) return false return item.file_name.includes(expectedSegment) }) return matched?.path || null } export function useNovaService({ platformConfig, conversationId, }: UseNovaServiceProps) { // API Client Singleton const apiClient = useMemo(() => { if (!platformConfig?.apiBaseUrl) { return null } return request }, [platformConfig]) // Get Artifact URL Method const getArtifactUrl = useCallback( async ( artifact: TaskArtifact, params?: Record ): Promise<{ data: string }> => { try { if (!apiClient) { throw new Error('API client is not initialized') } const taskId = artifact.task_id || conversationId let resolvedPath = artifact.path if (taskId) { const dirFilesResponse = await apiClient.post( '/v1/super_agent/chat/get_dir_file', { task_id: taskId } ) const dirFiles = extractDirFileList(dirFilesResponse) const matchedPath = resolvePathByName(dirFiles, artifact.file_name) if (matchedPath) { resolvedPath = matchedPath } } // Call OSS URL interface const response = await apiClient.post('/chat/oss_url', { file_path: resolvedPath, task_id: taskId, params }) const signedUrl = toAbsoluteHttpUrl(response) || '' const fallback = toAbsoluteHttpUrl(artifact.path) || '' return { data: signedUrl || fallback } } catch (error) { console.error('Failed to fetch artifact URL:', error) return { data: toAbsoluteHttpUrl(artifact.path) || '' } } }, [conversationId, apiClient] ) const stopChat = useCallback(async () => { try { if (!apiClient) { throw new Error('API client is not initialized') } if (!conversationId) { throw new Error('Conversation ID is required') } await apiClient.get('/chat/stop', { conversation_id: conversationId }) } catch (error) { console.error('Failed to stop chat:', error) throw error } }, [conversationId, apiClient]) /** 获取会话信息列表(用于轮询 task_status),使用 novaRequest */ const getConversationInfoList = useCallback( async (conversationIds: string[]) => { if (conversationIds.length === 0) { return { data: [] as ConversationInfo[] } } const res = await request.post<{ data?: ConversationInfo[] } | ConversationInfo[]>( '/conversation/info', { conversation_ids: conversationIds } ) const list = Array.isArray(res) ? res : (res?.data ?? []) return { data: list } }, [] ) return { apiClient, getArtifactUrl, stopChat, getConversationInfoList, } }