feat: 对话功能开发
This commit is contained in:
@@ -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 = [];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user