import request from '@/utils/request' import type { AiConfigPageRequest, AiConfigCreateRequest, AiConfigUpdateRequest, AiProvider, AiEndpointConfig, AiSceneBinding, AiCallLog, AiRuntimeRequest, AiEndpointRuntimeRequest } from '@/types/aiconfig' import type { LogQueryParams, PageResult } from '@/types/common' // 分页查询AI配置 export function getAiConfigPage(params: AiConfigPageRequest) { return request({ url: '/aiConfig/page', method: 'get', params }) } // 根据ID获取AI配置 export function getAiConfigById(id: string) { return request({ url: '/aiConfig/detail', method: 'get', params: { id } }) } // 创建AI配置 export function createAiConfig(data: AiConfigCreateRequest) { return request({ url: '/aiConfig/create', method: 'post', data }) } // 更新AI配置 export function updateAiConfig(data: AiConfigUpdateRequest) { return request({ url: '/aiConfig/update', method: 'put', data }) } // 删除AI配置 export function deleteAiConfig(id: string) { return request({ url: '/aiConfig/delete', method: 'delete', params: { id } }) } // 根据配置类型查询AI配置 export function getAiConfigByType(configType: string) { return request({ url: '/aiConfig/byConfigType', method: 'get', params: { configType } }) } // 根据服务提供商查询AI配置 export function getAiConfigByProvider(provider: string) { return request({ url: '/aiConfig/byProvider', method: 'get', params: { provider } }) } // 根据使用场景查询AI配置 export function getAiConfigByUsageScenario(usageScenario: string) { return request({ url: '/aiConfig/byUsageScenario', method: 'get', params: { usageScenario } }) } // 根据环境查询AI配置 export function getAiConfigByEnvironment(environment: string) { return request({ url: '/aiConfig/byEnvironment', method: 'get', params: { environment } }) } // 查询已启用的AI配置 export function getEnabledAiConfigs() { return request({ url: '/aiConfig/enabled', method: 'get' }) } // 查询已禁用的AI配置 export function getDisabledAiConfigs() { return request({ url: '/aiConfig/disabled', method: 'get' }) } // 查询默认配置 export function getDefaultAiConfigs() { return request({ url: '/aiConfig/default', method: 'get' }) } // 根据配置键值查询AI配置 export function getAiConfigByKey(configKey: string) { return request({ url: '/aiConfig/byConfigKey', method: 'get', params: { configKey } }) } // 启用AI配置 export function enableAiConfig(id: string) { return request({ url: '/aiConfig/enable', method: 'put', params: { id } }) } // 禁用AI配置 export function disableAiConfig(id: string) { return request({ url: '/aiConfig/disable', method: 'put', params: { id } }) } // 设置为默认配置 export function setAsDefaultConfig(id: string) { return request({ url: '/aiConfig/setDefault', method: 'put', params: { id } }) } // 取消默认配置 export function unsetDefaultConfig(id: string) { return request({ url: '/aiConfig/unsetDefault', method: 'put', params: { id } }) } // 查询最优配置 export function getBestAiConfig(usageScenario: string, environment: string) { return request({ url: '/aiConfig/bestConfig', method: 'get', params: { usageScenario, environment } }) } // 统计已启用配置数量 export function countEnabledConfigs() { return request({ url: '/aiConfig/countEnabled', method: 'get' }) } // 统计已禁用配置数量 export function countDisabledConfigs() { return request({ url: '/aiConfig/countDisabled', method: 'get' }) } // 统计默认配置数量 export function countDefaultConfigs() { return request({ url: '/aiConfig/countDefault', method: 'get' }) } // 根据配置类型统计数量 export function countByConfigType(configType: string) { return request({ url: '/aiConfig/countByConfigType', method: 'get', params: { configType } }) } // 根据服务提供商统计数量 export function countByProvider(provider: string) { return request({ url: '/aiConfig/countByProvider', method: 'get', params: { provider } }) } // 测试后更新AI配置 export function updateAiConfigFromTest(data: any) { return request({ url: '/aiConfig/updateFromTest', method: 'put', data }) } export function listAiProviders() { return request({ url: '/ai/providers', method: 'get' }) } export function saveAiProvider(data: AiProvider) { return request({ url: '/ai/providers', method: data.id ? 'put' : 'post', data }) } export function deleteAiProvider(id: string) { return request({ url: '/ai/providers', method: 'delete', params: { id } }) } export function listAiEndpoints() { return request({ url: '/ai/endpoints', method: 'get' }) } export function getEndpointTestTemplate(id: string) { return request({ url: '/ai/endpoints/test-template', method: 'get', params: { id } }) } export function saveAiEndpoint(data: AiEndpointConfig) { return request({ url: '/ai/endpoints', method: data.id ? 'put' : 'post', data }) } export function deleteAiEndpoint(id: string) { return request({ url: '/ai/endpoints', method: 'delete', params: { id } }) } export function listAiScenes() { return request({ url: '/ai/scenes', method: 'get' }) } export function getSceneTestTemplate(sceneCode: string) { return request({ url: '/ai/scenes/test-template', method: 'get', params: { sceneCode } }) } export function saveAiScene(data: AiSceneBinding) { return request({ url: '/ai/scenes', method: data.id ? 'put' : 'post', data }) } export function deleteAiScene(id: string) { return request({ url: '/ai/scenes', method: 'delete', params: { id } }) } export function listAiCallLogs(limit = 50) { return request({ url: '/ai/call-logs', method: 'get', params: { limit } }) } export function queryAiCallLogs(params: LogQueryParams) { return request>({ url: '/ai/call-logs', method: 'post', data: params }) } export function testAiRuntime(data: AiRuntimeRequest) { return request({ url: '/ai/runtime/test', method: 'post', data, timeout: 60000 }) } export interface AiRuntimeStreamEvent { type: string content?: string code?: string message?: string seq?: number metadata?: Record } function parseSseFrame(frame: string): AiRuntimeStreamEvent | null { const event = { type: 'message', data: '' } frame.split(/\r?\n/).forEach((line) => { if (line.startsWith('event:')) event.type = line.slice(6).trim() if (line.startsWith('data:')) event.data += line.slice(5).trim() }) if (!event.data) return null try { return JSON.parse(event.data) } catch { return { type: event.type, content: event.data } } } export function normalizeAiText(value?: string): string { if (!value) return '' const trimmed = value.trim() if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) return value try { const parsed = JSON.parse(trimmed) const extracted = extractTextValue(parsed) return extracted ? normalizeAiText(extracted) : value } catch { return value } } function findOverlapLength(current: string, next: string) { const max = Math.min(current.length, next.length) for (let size = max; size > 0; size -= 1) { if (current.slice(-size) === next.slice(0, size)) return size } return 0 } function mergeStreamOutput(current: string, chunk?: string) { const next = normalizeAiText(chunk || '') if (!next) return { output: current, delta: '' } if (!current) return { output: next, delta: next } if (next === current) return { output: current, delta: '' } if (next.length >= 16 && current.endsWith(next)) return { output: current, delta: '' } if (next.startsWith(current)) { return { output: next, delta: next.slice(current.length) } } const currentIndex = next.length > current.length ? next.indexOf(current) : -1 if (currentIndex >= 0) { return { output: next, delta: next.slice(currentIndex + current.length) } } const overlap = findOverlapLength(current, next) if (overlap < 8) return { output: current + next, delta: next } const delta = next.slice(overlap) return { output: current + delta, delta } } function extractTextValue(value: any): string { if (!value || typeof value !== 'object' || Array.isArray(value)) return '' for (const key of ['output', 'answer', 'content', 'text', 'result']) { const item = value[key] if (typeof item === 'string' && item.trim()) return item if (item && typeof item === 'object' && !Array.isArray(item)) { const nested = extractTextValue(item) if (nested) return nested } } for (const key of ['data', 'outputs', 'message']) { const nested = extractTextValue(value[key]) if (nested) return nested } return '' } async function fetchSseStream( url: string, body: Record, onEvent: (event: AiRuntimeStreamEvent, output: string) => void ) { const token = localStorage.getItem('adminToken') const response = await fetch(`${import.meta.env.VITE_APP_BASE_API}${url}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify(body) }) if (!response.ok || !response.body) { throw new Error(`流式测试请求失败(${response.status})`) } const reader = response.body.getReader() const decoder = new TextDecoder('utf-8') let buffer = '' let output = '' let recovered = false const finishRecovered = (event: AiRuntimeStreamEvent, message?: string) => { if (!output.trim()) return false recovered = true onEvent({ type: 'done', metadata: { ...(event.metadata || {}), recovered: true, warningCode: event.code, warningMessage: message || event.message } }, output) return true } const consumeText = (text: string) => { buffer += text const frames = buffer.split(/\r?\n\r?\n/) buffer = frames.pop() || '' frames.forEach((frame) => { const event = parseSseFrame(frame) if (!event) return if (event.type === 'delta') { output = mergeStreamOutput(output, event.content).output } onEvent(event, output) if (event.type === 'error' && finishRecovered(event)) { return } if (event.type === 'error') { throw new Error(event.message || event.code || '流式测试失败') } }) } try { while (true) { const { value, done } = await reader.read() if (done) break consumeText(decoder.decode(value, { stream: true })) if (recovered) break } } catch (error: any) { if (!finishRecovered({ type: 'error', message: error?.message })) { throw error } } if (recovered) return output consumeText(decoder.decode()) if (buffer.trim()) consumeText('\n\n') return output } export async function streamAiRuntime( data: AiRuntimeRequest, onEvent: (event: AiRuntimeStreamEvent, output: string) => void ) { return fetchSseStream('/ai/runtime/stream', data, onEvent) } export async function streamEndpointRuntime( data: AiEndpointRuntimeRequest, onEvent: (event: AiRuntimeStreamEvent, output: string) => void ) { return fetchSseStream('/ai/endpoint/stream', data, onEvent) } export function testEndpointRuntime(data: AiEndpointRuntimeRequest) { return request({ url: '/ai/endpoint/test', method: 'post', data, timeout: 60000 }) }