Merge branch 'fix-error' of https://gitea.cirry.cn/cirry/electron-opencode into fix-error

This commit is contained in:
houakang
2026-04-12 12:59:09 +08:00
4 changed files with 155 additions and 17 deletions

View File

@@ -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(() => {

View File

@@ -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,
};
});

View File

@@ -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;
}

View File

@@ -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);