| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- /**
- * AI 模块 API 封装
- * SSE 流式对话 — 支持 intent / status / content / meta 多事件
- */
- const BASE_URL = 'https://your-api-domain.com/api/v1'
- // 本地调试: const BASE_URL = 'http://localhost:8000/api/v1'
- function getToken() {
- return uni.getStorageSync('access_token') || ''
- }
- function request(url, options = {}) {
- return new Promise((resolve, reject) => {
- uni.request({
- url: BASE_URL + url,
- method: options.method || 'GET',
- data: options.data,
- header: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + getToken()
- },
- success: (res) => {
- if (res.statusCode === 200) resolve(res.data)
- else if (res.statusCode === 401) {
- uni.navigateTo({ url: '/pages/login/index' })
- reject(new Error('Unauthorized'))
- } else if (res.statusCode === 429) {
- // 限流:优先读取服务器返回的具体提示信息
- const msg = res.data?.detail || res.data?.message || res.data?.error || '请求过于频繁,请稍后再试'
- reject(new Error(msg))
- } else reject(new Error(res.data?.detail || res.data?.message || res.data?.error || 'HTTP ' + res.statusCode))
- },
- fail: (err) => reject(err)
- })
- })
- }
- /**
- * SSE 流式对话(支持多事件类型)
- * callbacks: { onIntent, onStatus, onToken, onMeta, onDone, onError }
- */
- export function chatStream(data, callbacks) {
- const { onToken, onDone, onError, onIntent, onStatus, onMeta } = callbacks
- let buffer = ''
- let currentEvent = ''
- // 自动选择端点:有媒体附件走 stream-multimodal,纯文本走 stream
- const hasMedia = !!(data.media_base64 && data.media_type)
- const endpoint = hasMedia ? '/chat/stream-multimodal' : '/chat/stream'
- const reqData = {
- message: data.message || '',
- conversation_id: data.conversation_id || ''
- }
- if (hasMedia) {
- reqData.media_type = data.media_type
- reqData.media_base64 = data.media_base64
- reqData.media_mime = data.media_mime || ''
- }
- let aborted = false
- const task = uni.request({
- url: BASE_URL + endpoint,
- method: 'POST',
- data: reqData,
- header: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + getToken()
- },
- enableChunked: true,
- responseType: 'text',
- success: () => {},
- fail: (err) => {
- if (aborted) return
- if (onError) onError(err)
- }
- })
- task.onHeadersReceived((res) => {
- // 检测 HTTP 状态码:429 或非 2xx 时立即中断
- if (res.statusCode === 429) {
- aborted = true
- task.abort()
- if (onError) onError(new Error('请求过于频繁,请稍后再试'))
- return
- }
- if (res.statusCode >= 400) {
- aborted = true
- task.abort()
- if (onError) onError(new Error('服务异常,请稍后再试'))
- return
- }
- })
- task.onChunkReceived((res) => {
- try {
- const chunk = typeof res.data === 'string'
- ? res.data
- : new TextDecoder('utf-8').decode(res.data)
- buffer += chunk
- const lines = buffer.split('\n')
- buffer = lines.pop() || ''
- for (const line of lines) {
- if (line.startsWith('event: ')) {
- currentEvent = line.slice(7).trim()
- continue
- }
- if (!line.startsWith('data: ')) continue
- const d = line.slice(6)
- if (d === '[DONE]') continue
- if (currentEvent === 'intent' && onIntent) {
- onIntent(d)
- } else if (currentEvent === 'status' && onStatus) {
- onStatus(d)
- } else if (currentEvent === 'meta' && onMeta) {
- try { onMeta(JSON.parse(d)) } catch(e) {}
- } else if (currentEvent === 'content' || !currentEvent) {
- if (onToken) onToken(d)
- }
- currentEvent = ''
- }
- } catch (e) {
- console.error('SSE parse error:', e)
- }
- })
- return task
- }
- /**
- * 非流式对话(获取来源)
- */
- export function chatAsk(data) {
- return request('/chat/ask', {
- method: 'POST',
- data: {
- message: data.message,
- conversation_id: data.conversation_id || ''
- }
- })
- }
- /**
- * 对话历史
- */
- export function getHistory(page = 1, pageSize = 20) {
- return request('/chat/history?page=' + page + '&page_size=' + pageSize)
- }
- /**
- * 对话详情
- */
- export function getConversation(cid) {
- return request('/chat/history/' + cid)
- }
- /**
- * 提交反馈
- */
- export function submitFeedback(conversationId, messageId, feedback) {
- return request('/chat/feedback', {
- method: 'POST',
- data: { conversation_id: conversationId, message_id: messageId, feedback }
- })
- }
- /**
- * 药品搜索
- */
- export function searchDrug(keyword, options = {}) {
- const { category, page = 1, pageSize = 20 } = options
- let url = '/drug/search?keyword=' + encodeURIComponent(keyword) +
- '&page=' + page + '&page_size=' + pageSize
- if (category) url += '&category=' + encodeURIComponent(category)
- return request(url)
- }
- export function getDrugDetail(drugId) { return request('/drug/' + drugId) }
- export function getCategoryTree() { return request('/drug/category/tree') }
|