Files
electron-opencode/src/renderer/views/home/HomeView.vue
2026-04-12 17:06:32 +08:00

379 lines
8.5 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="home-container">
<!-- 中间内容区标题副标题卡片 -->
<div class="center-content">
<!-- 第一行标题 -->
<div class="title-section">
<span class="title-highlight"></span>
<span class="title-normal mr-4">鉴万物</span>
<span class="title-highlight"></span>
<span class="title-normal">知明理 </span>
</div>
<!-- 第二行副标题 -->
<div class="subtitle-section">本地运行自主规划安全可控的AI工作搭子</div>
<!-- 第三行卡片 -->
<div class="card-section flex gap-8 items-center">
<div class="feature-card">
<div class="card-icon">
<img src="@/assets/icons/document-organization.svg" width="42" height="42" />
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
<div class="feature-card">
<div class="card-icon">
<img src="@/assets/icons/official-document.svg" width="42" height="42" />
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
<div class="feature-card">
<div class="card-icon">
<img src="@/assets/icons/pdf.svg" width="42" height="42" />
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
</div>
</div>
<!-- 底部输入框 -->
<div class="input-section">
<div class="input-wrapper">
<textarea
v-model="inputText"
class="input-textarea"
placeholder="输入 / 调用技能,输入 @ 调用知识库"
:disabled="isCreating"
@keydown="handleKeydown"
@input="autoResize"
ref="textareaRef"
></textarea>
<div class="input-toolbar">
<div class="toolbar-left">
<button class="dir-btn">
<LucideIcon name="folder-input" size="16"></LucideIcon>
<span>选择工作目录</span>
</button>
<el-tooltip content="添加文件或者文件夹作为上下文" placement="top" :show-arrow="false">
<button class="toolbar-btn icon-btn">
<LucideIcon name="paperclip" size="16"></LucideIcon>
</button>
</el-tooltip>
<el-tooltip content="使用 / 调用技能" placement="top" :show-arrow="false">
<button class="toolbar-btn icon-btn">/</button>
</el-tooltip>
<el-tooltip content="使用 @ 调用知识库" placement="top" :show-arrow="false">
<button class="toolbar-btn icon-btn">@</button>
</el-tooltip>
</div>
<div class="toolbar-right">
<button class="send-btn" :disabled="!inputText.trim() || isCreating" @click="handleSend">
<LucideIcon name="arrow-up"></LucideIcon>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<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, FolderOpened, Paperclip } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import LucideIcon from '@/components/base/LucideIcon.vue';
const router = useRouter();
const appStore = useAppStore();
const historyStore = useHistoryStore();
const inputText = ref('');
const isCreating = ref(false);
const textareaRef = ref(null);
function autoResize() {
const el = textareaRef.value;
if (!el) return;
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 323) + 'px';
}
// 处理发送消息
async function handleSend() {
// 检查 opencode 服务是否已启动
if (appStore.serviceStatus !== appStore.SERVICE_STATUS.RUNNING) {
ElMessage.warning('暂时没有运行的智能体');
return;
}
const text = inputText.value.trim();
if (!text || isCreating.value) return;
isCreating.value = true;
try {
// 创建会话title 使用用户输入的文本
const session = await historyStore.createSession(text);
console.log('创建会话成功:', session);
// 清空输入框
inputText.value = '';
// 跳转到对话页面,并将消息文本带入 query
router.push({
name: 'Chat',
params: { id: session.id },
query: { text: text },
});
} catch (err) {
console.error('创建会话失败:', err);
} finally {
isCreating.value = false;
}
}
// 处理键盘事件
function handleKeydown(e) {
// Enter 发送Shift+Enter 换行
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}
</script>
<style scoped>
.home-container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
background: #ffffff;
position: relative;
}
/* 中间内容区 */
.center-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
width: 100%;
}
/* 第一行:标题 */
.title-section {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
}
.title-highlight {
font-weight: 900;
font-size: 42px;
line-height: 48px;
letter-spacing: -0.39px;
text-align: center;
color: #409eff;
}
.title-normal {
font-weight: 900;
font-size: 42px;
line-height: 48px;
letter-spacing: -0.39px;
text-align: center;
color: #303133;
}
/* 第二行:副标题 */
.subtitle-section {
font-weight: 400;
font-size: 14px;
line-height: 22.75px;
letter-spacing: -0.15px;
text-align: center;
color: #606266;
}
/* 第三行:卡片 */
.card-section {
margin: 16px 0;
}
.feature-card {
width: 220px;
height: 158px;
border: 1px solid #e4e7ed;
border-radius: 8px;
display: flex;
flex-direction: column;
padding: 16px;
gap: 8px;
background: #ffffff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.card-icon {
width: 42px;
height: 42px;
margin-top: 9px;
margin-bottom: 4px;
}
.card-icon img {
transition: transform 0.3s ease;
}
.card-icon img:hover {
transform: rotate(-15deg);
}
.card-title {
font-weight: 600;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.55px;
color: #1a1a1a;
}
.card-desc {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #8a9097;
}
/* 底部:输入框 */
.input-section {
margin-top: auto;
padding-top: 24px;
padding-bottom: 32px;
}
.input-wrapper {
width: 760px;
border-radius: 12px;
border: 1px solid #dee0e4;
display: flex;
flex-direction: column;
background: #ffffff;
box-shadow: 0px 1px 20px 0px #00000008;
}
.input-textarea {
width: 758px;
min-height: 60px;
max-height: 323px;
padding: 16px;
border: none;
outline: none;
resize: none;
overflow-y: auto;
font-size: 14px;
line-height: 20px;
color: #303133;
background: transparent;
font-family: inherit;
}
.input-textarea::placeholder {
color: #c0c4cc;
}
.input-toolbar {
height: 54px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #dcdfe6;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
color: #606266;
}
.toolbar-btn:hover {
border-color: #409eff;
color: #409eff;
}
.dir-btn {
height: 28px;
gap: 4px;
padding: 0 8px;
background: #f5f6f7;
border-radius: 28px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #606266;
}
.dir-btn:hover {
background: #eeeff2;
}
.icon-btn {
width: 28px;
height: 28px;
padding: 0 7px;
border-radius: 9999px;
font-size: 12px;
font-weight: 500;
}
.toolbar-right {
display: flex;
align-items: center;
}
.send-btn {
width: 32px;
height: 32px;
border: none;
background: #1a1a1a;
border-radius: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #ffffff;
}
.send-btn:hover {
background: #333333;
}
.send-btn:disabled {
background: #8c8c8c;
cursor: not-allowed;
}
</style>