Merge branch 'fix-error' of https://gitea.cirry.cn/cirry/electron-opencode into fix-error
This commit is contained in:
@@ -47,12 +47,28 @@
|
||||
<!-- 服务状态栏 -->
|
||||
<div class="flex items-center justify-between px-3 py-2 border-t border-gray-200">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="isServiceRunning ? 'bg-green-500' : 'bg-gray-400'" />
|
||||
<span
|
||||
class="w-2 h-2 rounded-full"
|
||||
:class="{
|
||||
'bg-green-500': appStore.serviceStatus === appStore.SERVICE_STATUS.RUNNING,
|
||||
'bg-yellow-500': appStore.serviceStatus === appStore.SERVICE_STATUS.CONNECTING,
|
||||
'bg-red-500': appStore.serviceStatus === appStore.SERVICE_STATUS.FAILED,
|
||||
'bg-gray-400': appStore.serviceStatus === appStore.SERVICE_STATUS.IDLE,
|
||||
}"
|
||||
/>
|
||||
<span class="text-xs text-gray-600">
|
||||
{{ isServiceRunning ? '服务运行中' : '服务未启动' }}
|
||||
{{ statusLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button v-if="!isServiceRunning" size="small" type="primary" @click="startService"> 启动 </el-button>
|
||||
<el-button
|
||||
v-if="appStore.serviceStatus !== appStore.SERVICE_STATUS.RUNNING"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="appStore.serviceStatus === appStore.SERVICE_STATUS.CONNECTING"
|
||||
@click="startService"
|
||||
>
|
||||
{{ appStore.serviceStatus === appStore.SERVICE_STATUS.FAILED ? '重新启动' : '启动' }}
|
||||
</el-button>
|
||||
<el-button v-else size="small" type="danger" plain @click="stopService"> 停止 </el-button>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +104,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { useHistoryStore } from '@/stores/history';
|
||||
@@ -100,6 +116,20 @@ const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const historyStore = useHistoryStore();
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
switch (appStore.serviceStatus) {
|
||||
case appStore.SERVICE_STATUS.CONNECTING:
|
||||
return '连接中';
|
||||
case appStore.SERVICE_STATUS.RUNNING:
|
||||
return '服务运行中';
|
||||
case appStore.SERVICE_STATUS.FAILED:
|
||||
return '连接失败';
|
||||
case appStore.SERVICE_STATUS.IDLE:
|
||||
default:
|
||||
return '服务未启动';
|
||||
}
|
||||
});
|
||||
|
||||
const menus = ref([
|
||||
{ name: '新对话', index: '/', icon: 'plus' },
|
||||
{ name: '知识空间', index: '/knowledge', icon: 'book' },
|
||||
@@ -117,6 +147,9 @@ function handleMenuClick(item) {
|
||||
|
||||
const isServiceRunning = ref(false);
|
||||
let checkInterval = null;
|
||||
let retryCount = 0;
|
||||
const MAX_RETRY = 3;
|
||||
const RETRY_DELAY = 3000;
|
||||
|
||||
// 查询历史会话列表
|
||||
async function loadHistorySessions() {
|
||||
@@ -142,17 +175,42 @@ async function onHistoryClick(item) {
|
||||
|
||||
// 启动服务
|
||||
async function startService() {
|
||||
if (appStore.serviceStatus === appStore.SERVICE_STATUS.CONNECTING) return;
|
||||
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.CONNECTING;
|
||||
retryCount = 0;
|
||||
doStartService();
|
||||
}
|
||||
|
||||
async function doStartService() {
|
||||
try {
|
||||
const info = await window.opencode.start();
|
||||
isServiceRunning.value = info.running;
|
||||
if (info.running) {
|
||||
isServiceRunning.value = true;
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.RUNNING;
|
||||
// 更新 baseUrl 供 http 层使用
|
||||
if (info.url) window.__opencodeBaseUrl = info.url;
|
||||
// 服务启动成功后,初始化 SSE 连接
|
||||
if (info.running) {
|
||||
appStore.initSSE();
|
||||
retryCount = 0;
|
||||
} else {
|
||||
handleRetry();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('启动服务失败:', err);
|
||||
handleRetry();
|
||||
}
|
||||
}
|
||||
|
||||
function handleRetry() {
|
||||
if (retryCount < MAX_RETRY) {
|
||||
retryCount++;
|
||||
console.log(`[App.vue] 启动失败,第 ${retryCount} 次重连中...`);
|
||||
setTimeout(doStartService, RETRY_DELAY);
|
||||
} else {
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.FAILED;
|
||||
isServiceRunning.value = false;
|
||||
console.error(`[App.vue] 重连 ${MAX_RETRY} 次后依然失败,停止重连。`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +219,9 @@ async function stopService() {
|
||||
try {
|
||||
await window.opencode.stop();
|
||||
isServiceRunning.value = false;
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.IDLE;
|
||||
historyStore.clearHistory();
|
||||
appStore.closeSSE();
|
||||
} catch (err) {
|
||||
console.error('停止服务失败:', err);
|
||||
}
|
||||
@@ -169,14 +229,17 @@ async function stopService() {
|
||||
|
||||
// 检查服务状态
|
||||
async function checkServiceStatus() {
|
||||
// 如果正在重连中,则不进行状态更新,避免干扰
|
||||
if (appStore.serviceStatus === appStore.SERVICE_STATUS.CONNECTING) return;
|
||||
|
||||
try {
|
||||
const info = await window.opencode?.info();
|
||||
const isRunning = info?.running || false;
|
||||
|
||||
// 更新服务状态
|
||||
isServiceRunning.value = isRunning;
|
||||
|
||||
if (isRunning) {
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.RUNNING;
|
||||
// 服务运行中,更新 baseUrl 并加载历史记录
|
||||
if (info?.url) {
|
||||
window.__opencodeBaseUrl = info.url;
|
||||
@@ -185,13 +248,24 @@ async function checkServiceStatus() {
|
||||
if (historyStore.historyItems.length === 0) {
|
||||
loadHistorySessions();
|
||||
}
|
||||
// 确保 SSE 连接已建立
|
||||
if (!appStore.sseConnected) {
|
||||
appStore.initSSE();
|
||||
}
|
||||
} else {
|
||||
// 只有当前是运行中才变更为未启动,防止覆盖 FAILED 状态
|
||||
if (appStore.serviceStatus === appStore.SERVICE_STATUS.RUNNING) {
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.IDLE;
|
||||
}
|
||||
// 服务未运行,清空历史记录
|
||||
historyStore.clearHistory();
|
||||
}
|
||||
} catch (err) {
|
||||
// 获取状态失败,视为服务断开
|
||||
isServiceRunning.value = false;
|
||||
if (appStore.serviceStatus === appStore.SERVICE_STATUS.RUNNING) {
|
||||
appStore.serviceStatus = appStore.SERVICE_STATUS.IDLE;
|
||||
}
|
||||
historyStore.clearHistory();
|
||||
}
|
||||
}
|
||||
@@ -201,6 +275,16 @@ onMounted(() => {
|
||||
checkServiceStatus();
|
||||
// 定期检查服务状态(每2秒)
|
||||
checkInterval = setInterval(checkServiceStatus, 2000);
|
||||
|
||||
// 监听启动服务的指令
|
||||
watch(
|
||||
() => appStore.startServiceFlag,
|
||||
(newVal) => {
|
||||
if (newVal > 0) {
|
||||
startService();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -3,8 +3,22 @@ import { ref, computed } from 'vue';
|
||||
import { sseManager } from '@/http/sse.js';
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
// 服务运行状态常量
|
||||
const SERVICE_STATUS = {
|
||||
IDLE: 'idle', // 未启动
|
||||
CONNECTING: 'connecting', // 连接中
|
||||
RUNNING: 'running', // 服务运行中
|
||||
FAILED: 'failed', // 连接失败
|
||||
};
|
||||
|
||||
const title = ref('智聚超脑');
|
||||
const collapsed = ref(false);
|
||||
const serviceStatus = ref(SERVICE_STATUS.IDLE);
|
||||
const startServiceFlag = ref(0); // 用于触发外部启动服务的标记
|
||||
|
||||
function triggerStartService() {
|
||||
startServiceFlag.value++;
|
||||
}
|
||||
|
||||
// SSE 相关状态
|
||||
const sseConnected = ref(false);
|
||||
@@ -134,5 +148,10 @@ export const useAppStore = defineStore('app', () => {
|
||||
isAssistantMessage,
|
||||
addAssistantMessageId,
|
||||
clearAssistantMessageIds,
|
||||
// 服务运行状态
|
||||
serviceStatus,
|
||||
SERVICE_STATUS,
|
||||
startServiceFlag,
|
||||
triggerStartService,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ChatDotRound, ArrowRight, ArrowDown } from '@element-plus/icons-vue';
|
||||
import { useAppStore } from '@/stores/app.js';
|
||||
@@ -52,6 +52,7 @@ import { sseManager } from '@/http/sse.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const isSending = ref(false);
|
||||
const inputText = ref('');
|
||||
@@ -69,9 +70,17 @@ async function loadHistoryMessages(sessionId) {
|
||||
if (!sessionId) return;
|
||||
try {
|
||||
const baseUrl = window.__opencodeBaseUrl || 'http://127.0.0.1:4096';
|
||||
const response = await axios.get(`${baseUrl}/session/${sessionId}/message`);
|
||||
const requestUrl = `${baseUrl}/session/${sessionId}/message`;
|
||||
console.log('[loadHistoryMessages] ========================================');
|
||||
console.log('[loadHistoryMessages] 开始获取消息,sessionId:', sessionId);
|
||||
console.log('[loadHistoryMessages] 请求URL:', requestUrl);
|
||||
|
||||
const response = await axios.get(requestUrl);
|
||||
const messagesData = response.data || [];
|
||||
console.log('[loadHistoryMessages] 原始消息数据:', messagesData);
|
||||
|
||||
console.log('[loadHistoryMessages] 获取成功!消息数量:', messagesData.length);
|
||||
console.log('[loadHistoryMessages] 原始消息数据 (JSON):', JSON.stringify(messagesData, null, 2));
|
||||
console.log('[loadHistoryMessages] ========================================');
|
||||
|
||||
// 清空当前消息
|
||||
messages.value = [];
|
||||
@@ -136,10 +145,25 @@ async function loadHistoryMessages(sessionId) {
|
||||
// 监听路由参数变化,加载对应会话
|
||||
watch(
|
||||
routeSessionId,
|
||||
(newSessionId) => {
|
||||
async (newSessionId) => {
|
||||
if (newSessionId) {
|
||||
currentSessionId.value = newSessionId;
|
||||
loadHistoryMessages(newSessionId);
|
||||
// 等待历史消息加载完毕,避免在 send 时 messages 被清空
|
||||
await loadHistoryMessages(newSessionId);
|
||||
|
||||
// 处理从首页带过来的初始消息
|
||||
const text = route.query.text;
|
||||
if (text) {
|
||||
inputText.value = text;
|
||||
// 清除 query 中的 text,防止刷新页面时重复发送
|
||||
const query = { ...route.query };
|
||||
delete query.text;
|
||||
router.replace({ query });
|
||||
|
||||
// 触发发送逻辑
|
||||
send();
|
||||
}
|
||||
|
||||
// 确保 SSE 连接已建立
|
||||
if (!appStore.sseConnected) {
|
||||
appStore.initSSE();
|
||||
@@ -241,8 +265,14 @@ async function send() {
|
||||
|
||||
try {
|
||||
await window.opencode.promptAsync(currentSessionId.value, text);
|
||||
// 发送成功后等待 SSE 事件流推送 AI 响应,isSending 由 message.completed 事件重置
|
||||
// 发送成功后等待 SSE 事件流推送 AI 响应,isSending 由 session.idle 事件重置
|
||||
} catch (err) {
|
||||
console.error('发送指令失败:', err);
|
||||
// 如果是服务未运行,尝试启动服务
|
||||
if (appStore.serviceStatus !== appStore.SERVICE_STATUS.RUNNING) {
|
||||
ElMessage.info('服务未运行,正在尝试启动...');
|
||||
appStore.triggerStartService();
|
||||
}
|
||||
ElMessage.error(`发送失败: ${err.message}`);
|
||||
isSending.value = false;
|
||||
}
|
||||
|
||||
@@ -72,10 +72,12 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { useHistoryStore } from '@/stores/history';
|
||||
import { Document, Plus, Promotion } from '@element-plus/icons-vue';
|
||||
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const historyStore = useHistoryStore();
|
||||
const inputText = ref('');
|
||||
const isCreating = ref(false);
|
||||
@@ -94,10 +96,13 @@ async function handleSend() {
|
||||
// 清空输入框
|
||||
inputText.value = '';
|
||||
|
||||
// 跳转到对话页面
|
||||
// 跳转到对话页面,并将消息文本带入 query
|
||||
router.push({
|
||||
path: '/chat',
|
||||
query: { sessionId: session.id },
|
||||
query: {
|
||||
sessionId: session.id,
|
||||
text: text,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('创建会话失败:', err);
|
||||
|
||||
Reference in New Issue
Block a user