diff --git a/f/web-kboss/src/api/AI/ai.js b/f/web-kboss/src/api/AI/ai.js new file mode 100644 index 0000000..e43c230 --- /dev/null +++ b/f/web-kboss/src/api/AI/ai.js @@ -0,0 +1,70 @@ +const AI_CHAT_URL = 'https://dev.ncmatch.cn/api/ai-chat/chat/stream/' + +// AI 对话 +export const reqAIChat = (data, onMessage) => { + return fetch(AI_CHAT_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }).then(async(response) => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + if (!response.body) { + throw new Error('当前浏览器不支持流式响应') + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let fullResponse = '' + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + lines.forEach((line) => { + if (!line.startsWith('data: ')) { + return + } + + try { + const parsed = JSON.parse(line.slice(6)) + if (parsed && parsed.content) { + fullResponse += parsed.content + if (typeof onMessage === 'function') { + onMessage(fullResponse) + } + } + } catch (error) { + // Ignore incomplete stream fragments until the next chunk arrives. + } + }) + } + + if (buffer && buffer.startsWith('data: ')) { + try { + const parsed = JSON.parse(buffer.slice(6)) + if (parsed && parsed.content) { + fullResponse += parsed.content + if (typeof onMessage === 'function') { + onMessage(fullResponse) + } + } + } catch (error) { + // Ignore invalid trailing fragments. + } + } + + return { + content: fullResponse + } + }) +} diff --git a/f/web-kboss/src/assets/css/iconfont/demo_index.html b/f/web-kboss/src/assets/css/iconfont/demo_index.html index 3c4d26b..20b7491 100644 --- a/f/web-kboss/src/assets/css/iconfont/demo_index.html +++ b/f/web-kboss/src/assets/css/iconfont/demo_index.html @@ -54,6 +54,12 @@