|
@@ -1,37 +1,16 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, nextTick, onMounted, reactive } from 'vue'
|
|
|
+import { ref, nextTick, onMounted, computed, onUnmounted, reactive, watch } from 'vue'
|
|
|
import { Local } from '/@/utils/storage'
|
|
|
import { User, ChatDotRound, Delete, Edit, Check, Close } from '@element-plus/icons-vue'
|
|
|
import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
|
|
|
import EChartsPlugin from '/@/components/markdown/plugins/markdown-it-echarts'
|
|
|
import Markdown from '/@/components/markdown/Markdown.vue'
|
|
|
+import assist from '/@/api/assist'
|
|
|
+import { ChatResponse, Message } from '/@/api/assist/type'
|
|
|
|
|
|
const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin()]
|
|
|
|
|
|
-const getUserInfos = ref<{
|
|
|
- avatar: string
|
|
|
- userName: string
|
|
|
-}>(Local.get('userInfo') || {})
|
|
|
-
|
|
|
-// 消息类型定义
|
|
|
-interface Message {
|
|
|
- id: number
|
|
|
- type: 'user' | 'ai'
|
|
|
- content: string
|
|
|
- timestamp: Date
|
|
|
-}
|
|
|
-
|
|
|
-// 会话列表
|
|
|
-const conversations = ref<{ id: number; title: string; active: boolean }[]>([])
|
|
|
-
|
|
|
-for (let i = 0; i < 100; i++) {
|
|
|
- conversations.value.push({ id: i, title: `Summary ${i}`, active: false })
|
|
|
-}
|
|
|
-
|
|
|
-// 编辑状态管理
|
|
|
-const editingConversationId = ref<number | null>(null)
|
|
|
-const editingTitle = ref('')
|
|
|
-
|
|
|
+//聊天管理接口
|
|
|
// 消息列表
|
|
|
const messages = ref<Message[]>([])
|
|
|
|
|
@@ -39,7 +18,7 @@ const messages = ref<Message[]>([])
|
|
|
const inputMessage = ref('')
|
|
|
const messagesContainer = ref<HTMLElement>()
|
|
|
|
|
|
-// 工具选择数据
|
|
|
+// 工具选择
|
|
|
const toolOptions = ref([
|
|
|
{
|
|
|
value: 'code',
|
|
@@ -89,7 +68,7 @@ const toolOptions = ref([
|
|
|
},
|
|
|
])
|
|
|
|
|
|
-// 模型选择数据
|
|
|
+// 模型选择
|
|
|
const modelOptions = ref([
|
|
|
{ label: 'Claude Sonnet 4', value: 'claude-sonnet-4' },
|
|
|
{ label: 'GPT-4', value: 'gpt-4' },
|
|
@@ -101,81 +80,127 @@ const modelOptions = ref([
|
|
|
const selectedTool = ref([])
|
|
|
const selectedModel = ref('claude-sonnet-4')
|
|
|
|
|
|
-// 对话状态
|
|
|
-const isConversationActive = ref(false)
|
|
|
+const chatInstance = ref<(() => void) | undefined>(undefined)
|
|
|
+onUnmounted(() => chatInstance.value?.())
|
|
|
+// 是否正在对话
|
|
|
+const isConversationActive = computed(() => chatInstance.value !== undefined)
|
|
|
|
|
|
// 发送消息
|
|
|
const sendMessage = () => {
|
|
|
if (!inputMessage.value.trim()) return
|
|
|
|
|
|
- // 设置对话状态为活跃
|
|
|
- isConversationActive.value = true
|
|
|
-
|
|
|
- // 添加用户消息
|
|
|
messages.value.push({
|
|
|
id: Date.now(),
|
|
|
- type: 'user',
|
|
|
+ role: 'user',
|
|
|
content: inputMessage.value,
|
|
|
- timestamp: new Date(),
|
|
|
+ timestamp: Date.now(),
|
|
|
})
|
|
|
|
|
|
- //清空用户聊天框
|
|
|
- const userMessage = inputMessage.value
|
|
|
- inputMessage.value = ''
|
|
|
-
|
|
|
- // 滚动到底部
|
|
|
- scrollToBottom()
|
|
|
-
|
|
|
- const ai = reactive<Message>({
|
|
|
- id: Date.now()+1,
|
|
|
- type: 'ai',
|
|
|
+ const rtn = reactive<Message>({
|
|
|
+ id: Date.now(),
|
|
|
+ role: 'assistant',
|
|
|
content: '',
|
|
|
- timestamp: new Date()
|
|
|
+ timestamp: Date.now(),
|
|
|
})
|
|
|
-
|
|
|
- // 添加一个回复
|
|
|
- messages.value.push(ai)
|
|
|
-
|
|
|
- const delay = async (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
|
|
|
-
|
|
|
- const action = async () => {
|
|
|
- const message = `我收到了您的消息:"${userMessage}"。这是一个示例回复,包含markdown格式的内容。
|
|
|
-
|
|
|
-## 回复内容
|
|
|
-
|
|
|
-- 列表项 1
|
|
|
-- 列表项 2
|
|
|
-- 列表项 3
|
|
|
-
|
|
|
-\`\`\`javascript
|
|
|
-console.log('这是代码示例');
|
|
|
-\`\`\``
|
|
|
- for (let i = 0; i < message.length; i++) {
|
|
|
- ai.content += message[i]
|
|
|
- await delay(50)
|
|
|
+ const fe = watch(()=> rtn.content,()=>scrollToBottom())
|
|
|
+
|
|
|
+
|
|
|
+ let toolcall: { name: string; param?: string; value?: string } | undefined = undefined
|
|
|
+ chatInstance.value = assist.chat({
|
|
|
+ chatRequest: {
|
|
|
+ message: messages.value,
|
|
|
+ },
|
|
|
+ onReceive: (resp: ChatResponse) => {
|
|
|
+ switch (resp.type) {
|
|
|
+ case 'message':
|
|
|
+ rtn.content += resp.message
|
|
|
+ break
|
|
|
+ case 'toolcall':
|
|
|
+ toolcall = {
|
|
|
+ name: resp.request.name,
|
|
|
+ param: resp.request.data,
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'toolres':
|
|
|
+ if (toolcall !== undefined) {
|
|
|
+ toolcall.value = resp.response.data
|
|
|
+ }
|
|
|
+
|
|
|
+ rtn.content += `
|
|
|
+> ### Tool Call
|
|
|
+>
|
|
|
+> **Params**: \`${toolcall?.name}\`
|
|
|
+>
|
|
|
+> **Value**: \`${toolcall?.value}\`
|
|
|
+
|
|
|
+`
|
|
|
+ toolcall = undefined
|
|
|
+ break
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onComplete: ()=> {
|
|
|
+ chatInstance.value = undefined
|
|
|
+ scrollToBottom()
|
|
|
+ fe()
|
|
|
}
|
|
|
- }
|
|
|
- action().finally(()=> {
|
|
|
- isConversationActive.value = false
|
|
|
- scrollToBottom()
|
|
|
})
|
|
|
+ messages.value.push(rtn)
|
|
|
|
|
|
- // setTimeout(() => {
|
|
|
+ //
|
|
|
+ // // 添加用户消息
|
|
|
// messages.value.push({
|
|
|
- // id: Date.now() + 1,
|
|
|
- // type: 'ai',
|
|
|
- // content: ,
|
|
|
+ // id: Date.now(),
|
|
|
+ // type: 'user',
|
|
|
+ // content: inputMessage.value,
|
|
|
// timestamp: new Date(),
|
|
|
// })
|
|
|
- // isConversationActive.value = false
|
|
|
+ //
|
|
|
+ // //清空用户聊天框
|
|
|
+ // const userMessage = inputMessage.value
|
|
|
+ // inputMessage.value = ''
|
|
|
+ //
|
|
|
+ // // 滚动到底部
|
|
|
// scrollToBottom()
|
|
|
- // }, 1000)
|
|
|
+ //
|
|
|
+ // const ai = reactive<Message>({
|
|
|
+ // id: Date.now()+1,
|
|
|
+ // type: 'ai',
|
|
|
+ // content: '',
|
|
|
+ // timestamp: new Date()
|
|
|
+ // })
|
|
|
+ //
|
|
|
+ // // 添加一个回复
|
|
|
+ // messages.value.push(ai)
|
|
|
+ //
|
|
|
+ // const delay = async (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
|
|
|
+ //
|
|
|
+ // const action = async () => {
|
|
|
+ // const message = `我收到了您的消息:"${userMessage}"。这是一个示例回复,包含markdown格式的内容。
|
|
|
+ //
|
|
|
+ // ## 回复内容
|
|
|
+ //
|
|
|
+ // - 列表项 1
|
|
|
+ // - 列表项 2
|
|
|
+ // - 列表项 3
|
|
|
+ //
|
|
|
+ // \`\`\`javascript
|
|
|
+ // console.log('这是代码示例');
|
|
|
+ // \`\`\``
|
|
|
+ // for (let i = 0; i < message.length; i++) {
|
|
|
+ // ai.content += message[i]
|
|
|
+ // await delay(50)
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // action().finally(()=> {
|
|
|
+ // isConversationActive.value = false
|
|
|
+ // scrollToBottom()
|
|
|
+ // })
|
|
|
}
|
|
|
|
|
|
// 终止对话
|
|
|
const stopConversation = () => {
|
|
|
- isConversationActive.value = false
|
|
|
- // 这里可以添加实际的终止逻辑
|
|
|
+ chatInstance.value?.()
|
|
|
+ chatInstance.value = undefined
|
|
|
}
|
|
|
|
|
|
// 滚动到底部
|
|
@@ -186,20 +211,27 @@ const scrollToBottom = () => {
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+// 会话管理模块
|
|
|
+// 所有会话
|
|
|
+const conversations = ref<{ id: number; title: string }[]>([])
|
|
|
+// 当前活跃会话
|
|
|
+const activeConversationId = ref<number | undefined>(undefined)
|
|
|
+
|
|
|
// 选择会话
|
|
|
const selectConversation = (id: number) => {
|
|
|
- conversations.value.forEach((conv) => {
|
|
|
- conv.active = conv.id === id
|
|
|
- })
|
|
|
+ activeConversationId.value = id
|
|
|
}
|
|
|
|
|
|
-// 删除对话
|
|
|
+// 删除会话
|
|
|
const deleteConversation = (id: number) => {
|
|
|
- console.log('删除对话:', id)
|
|
|
// TODO: 实现删除对话逻辑
|
|
|
}
|
|
|
|
|
|
-// 编辑摘要
|
|
|
+// 编辑会话状态管理
|
|
|
+const editingConversationId = ref<number | undefined>(undefined)
|
|
|
+const editingTitle = ref('')
|
|
|
+// 编辑会话摘要
|
|
|
const editSummary = (id: number) => {
|
|
|
const conversation = conversations.value.find((conv) => conv.id === id)
|
|
|
if (conversation) {
|
|
@@ -216,28 +248,33 @@ const editSummary = (id: number) => {
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
// 确认编辑
|
|
|
const confirmEdit = (id: number) => {
|
|
|
const conversation = conversations.value.find((conv) => conv.id === id)
|
|
|
if (conversation && editingTitle.value.trim()) {
|
|
|
conversation.title = editingTitle.value.trim()
|
|
|
// 清除编辑状态
|
|
|
- editingConversationId.value = null
|
|
|
+ editingConversationId.value = undefined
|
|
|
editingTitle.value = ''
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 取消编辑
|
|
|
-const cancelEdit = (id: number) => {
|
|
|
+const cancelEdit = () => {
|
|
|
// 清除编辑状态
|
|
|
- editingConversationId.value = null
|
|
|
+ editingConversationId.value = undefined
|
|
|
editingTitle.value = ''
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
scrollToBottom()
|
|
|
})
|
|
|
+
|
|
|
+//杂项
|
|
|
+const getUserInfos = ref<{
|
|
|
+ avatar: string
|
|
|
+ userName: string
|
|
|
+}>(Local.get('userInfo') || {})
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
@@ -252,7 +289,7 @@ onMounted(() => {
|
|
|
v-for="conv in conversations"
|
|
|
:key="conv.id"
|
|
|
@click="editingConversationId !== conv.id ? selectConversation(conv.id) : () => {}"
|
|
|
- :class="['conversation-item', { active: conv.active, editing: editingConversationId === conv.id }]"
|
|
|
+ :class="['conversation-item', { active: activeConversationId === conv.id, editing: editingConversationId === conv.id }]"
|
|
|
>
|
|
|
<!-- 非编辑状态 -->
|
|
|
<div v-if="editingConversationId !== conv.id" class="conversation-content">
|
|
@@ -261,13 +298,7 @@ onMounted(() => {
|
|
|
|
|
|
<!-- 编辑状态 -->
|
|
|
<div v-else class="conversation-edit-content">
|
|
|
- <el-input
|
|
|
- v-model="editingTitle"
|
|
|
- size="small"
|
|
|
- @keydown.enter="confirmEdit(conv.id)"
|
|
|
- @keydown.esc="cancelEdit(conv.id)"
|
|
|
- class="edit-input"
|
|
|
- />
|
|
|
+ <el-input v-model="editingTitle" size="small" @keydown.enter="confirmEdit(conv.id)" @keydown.esc="cancelEdit" class="edit-input" />
|
|
|
</div>
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
@@ -320,9 +351,9 @@ onMounted(() => {
|
|
|
<!-- 消息展示区域 -->
|
|
|
<div class="messages-container" ref="messagesContainer">
|
|
|
<div v-if="messages.length !== 0">
|
|
|
- <div v-for="message in messages" :key="message.id" :class="['message-wrapper', message.type]">
|
|
|
+ <div v-for="message in messages" :key="message.id" :class="['message-wrapper', message.role]">
|
|
|
<!-- AI消息 -->
|
|
|
- <div v-if="message.type === 'ai'" class="ai-message-container">
|
|
|
+ <div v-if="message.role === 'assistant'" class="ai-message-container">
|
|
|
<el-avatar class="message-avatar" :icon="ChatDotRound" />
|
|
|
<div class="message-bubble ai-bubble">
|
|
|
<Markdown :content="message.content" :plugins="plugins" class="markdown-content" />
|
|
@@ -442,7 +473,7 @@ onMounted(() => {
|
|
|
>
|
|
|
发送
|
|
|
</el-button>
|
|
|
- <el-button v-else type="danger" size="small" @click="stopConversation"> 终止 </el-button>
|
|
|
+ <el-button v-else type="danger" size="small" @click="stopConversation"> 终止</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -695,7 +726,12 @@ onMounted(() => {
|
|
|
/* Markdown 内容样式优化 - 接入Element Plus颜色系统 */
|
|
|
:deep(.markdown-content) {
|
|
|
/* 标题样式 */
|
|
|
- h1, h2, h3, h4, h5, h6 {
|
|
|
+ h1,
|
|
|
+ h2,
|
|
|
+ h3,
|
|
|
+ h4,
|
|
|
+ h5,
|
|
|
+ h6 {
|
|
|
margin-top: 24px;
|
|
|
margin-bottom: 16px;
|
|
|
font-weight: 600;
|
|
@@ -759,7 +795,8 @@ onMounted(() => {
|
|
|
}
|
|
|
|
|
|
/* 列表样式 */
|
|
|
- ul, ol {
|
|
|
+ ul,
|
|
|
+ ol {
|
|
|
margin: 16px 0;
|
|
|
padding-left: 32px;
|
|
|
color: var(--el-text-color-regular);
|
|
@@ -778,7 +815,8 @@ onMounted(() => {
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
}
|
|
|
|
|
|
- th, td {
|
|
|
+ th,
|
|
|
+ td {
|
|
|
border: 1px solid var(--el-border-color-light);
|
|
|
padding: 8px 12px;
|
|
|
text-align: left;
|