feat: 对话功能开发

This commit is contained in:
2026-04-11 17:53:31 +08:00
parent 2ab6dd1050
commit cc774b717e
2 changed files with 96 additions and 16 deletions

View File

@@ -8,6 +8,19 @@
</div>
<div v-for="msg in messages" :key="msg.id" class="bubble-wrap" :class="msg.role">
<div class="bubble">
<!-- 推理过程仅助手消息显示 -->
<div v-if="msg.parts?.reasoning?.length > 0" class="reasoning-section">
<div class="reasoning-header" @click="msg.showReasoning = !msg.showReasoning">
<el-icon :size="14"><ArrowRight v-if="!msg.showReasoning" /><ArrowDown v-else /></el-icon>
<span>思考过程</span>
</div>
<div v-show="msg.showReasoning" class="reasoning-content">
<div v-for="(r, idx) in msg.parts.reasoning" :key="idx" class="reasoning-item">
{{ r.text }}
</div>
</div>
</div>
<!-- 文本内容 -->
<pre class="bubble-text">{{ msg.text }}</pre>
</div>
</div>
@@ -33,8 +46,9 @@
import { ref, computed, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import { ChatDotRound } from '@element-plus/icons-vue';
import { createEventSource, listMessagesAction } from '@/http/api.js';
import { ChatDotRound, ArrowRight, ArrowDown } from '@element-plus/icons-vue';
import { createEventSource } from '@/http/api.js';
import axios from 'axios';
const route = useRoute();
const isSending = ref(false);
@@ -52,7 +66,11 @@ const routeSessionId = computed(() => route.query.sessionId);
async function loadHistoryMessages(sessionId) {
if (!sessionId) return;
try {
const messagesData = await listMessagesAction(sessionId);
const baseUrl = window.__opencodeBaseUrl || 'http://127.0.0.1:4096';
const response = await axios.get(`${baseUrl}/session/${sessionId}/message`);
const messagesData = response.data || [];
console.log('[loadHistoryMessages] 原始消息数据:', messagesData);
// 清空当前消息
messages.value = [];
assistantMessageIds.clear();
@@ -62,17 +80,38 @@ async function loadHistoryMessages(sessionId) {
const { info, parts } = item;
if (!info || !parts) return;
// 提取文本内容
const text = parts
.filter((part) => part.type === 'text')
.map((part) => part.text)
.join('');
// 按 type 分类存储 parts
const partsByType = {
text: [],
reasoning: [],
'step-start': [],
'step-finish': [],
// 可以在这里添加更多 type
};
if (text) {
parts.forEach((part) => {
if (partsByType.hasOwnProperty(part.type)) {
partsByType[part.type].push(part);
} else {
// 未识别的 type 统一放到 others
if (!partsByType.others) partsByType.others = [];
partsByType.others.push(part);
}
});
console.log(`[loadHistoryMessages] 消息 ${info.id} 的 parts 分类:`, partsByType);
// 提取文本内容(用于展示)
const text = partsByType.text.map((part) => part.text).join('');
if (text || info.role === 'assistant') {
messages.value.push({
id: info.id,
role: info.role,
text: text,
parts: partsByType, // 存储分类后的 parts方便后续按 type 渲染
rawParts: parts, // 保留原始 parts
showReasoning: false, // 默认折叠推理过程
});
// 记录 assistant 消息 ID
@@ -82,6 +121,7 @@ async function loadHistoryMessages(sessionId) {
}
});
console.log('[loadHistoryMessages] 处理后的消息列表:', messages.value);
scrollToBottom();
} catch (err) {
console.error('加载历史消息失败:', err);
@@ -265,6 +305,43 @@ onUnmounted(() => {
font-family: inherit;
}
.reasoning-section {
margin-bottom: 8px;
border-radius: 6px;
background: rgba(0, 0, 0, 0.03);
overflow: hidden;
}
.reasoning-header {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px;
font-size: 12px;
color: #666;
cursor: pointer;
user-select: none;
transition: background 0.2s;
}
.reasoning-header:hover {
background: rgba(0, 0, 0, 0.05);
}
.reasoning-content {
padding: 0 10px 8px;
}
.reasoning-item {
font-size: 12px;
color: #888;
line-height: 1.5;
padding: 4px 0;
border-left: 2px solid #ddd;
padding-left: 8px;
margin: 4px 0;
}
.input-area {
display: flex;
gap: 10px;