临时保存代码
This commit is contained in:
356
common/js/ai-service.js
Normal file
356
common/js/ai-service.js
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
import http from './http.js'
|
||||||
|
import store from '@/store/index.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 发送消息到Dify API
|
||||||
|
* @param {string} message 用户消息内容
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async sendMessage(message, options = {}) {
|
||||||
|
try {
|
||||||
|
// 获取AI配置
|
||||||
|
const aiConfig = store.getters.globalAIAgentConfig
|
||||||
|
|
||||||
|
// 构建Dify API请求参数
|
||||||
|
const params = {
|
||||||
|
url: '/api/ai/chat', // 后端代理接口
|
||||||
|
data: {
|
||||||
|
message: message,
|
||||||
|
conversation_id: options.conversationId || this.generateConversationId(),
|
||||||
|
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||||
|
stream: options.stream || false, // 是否流式响应
|
||||||
|
// Dify API参数
|
||||||
|
inputs: {},
|
||||||
|
query: message,
|
||||||
|
response_mode: options.stream ? 'streaming' : 'blocking',
|
||||||
|
user: store.state.memberInfo?.id || 'anonymous'
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有Dify配置,添加API密钥
|
||||||
|
if (aiConfig?.difyApiKey) {
|
||||||
|
params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiConfig?.difyBaseUrl) {
|
||||||
|
params.header['X-Dify-Url'] = aiConfig.difyBaseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
const response = await http.sendRequest({
|
||||||
|
...params,
|
||||||
|
async: false // 使用Promise方式
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.handleResponse(response, options)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Dify API请求失败:', error)
|
||||||
|
throw new Error('AI服务暂时不可用,请稍后重试')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式消息处理
|
||||||
|
* @param {string} message 用户消息
|
||||||
|
* @param {Function} onChunk 流式数据回调
|
||||||
|
* @param {Function} onComplete 完成回调
|
||||||
|
*/
|
||||||
|
async sendStreamMessage(message, onChunk, onComplete) {
|
||||||
|
try {
|
||||||
|
const aiConfig = store.getters.globalAIAgentConfig
|
||||||
|
|
||||||
|
// 检查配置
|
||||||
|
if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) {
|
||||||
|
throw new Error('未配置Dify服务')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建WebSocket连接或使用Server-Sent Events
|
||||||
|
if (aiConfig?.difyWsUrl) {
|
||||||
|
return this.connectWebSocket(message, onChunk, onComplete)
|
||||||
|
} else {
|
||||||
|
// 使用HTTP流式请求
|
||||||
|
return this.sendHttpStream(message, onChunk, onComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('流式消息发送失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket连接
|
||||||
|
*/
|
||||||
|
connectWebSocket(message, onChunk, onComplete) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const aiConfig = store.getters.globalAIAgentConfig
|
||||||
|
const wsUrl = aiConfig.difyWsUrl
|
||||||
|
|
||||||
|
if (!wsUrl) {
|
||||||
|
reject(new Error('未配置WebSocket地址'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
const ws = new WebSocket(wsUrl)
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
// 发送消息
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||||
|
conversation_id: this.generateConversationId()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.type === 'chunk' && onChunk) {
|
||||||
|
onChunk(data.content)
|
||||||
|
} else if (data.type === 'complete' && onComplete) {
|
||||||
|
onComplete(data.content)
|
||||||
|
ws.close()
|
||||||
|
resolve(data.content)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('WebSocket消息解析失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
console.error('WebSocket连接错误:', error)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
console.log('WebSocket连接关闭')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN || APP-PLUS
|
||||||
|
// 小程序和APP使用uni.connectSocket
|
||||||
|
uni.connectSocket({
|
||||||
|
url: wsUrl,
|
||||||
|
success: () => {
|
||||||
|
uni.onSocketOpen(() => {
|
||||||
|
uni.sendSocketMessage({
|
||||||
|
data: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||||
|
conversation_id: this.generateConversationId()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.onSocketMessage((res) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(res.data)
|
||||||
|
if (data.type === 'chunk' && onChunk) {
|
||||||
|
onChunk(data.content)
|
||||||
|
} else if (data.type === 'complete' && onComplete) {
|
||||||
|
onComplete(data.content)
|
||||||
|
uni.closeSocket()
|
||||||
|
resolve(data.content)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('WebSocket消息解析失败:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
uni.onSocketError((error) => {
|
||||||
|
console.error('WebSocket连接错误:', error)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP流式请求
|
||||||
|
*/
|
||||||
|
async sendHttpStream(message, onChunk, onComplete) {
|
||||||
|
const aiConfig = store.getters.globalAIAgentConfig
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
url: '/api/ai/chat-stream',
|
||||||
|
data: {
|
||||||
|
message: message,
|
||||||
|
conversation_id: this.generateConversationId(),
|
||||||
|
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||||
|
stream: true
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiConfig?.difyApiKey) {
|
||||||
|
params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用fetch API进行流式请求(H5环境)
|
||||||
|
// #ifdef H5
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${aiConfig.difyBaseUrl}/chat-messages`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${aiConfig.difyApiKey}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
inputs: {},
|
||||||
|
query: message,
|
||||||
|
response_mode: 'streaming',
|
||||||
|
user: store.state.memberInfo?.id || 'anonymous'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let content = ''
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
|
||||||
|
const chunk = decoder.decode(value)
|
||||||
|
const lines = chunk.split('\n')
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(line.slice(6))
|
||||||
|
if (data.event === 'text_message' && data.text) {
|
||||||
|
content += data.text
|
||||||
|
if (onChunk) onChunk(data.text)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略解析错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onComplete) onComplete(content)
|
||||||
|
return content
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('HTTP流式请求失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 非H5环境使用普通请求模拟流式效果
|
||||||
|
// #ifndef H5
|
||||||
|
const response = await http.sendRequest({
|
||||||
|
...params,
|
||||||
|
async: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟流式效果
|
||||||
|
if (response.success && response.data) {
|
||||||
|
const content = response.data
|
||||||
|
const chunkSize = 3
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
const streamInterval = setInterval(() => {
|
||||||
|
if (index < content.length) {
|
||||||
|
const chunk = content.substring(index, index + chunkSize)
|
||||||
|
index += chunkSize
|
||||||
|
if (onChunk) onChunk(chunk)
|
||||||
|
} else {
|
||||||
|
clearInterval(streamInterval)
|
||||||
|
if (onComplete) onComplete(content)
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理API响应
|
||||||
|
*/
|
||||||
|
handleResponse(response, options) {
|
||||||
|
if (response.code === 0 || response.success) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
content: response.data?.answer || response.data?.content || response.data,
|
||||||
|
conversationId: response.data?.conversation_id,
|
||||||
|
messageId: response.data?.message_id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || 'AI服务返回错误')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成会话ID
|
||||||
|
*/
|
||||||
|
generateConversationId() {
|
||||||
|
return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AI服务状态
|
||||||
|
*/
|
||||||
|
async getServiceStatus() {
|
||||||
|
try {
|
||||||
|
const aiConfig = store.getters.globalAIAgentConfig
|
||||||
|
|
||||||
|
if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
reason: '未配置Dify服务'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的健康检查
|
||||||
|
const response = await http.sendRequest({
|
||||||
|
url: '/api/ai/health',
|
||||||
|
async: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
available: response.success,
|
||||||
|
reason: response.success ? '服务正常' : '服务异常'
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
reason: '服务检查失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除会话历史
|
||||||
|
*/
|
||||||
|
async clearConversation(conversationId) {
|
||||||
|
try {
|
||||||
|
const response = await http.sendRequest({
|
||||||
|
url: '/api/ai/clear-conversation',
|
||||||
|
data: { conversation_id: conversationId },
|
||||||
|
async: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.success
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除会话失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,16 +14,19 @@ try {
|
|||||||
// 调试版本,配置说明
|
// 调试版本,配置说明
|
||||||
const devCfg = {
|
const devCfg = {
|
||||||
// 商户ID
|
// 商户ID
|
||||||
uniacid: 460, //825
|
uniacid: 926, //825
|
||||||
|
|
||||||
//api请求地址
|
//api请求地址
|
||||||
baseUrl: 'https://xcx30.5g-quickapp.com/',
|
baseUrl: 'https://xcx21.5g-quickapp.com/',
|
||||||
|
// baseUrl: 'http://localhost:8010/',
|
||||||
|
|
||||||
// 图片域名
|
// 图片域名
|
||||||
imgDomain: 'https://xcx30.5g-quickapp.com/',
|
imgDomain: 'https://xcx21.5g-quickapp.com/',
|
||||||
|
//imgDomain: 'http://localhost:8010/',
|
||||||
|
|
||||||
// H5端域名
|
// H5端域名
|
||||||
h5Domain: 'https://xcx30.5g-quickapp.com/',
|
h5Domain: 'https://xcx21.5g-quickapp.com/',
|
||||||
|
// h5Domain: 'http://localhost:8010/',
|
||||||
|
|
||||||
// // api请求地址
|
// // api请求地址
|
||||||
// baseUrl: 'https://tsaas.liveplatform.cn/',
|
// baseUrl: 'https://tsaas.liveplatform.cn/',
|
||||||
|
|||||||
@@ -277,6 +277,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import nsLoading from '@/components/ns-loading/ns-loading.vue'
|
import nsLoading from '@/components/ns-loading/ns-loading.vue'
|
||||||
|
import aiService from '@/common/js/ai-service.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ai-chat-message',
|
name: 'ai-chat-message',
|
||||||
@@ -336,7 +337,12 @@ export default {
|
|||||||
streamingMessage: null, // 流式消息对象
|
streamingMessage: null, // 流式消息对象
|
||||||
streamInterval: null, // 流式更新定时器
|
streamInterval: null, // 流式更新定时器
|
||||||
streamContent: '', // 流式内容缓存
|
streamContent: '', // 流式内容缓存
|
||||||
isStreaming: false // 是否正在流式输出
|
isStreaming: false, // 是否正在流式输出
|
||||||
|
|
||||||
|
// Dify API相关
|
||||||
|
currentConversationId: null, // 当前会话ID
|
||||||
|
isAIServiceAvailable: true, // AI服务是否可用
|
||||||
|
aiServiceError: null // AI服务错误信息
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -370,7 +376,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
sendMessage() {
|
async sendMessage() {
|
||||||
if (!this.inputText.trim()) return
|
if (!this.inputText.trim()) return
|
||||||
|
|
||||||
const userMessage = {
|
const userMessage = {
|
||||||
@@ -394,28 +400,122 @@ export default {
|
|||||||
}
|
}
|
||||||
this.messages.push(loadingMessage)
|
this.messages.push(loadingMessage)
|
||||||
|
|
||||||
// 模拟AI回复
|
try {
|
||||||
setTimeout(() => {
|
// 检查AI服务状态
|
||||||
|
const status = await aiService.getServiceStatus()
|
||||||
|
this.isAIServiceAvailable = status.available
|
||||||
|
|
||||||
|
if (!this.isAIServiceAvailable) {
|
||||||
|
throw new Error('AI服务暂不可用')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用AI服务获取回复
|
||||||
|
if (this.enableStreaming) {
|
||||||
|
// 流式响应
|
||||||
|
await this.sendStreamMessage(userMessage.content)
|
||||||
|
} else {
|
||||||
|
// 普通响应
|
||||||
|
await this.sendNormalMessage(userMessage.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI服务调用失败:', error)
|
||||||
|
|
||||||
|
// 移除加载状态
|
||||||
this.messages = this.messages.filter(msg => msg.type !== 'loading')
|
this.messages = this.messages.filter(msg => msg.type !== 'loading')
|
||||||
|
|
||||||
const aiMessage = {
|
// 显示错误消息
|
||||||
|
const errorMessage = {
|
||||||
id: ++this.messageId,
|
id: ++this.messageId,
|
||||||
role: 'ai',
|
role: 'ai',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
content: this.generateAIResponse(userMessage.content),
|
content: '抱歉,AI服务暂时不可用,请稍后重试。',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 1, text: '有帮助', type: 'like' },
|
{ id: 1, text: '重试', type: 'retry' }
|
||||||
{ id: 2, text: '没帮助', type: 'dislike' }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messages.push(aiMessage)
|
this.messages.push(errorMessage)
|
||||||
|
this.$emit('ai-response', errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
// 触发消息发送事件
|
// 触发消息发送事件
|
||||||
this.$emit('message-sent', userMessage)
|
this.$emit('message-sent', userMessage)
|
||||||
this.$emit('ai-response', aiMessage)
|
},
|
||||||
}, 1000)
|
|
||||||
|
// 发送普通消息
|
||||||
|
async sendNormalMessage(userMessage) {
|
||||||
|
const response = await aiService.sendMessage(userMessage, {
|
||||||
|
conversationId: this.currentConversationId,
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 移除加载状态
|
||||||
|
this.messages = this.messages.filter(msg => msg.type !== 'loading')
|
||||||
|
|
||||||
|
// 更新会话ID
|
||||||
|
if (response.conversationId) {
|
||||||
|
this.currentConversationId = response.conversationId
|
||||||
|
}
|
||||||
|
|
||||||
|
const aiMessage = {
|
||||||
|
id: ++this.messageId,
|
||||||
|
role: 'ai',
|
||||||
|
type: 'text',
|
||||||
|
content: response.content,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
conversationId: response.conversationId,
|
||||||
|
messageId: response.messageId,
|
||||||
|
actions: [
|
||||||
|
{ id: 1, text: '有帮助', type: 'like' },
|
||||||
|
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.push(aiMessage)
|
||||||
|
this.$emit('ai-response', aiMessage)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送流式消息
|
||||||
|
async sendStreamMessage(userMessage) {
|
||||||
|
// 创建流式消息对象
|
||||||
|
const streamMessage = {
|
||||||
|
id: ++this.messageId,
|
||||||
|
role: 'ai',
|
||||||
|
type: 'text',
|
||||||
|
content: '',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
isStreaming: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除加载状态,添加流式消息
|
||||||
|
this.messages = this.messages.filter(msg => msg.type !== 'loading')
|
||||||
|
this.messages.push(streamMessage)
|
||||||
|
|
||||||
|
// 开始流式响应
|
||||||
|
await aiService.sendStreamMessage(
|
||||||
|
userMessage,
|
||||||
|
// 流式数据回调
|
||||||
|
(chunk) => {
|
||||||
|
streamMessage.content += chunk
|
||||||
|
// 触发内容更新
|
||||||
|
this.$forceUpdate()
|
||||||
|
},
|
||||||
|
// 完成回调
|
||||||
|
(completeContent) => {
|
||||||
|
streamMessage.isStreaming = false
|
||||||
|
streamMessage.content = completeContent
|
||||||
|
|
||||||
|
// 添加操作按钮
|
||||||
|
streamMessage.actions = [
|
||||||
|
{ id: 1, text: '有帮助', type: 'like' },
|
||||||
|
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||||
|
]
|
||||||
|
|
||||||
|
this.$emit('ai-response', streamMessage)
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 生成AI回复(模拟)
|
// 生成AI回复(模拟)
|
||||||
@@ -684,9 +784,91 @@ export default {
|
|||||||
|
|
||||||
// 处理操作
|
// 处理操作
|
||||||
handleAction(action, message) {
|
handleAction(action, message) {
|
||||||
|
// 处理重试操作
|
||||||
|
if (action.type === 'retry') {
|
||||||
|
this.retryMessage(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('action-click', { action, message })
|
this.$emit('action-click', { action, message })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 重试消息
|
||||||
|
async retryMessage(message) {
|
||||||
|
// 找到对应的用户消息
|
||||||
|
const userMessageIndex = this.messages.findIndex(msg =>
|
||||||
|
msg.role === 'user' && msg.id < message.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userMessageIndex !== -1) {
|
||||||
|
const userMessage = this.messages[userMessageIndex]
|
||||||
|
|
||||||
|
// 移除错误消息
|
||||||
|
this.messages = this.messages.filter(msg => msg.id !== message.id)
|
||||||
|
|
||||||
|
// 重新发送消息
|
||||||
|
await this.sendMessageWithContent(userMessage.content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 使用指定内容发送消息
|
||||||
|
async sendMessageWithContent(content) {
|
||||||
|
const userMessage = {
|
||||||
|
id: ++this.messageId,
|
||||||
|
role: 'user',
|
||||||
|
type: 'text',
|
||||||
|
content: content,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.push(userMessage)
|
||||||
|
|
||||||
|
// 显示AI正在输入
|
||||||
|
const loadingMessage = {
|
||||||
|
id: ++this.messageId,
|
||||||
|
role: 'ai',
|
||||||
|
type: 'loading',
|
||||||
|
content: '',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
this.messages.push(loadingMessage)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用AI服务获取回复
|
||||||
|
if (this.enableStreaming) {
|
||||||
|
// 流式响应
|
||||||
|
await this.sendStreamMessage(userMessage.content)
|
||||||
|
} else {
|
||||||
|
// 普通响应
|
||||||
|
await this.sendNormalMessage(userMessage.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI服务调用失败:', error)
|
||||||
|
|
||||||
|
// 移除加载状态
|
||||||
|
this.messages = this.messages.filter(msg => msg.type !== 'loading')
|
||||||
|
|
||||||
|
// 显示错误消息
|
||||||
|
const errorMessage = {
|
||||||
|
id: ++this.messageId,
|
||||||
|
role: 'ai',
|
||||||
|
type: 'text',
|
||||||
|
content: '抱歉,AI服务暂时不可用,请稍后重试。',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
actions: [
|
||||||
|
{ id: 1, text: '重试', type: 'retry' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.push(errorMessage)
|
||||||
|
this.$emit('ai-response', errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发消息发送事件
|
||||||
|
this.$emit('message-sent', userMessage)
|
||||||
|
},
|
||||||
|
|
||||||
// 输入事件
|
// 输入事件
|
||||||
onInput(e) {
|
onInput(e) {
|
||||||
this.$emit('input-change', e.detail.value)
|
this.$emit('input-change', e.detail.value)
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
<!-- 悬浮按钮 -->
|
<!-- 悬浮按钮 -->
|
||||||
<view v-if="pageCount == 1 || need" class="fixed-box" :style="{ height: fixBtnShow ? '330rpx' : '120rpx' }">
|
<view v-if="pageCount == 1 || need" class="fixed-box" :style="{ height: fixBtnShow ? '330rpx' : '120rpx' }">
|
||||||
<!-- <view class="btn-item" v-if="fixBtnShow" @click="$util.redirectTo('/pages/index/index')"> -->
|
<!-- <view class="btn-item" v-if="fixBtnShow" @click="$util.redirectTo('/pages/index/index')"> -->
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<button class="btn-item" v-if="fixBtnShow" hoverClass="none" openType="contact" sessionFrom="weapp" showMessageCard="true" :style="{backgroundImage:'url('+(kefuimg?kefuimg:'')+')',backgroundSize:'100% 100%'}">
|
|
||||||
<text class="icox icox-kefu" v-if="!kefuimg"></text>
|
|
||||||
<!-- <view>首页</view> -->
|
|
||||||
</button>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- AI智能助手 -->
|
<!-- AI智能助手 -->
|
||||||
<view class="btn-item" v-if="fixBtnShow && enableAIChat" @click="openAIChat" :style="{backgroundImage:'url('+(aiAgentimg?aiAgentimg:'')+')',backgroundSize:'100% 100%'}">
|
<view class="btn-item" v-if="fixBtnShow && enableAIChat" @click="openAIChat" :style="{backgroundImage:'url('+(aiAgentimg?aiAgentimg:'')+')',backgroundSize:'100% 100%'}">
|
||||||
@@ -18,6 +12,13 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<button class="btn-item" v-if="fixBtnShow" hoverClass="none" openType="contact" sessionFrom="weapp" showMessageCard="true" :style="{backgroundImage:'url('+(kefuimg?kefuimg:'')+')',backgroundSize:'100% 100%'}">
|
||||||
|
<text class="icox icox-kefu" v-if="!kefuimg"></text>
|
||||||
|
<!-- <view>首页</view> -->
|
||||||
|
</button>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 电话 -->
|
<!-- 电话 -->
|
||||||
<view class="btn-item" v-if="fixBtnShow" @click="call()" :style="{backgroundImage:'url('+(phoneimg?phoneimg:'')+')',backgroundSize:'100% 100%'}">
|
<view class="btn-item" v-if="fixBtnShow" @click="call()" :style="{backgroundImage:'url('+(phoneimg?phoneimg:'')+')',backgroundSize:'100% 100%'}">
|
||||||
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
|
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
|
||||||
@@ -202,6 +203,9 @@
|
|||||||
|
|
||||||
.badge-text {
|
.badge-text {
|
||||||
font-size: 8rpx;
|
font-size: 8rpx;
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
font-size: 20rpx;
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
lang/zh-cn/ai/ai-chat.js
Normal file
4
lang/zh-cn/ai/ai-chat.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const lang = {
|
||||||
|
//title为每个页面的标题
|
||||||
|
title: 'AI智能客服'
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"quickapp" : {},
|
"quickapp" : {},
|
||||||
/* 小程序特有相关 */
|
/* 小程序特有相关 */
|
||||||
"mp-weixin" : {
|
"mp-weixin" : {
|
||||||
"appid" : "wx29215aa1bd97bbd6",
|
"appid" : "wxa8f94045d9c2fc10",
|
||||||
"setting" : {
|
"setting" : {
|
||||||
"urlCheck" : false,
|
"urlCheck" : false,
|
||||||
"postcss" : false,
|
"postcss" : false,
|
||||||
|
|||||||
@@ -843,13 +843,6 @@
|
|||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//******************AI客服浮动按钮演示******************
|
|
||||||
{
|
|
||||||
"path": "ai-chat-float/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "AI客服浮动按钮演示"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//******************AI客服******************
|
//******************AI客服******************
|
||||||
{
|
{
|
||||||
"path": "ai-chat/index",
|
"path": "ai-chat/index",
|
||||||
|
|||||||
@@ -105,11 +105,14 @@ export default {
|
|||||||
},
|
},
|
||||||
wrapperPageStyle() {
|
wrapperPageStyle() {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
return {
|
return `top: ${this.navBarHeight + 'px'};`
|
||||||
top: this.navBarHeight + 'px'
|
|
||||||
}
|
|
||||||
// #endif
|
// #endif
|
||||||
return {}
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
return `top: -1px;` // 微信小程序需要上移1px, 否则与系统导航栏出现1px的空隙
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
return ``
|
||||||
},
|
},
|
||||||
wrapperChatContentStyle() {
|
wrapperChatContentStyle() {
|
||||||
return {
|
return {
|
||||||
@@ -395,15 +398,16 @@ export default {
|
|||||||
onMessageSent(message) {
|
onMessageSent(message) {
|
||||||
console.log('用户发送消息:', message)
|
console.log('用户发送消息:', message)
|
||||||
|
|
||||||
// 模拟AI回复
|
// 使用AI服务获取回复
|
||||||
setTimeout(() => {
|
// AI聊天组件内部已经集成了AI服务,这里只需要监听事件
|
||||||
this.generateAIResponse(message)
|
|
||||||
}, 1000)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// AI回复消息
|
// AI回复消息
|
||||||
onAIResponse(message) {
|
onAIResponse(message) {
|
||||||
console.log('AI回复消息:', message)
|
console.log('AI回复消息:', message)
|
||||||
|
|
||||||
|
// 可以在这里处理AI回复后的逻辑
|
||||||
|
// 比如记录对话、更新状态等
|
||||||
},
|
},
|
||||||
|
|
||||||
// 生成AI回复
|
// 生成AI回复
|
||||||
|
|||||||
57
scripts/iconfontcss-generate-preview.js
Normal file
57
scripts/iconfontcss-generate-preview.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// generate-preview.js
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function generateIconfontPreview(cssPath, outputPath) {
|
||||||
|
const cssContent = fs.readFileSync(cssPath, 'utf8');
|
||||||
|
|
||||||
|
// 解析图标
|
||||||
|
const iconRegex = /\.(icon-[^:]+):before\s*{\s*content:\s*["']\\([^"']+)["']/g;
|
||||||
|
const icons = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = iconRegex.exec(cssContent)) !== null) {
|
||||||
|
icons.push({
|
||||||
|
className: match[1],
|
||||||
|
unicode: '\\' + match[2]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算引入css文件相对于 outputPath的路径
|
||||||
|
const relativeCssPath = path.relative(path.dirname(outputPath), cssPath);
|
||||||
|
|
||||||
|
// 生成 HTML
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Iconfont Preview</title>
|
||||||
|
<link rel="stylesheet" href="${relativeCssPath}">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial; padding: 20px; }
|
||||||
|
.grid { font-family: iconfont; display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
|
||||||
|
.icon { text-align: center; padding: 10px; border: 1px solid #ddd; }
|
||||||
|
.char { font-size: 24px; }
|
||||||
|
.name { font-size: 12px; margin-top: 5px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Iconfont Preview (${icons.length} icons)</h1>
|
||||||
|
<div class="grid">
|
||||||
|
${icons.map(icon => `
|
||||||
|
<div class="icon">
|
||||||
|
<div class="char ${icon.className}"></div>
|
||||||
|
<div class="name">${icon.className}</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, html);
|
||||||
|
console.log(`预览已生成: ${outputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
const cssPath = path.join(__dirname, '../common/css/iconfont.css');
|
||||||
|
const outputPath = path.join(__dirname, '../iconfont-preview.html');
|
||||||
|
generateIconfontPreview(cssPath, outputPath);
|
||||||
Reference in New Issue
Block a user