Files
electron-opencode/src/renderer/App.vue
2026-04-11 17:53:31 +08:00

218 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex flex-col h-screen w-screen overflow-hidden">
<div class="flex flex-1 overflow-hidden">
<!-- 侧边栏 -->
<aside :class="['flex flex-col bg-[#EEEFF2] border-r border-gray-200 transition-all duration-300 no-drag', appStore.collapsed ? 'w-16' : 'w-[244px]']">
<!-- 自定义菜单 -->
<nav class="flex-1 px-3 py-4 space-y-1 overflow-y-auto">
<div
v-for="(item, index) in menus"
:key="index"
:class="[
'flex items-center justify-between cursor-pointer transition-all duration-200 ease-out group relative overflow-hidden',
appStore.collapsed ? 'justify-center px-0 py-3 mx-auto w-11 rounded-xl' : 'w-[216px] h-[37px] rounded-[10px] pl-3 pr-[146px] py-0 mx-auto',
$route.path === item.index ? 'bg-[#DEE0E4] text-gray-900' : 'text-gray-500 hover:bg-gray-50 hover:text-gray-700',
]"
@click="router.push(item.index)"
>
<!-- 图标 -->
<div
:class="[
'flex items-center justify-center shrink-0 transition-transform duration-200',
appStore.collapsed ? 'w-6 h-6' : 'w-5 h-5',
$route.path === item.index ? 'scale-110' : 'group-hover:scale-105',
]"
>
<LucideIcon :name="item.icon" :size="appStore.collapsed ? 20 : 18" :class="['transition-colors duration-200']" />
</div>
<!-- 文字 -->
<span v-show="!appStore.collapsed" class="ml-3 text-sm font-medium whitespace-nowrap transition-all duration-200">
{{ item.name }}
</span>
</div>
<el-divider></el-divider>
<div class="w-54 h-8 text-sm mx-3 py-4 flex items-center" style="color: #8a9097">历史记录</div>
<div
v-for="(item, index) in historyItems"
:key="index"
class="flex items-center w-[216px] h-[37px] justify-between rounded-[10px] pl-3 pr-3 mx-auto text-gray-500 hover:bg-gray-50 hover:text-gray-700 cursor-pointer transition-all duration-200"
@click="onHistoryClick(item)"
:title="item.name"
>
<span class="text-sm font-medium whitespace-nowrap truncate flex-1">{{ item.name }}</span>
</div>
</nav>
<!-- 服务状态栏 -->
<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="text-xs text-gray-600">
{{ isServiceRunning ? '服务运行中' : '服务未启动' }}
</span>
</div>
<el-button v-if="!isServiceRunning" size="small" type="primary" @click="startService"> 启动 </el-button>
<el-button v-else size="small" type="danger" plain @click="stopService"> 停止 </el-button>
</div>
<!-- 用户区域 -->
<div class="flex items-center justify-between p-3 border-t border-gray-200">
<div class="flex items-center">
<el-avatar :size="32" />
<div v-show="!appStore.collapsed" class="ml-3">
<div class="text-sm font-medium">123</div>
<div class="text-xs text-gray-500">12321</div>
</div>
</div>
<LucideIcon v-show="!appStore.collapsed" name="bolt" color="#808080" size="18"></LucideIcon>
</div>
<!-- &lt;!&ndash; 折叠按钮 &ndash;&gt;-->
<!-- <div class="p-3 border-t border-gray-200">-->
<!-- <el-button :icon="appStore.collapsed ? Expand : Fold" circle size="small" @click="appStore.toggleSidebar" />-->
<!-- </div>-->
</aside>
<!-- 主内容区 -->
<div class="flex flex-col flex-1 overflow-hidden">
<!-- 页面内容 -->
<main class="flex-1 overflow-hidden p-6">
<div class="h-full overflow-auto">
<router-view />
</div>
</main>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import { useAppStore } from '@/stores/app';
import { House, Monitor, Expand, Fold, ChatDotRound, Search, Collection, Clock } from '@element-plus/icons-vue';
import router from '@/router';
import axios from 'axios';
const route = useRoute();
const appStore = useAppStore();
const menus = ref([
{ name: '新对话', index: '/', icon: 'plus' },
{ name: '知识空间', index: '/knowledge', icon: 'book' },
{ name: 'opencode对话', index: '/chat', icon: 'bot' },
{ name: '发现设备', index: '/bonjour', icon: 'server' },
]);
const historyItems = ref([]);
const isServiceRunning = ref(false);
let checkInterval = null;
// 查询历史会话列表
async function loadHistorySessions() {
console.log('[loadHistorySessions] 开始加载历史会话...');
try {
const baseUrl = window.__opencodeBaseUrl || 'http://127.0.0.1:4096';
console.log('[loadHistorySessions] baseUrl:', baseUrl);
const response = await axios.get(`${baseUrl}/session`);
console.log('[loadHistorySessions] 响应数据:', response.data);
const sessions = response.data || [];
console.log('[loadHistorySessions] 会话数量:', sessions.length);
// 将会话列表转换为历史记录格式
historyItems.value = sessions.map((session) => ({
id: session.id,
name: session.title || `会话 ${session.slug || session.id.slice(0, 8)}...`,
slug: session.slug,
created: session.time?.created,
updated: session.time?.updated,
}));
console.log('[loadHistorySessions] 转换后的历史记录:', historyItems.value);
} catch (err) {
console.error('[loadHistorySessions] 加载历史会话失败:', err);
}
}
// 点击历史会话,跳转到对话页面并加载该会话
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({
path: '/chat',
query: { sessionId: item.id },
});
}
// 启动服务
async function startService() {
try {
const info = await window.opencode.start();
isServiceRunning.value = info.running;
// 更新 baseUrl 供 http 层使用
if (info.url) window.__opencodeBaseUrl = info.url;
} catch (err) {
console.error('启动服务失败:', err);
}
}
// 停止服务
async function stopService() {
try {
await window.opencode.stop();
isServiceRunning.value = false;
historyItems.value = [];
} catch (err) {
console.error('停止服务失败:', err);
}
}
// 检查服务状态
async function checkServiceStatus() {
try {
const info = await window.opencode?.info();
const isRunning = info?.running || false;
// 更新服务状态
isServiceRunning.value = isRunning;
if (isRunning) {
// 服务运行中,更新 baseUrl 并加载历史记录
if (info?.url) {
window.__opencodeBaseUrl = info.url;
}
// 如果历史记录为空,则加载
if (historyItems.value.length === 0) {
loadHistorySessions();
}
} else {
// 服务未运行,清空历史记录
historyItems.value = [];
}
} catch (err) {
// 获取状态失败,视为服务断开
isServiceRunning.value = false;
historyItems.value = [];
}
}
onMounted(() => {
// 初始检查
checkServiceStatus();
// 定期检查服务状态每2秒
checkInterval = setInterval(checkServiceStatus, 2000);
});
onUnmounted(() => {
if (checkInterval) {
clearInterval(checkInterval);
}
});
</script>