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

@@ -134,7 +134,16 @@ async function loadHistorySessions() {
} }
// 点击历史会话,跳转到对话页面并加载该会话 // 点击历史会话,跳转到对话页面并加载该会话
function onHistoryClick(item) { async function onHistoryClick(item) {
try {
const baseUrl = window.__opencodeBaseUrl || 'http://127.0.0.1:4096';
const detailUrl = `${baseUrl}/session/${item.id}/message`;
const response = await axios.get(detailUrl);
console.log('[onHistoryClick] 会话详情数据:', response.data);
} catch (err) {
console.error('[onHistoryClick] 获取会话详情失败:', err);
}
// 跳转到对话页面
router.push({ router.push({
path: '/chat', path: '/chat',
query: { sessionId: item.id }, query: { sessionId: item.id },
@@ -167,9 +176,7 @@ async function stopService() {
// 检查服务状态 // 检查服务状态
async function checkServiceStatus() { async function checkServiceStatus() {
try { try {
console.log('[checkServiceStatus] 检查服务状态...');
const info = await window.opencode?.info(); const info = await window.opencode?.info();
console.log('[checkServiceStatus] 服务信息:', info);
const isRunning = info?.running || false; const isRunning = info?.running || false;
// 更新服务状态 // 更新服务状态
@@ -179,20 +186,16 @@ async function checkServiceStatus() {
// 服务运行中,更新 baseUrl 并加载历史记录 // 服务运行中,更新 baseUrl 并加载历史记录
if (info?.url) { if (info?.url) {
window.__opencodeBaseUrl = info.url; window.__opencodeBaseUrl = info.url;
console.log('[checkServiceStatus] 更新 baseUrl:', info.url);
} }
// 如果历史记录为空,则加载 // 如果历史记录为空,则加载
if (historyItems.value.length === 0) { if (historyItems.value.length === 0) {
console.log('[checkServiceStatus] 服务运行中,加载历史记录');
loadHistorySessions(); loadHistorySessions();
} }
} else { } else {
// 服务未运行,清空历史记录 // 服务未运行,清空历史记录
console.log('[checkServiceStatus] 服务未运行,清空历史记录');
historyItems.value = []; historyItems.value = [];
} }
} catch (err) { } catch (err) {
console.error('[checkServiceStatus] 获取服务状态失败:', err);
// 获取状态失败,视为服务断开 // 获取状态失败,视为服务断开
isServiceRunning.value = false; isServiceRunning.value = false;
historyItems.value = []; historyItems.value = [];

View File

@@ -8,6 +8,19 @@
</div> </div>
<div v-for="msg in messages" :key="msg.id" class="bubble-wrap" :class="msg.role"> <div v-for="msg in messages" :key="msg.id" class="bubble-wrap" :class="msg.role">
<div class="bubble"> <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> <pre class="bubble-text">{{ msg.text }}</pre>
</div> </div>
</div> </div>
@@ -33,8 +46,9 @@
import { ref, computed, onUnmounted, nextTick, watch } from 'vue'; import { ref, computed, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { ChatDotRound } from '@element-plus/icons-vue'; import { ChatDotRound, ArrowRight, ArrowDown } from '@element-plus/icons-vue';
import { createEventSource, listMessagesAction } from '@/http/api.js'; import { createEventSource } from '@/http/api.js';
import axios from 'axios';
const route = useRoute(); const route = useRoute();
const isSending = ref(false); const isSending = ref(false);
@@ -52,7 +66,11 @@ const routeSessionId = computed(() => route.query.sessionId);
async function loadHistoryMessages(sessionId) { async function loadHistoryMessages(sessionId) {
if (!sessionId) return; if (!sessionId) return;
try { 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 = []; messages.value = [];
assistantMessageIds.clear(); assistantMessageIds.clear();
@@ -62,17 +80,38 @@ async function loadHistoryMessages(sessionId) {
const { info, parts } = item; const { info, parts } = item;
if (!info || !parts) return; if (!info || !parts) return;
// 提取文本内容 // 按 type 分类存储 parts
const text = parts const partsByType = {
.filter((part) => part.type === 'text') text: [],
.map((part) => part.text) reasoning: [],
.join(''); '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({ messages.value.push({
id: info.id, id: info.id,
role: info.role, role: info.role,
text: text, text: text,
parts: partsByType, // 存储分类后的 parts方便后续按 type 渲染
rawParts: parts, // 保留原始 parts
showReasoning: false, // 默认折叠推理过程
}); });
// 记录 assistant 消息 ID // 记录 assistant 消息 ID
@@ -82,6 +121,7 @@ async function loadHistoryMessages(sessionId) {
} }
}); });
console.log('[loadHistoryMessages] 处理后的消息列表:', messages.value);
scrollToBottom(); scrollToBottom();
} catch (err) { } catch (err) {
console.error('加载历史消息失败:', err); console.error('加载历史消息失败:', err);
@@ -265,6 +305,43 @@ onUnmounted(() => {
font-family: inherit; 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 { .input-area {
display: flex; display: flex;
gap: 10px; gap: 10px;