增加模型的工具栏
This commit is contained in:
parent
e0ae9c2c09
commit
3ac2af597c
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -11,6 +11,6 @@
|
|||||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||||
"typescript.tsc.autoDetect": "off",
|
"typescript.tsc.autoDetect": "off",
|
||||||
|
|
||||||
"i18n-haru.root": "app/src/i18n",
|
"i18n-haru.root": "renderer/src/i18n",
|
||||||
"i18n-haru.main": "zh"
|
"i18n-haru.main": "zh"
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ vsc-extension-quickstart.md
|
|||||||
**/*.map
|
**/*.map
|
||||||
**/*.ts
|
**/*.ts
|
||||||
**/.vscode-test.*
|
**/.vscode-test.*
|
||||||
app/**
|
renderer/**
|
||||||
backend/**
|
service/**
|
||||||
test/**
|
test/**
|
||||||
servers/**
|
servers/**
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "renderer",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4870215 */
|
font-family: "iconfont"; /* Project id 4870215 */
|
||||||
src: url('iconfont.woff2?t=1743002215431') format('woff2'),
|
src: url('iconfont.woff2?t=1743933179660') format('woff2'),
|
||||||
url('iconfont.woff?t=1743002215431') format('woff'),
|
url('iconfont.woff?t=1743933179660') format('woff'),
|
||||||
url('iconfont.ttf?t=1743002215431') format('truetype');
|
url('iconfont.ttf?t=1743933179660') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,22 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-robot1:before {
|
||||||
|
content: "\e644";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-length:before {
|
||||||
|
content: "\e60d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-web:before {
|
||||||
|
content: "\e935";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-temperature:before {
|
||||||
|
content: "\e62e";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-close:before {
|
.icon-close:before {
|
||||||
content: "\e615";
|
content: "\e615";
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -26,7 +26,8 @@
|
|||||||
src: url("./CascadiaCode.woff2");
|
src: url("./CascadiaCode.woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -42,6 +43,7 @@ body::-webkit-scrollbar {
|
|||||||
height: 1.5px;
|
height: 1.5px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
@ -100,25 +102,17 @@ body::-webkit-scrollbar {
|
|||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner {
|
||||||
|
border-radius: .9em !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
box-shadow: 0 0 0 1px var(--main-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.digital-ide-icon {
|
|
||||||
background-image: url(./icon.svg);
|
|
||||||
background-size: 100%;
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.digital-ide-icon.big {
|
|
||||||
background-image: url(./icon.svg);
|
|
||||||
background-size: 100%;
|
|
||||||
height: 150px;
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-radio-button__original-radio:disabled:checked+.el-radio-button__inner {
|
.el-radio-button__original-radio:disabled:checked+.el-radio-button__inner {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
@ -127,3 +121,25 @@ a {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
box-shadow: unset !important;
|
box-shadow: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
/* 浅紫色背景 */
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner::-webkit-scrollbar-thumb {
|
||||||
|
background: #cb81da;
|
||||||
|
/* 使用您的主题色 */
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a259b5;
|
||||||
|
/* 深紫色悬停状态 */
|
||||||
|
}
|
18
renderer/src/components/main-panel/chat/chat.ts
Normal file
18
renderer/src/components/main-panel/chat/chat.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export interface ChatMessage {
|
||||||
|
role: 'user' | 'assistant' | 'system';
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatSetting {
|
||||||
|
modelIndex: number
|
||||||
|
systemPrompt: string
|
||||||
|
enableTools: boolean
|
||||||
|
temperature: number
|
||||||
|
enableWebSearch: boolean
|
||||||
|
contextLength: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatStorage {
|
||||||
|
messages: ChatMessage[]
|
||||||
|
settings: ChatSetting
|
||||||
|
}
|
@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chat-container" ref="chatContainerRef">
|
<div class="chat-container" ref="chatContainerRef">
|
||||||
<el-scrollbar :height="scrollHeight">
|
<el-scrollbar :height="'90%'">
|
||||||
<div class="message-list">
|
<div class="message-list">
|
||||||
<div v-for="(message, index) in messages" :key="index" :class="['message-item', message.role]">
|
<div v-for="(message, index) in messages" :key="index" :class="['message-item', message.role]">
|
||||||
<div class="message-avatar">
|
<div class="message-avatar" v-if="message.role === 'assistant'">
|
||||||
<el-avatar :icon="message.role === 'user' ? User : Comment" />
|
<span class="iconfont icon-chat"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<div class="message-role">{{ message.role === 'user' ? '你' : 'AI' }}</div>
|
<div class="message-role">{{ message.role === 'user' ? '' : 'Agent' }}</div>
|
||||||
<div class="message-text">{{ message.content }}</div>
|
<div class="message-text">
|
||||||
|
<span>{{ message.content }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -26,24 +28,39 @@
|
|||||||
|
|
||||||
<el-footer class="chat-footer" ref="footerRef">
|
<el-footer class="chat-footer" ref="footerRef">
|
||||||
<div class="input-area">
|
<div class="input-area">
|
||||||
<el-input v-model="userInput" type="textarea" :rows="3" placeholder="输入消息..." :disabled="isLoading"
|
|
||||||
@keydown.enter.prevent="handleSend" />
|
<Setting :tabId="tabId" />
|
||||||
<el-button type="primary" :loading="isLoading" @click="handleSend" class="send-button">
|
|
||||||
发送
|
<div class="input-wrapper">
|
||||||
|
<el-input v-model="userInput" type="textarea" :rows="inputHeightLines" :maxlength="2000"
|
||||||
|
placeholder="输入消息..." :disabled="isLoading" @keydown.enter="handleKeydown" resize="none"
|
||||||
|
class="chat-input" />
|
||||||
|
<el-button type="primary" :loading="isLoading" @click="handleSend" class="send-button"
|
||||||
|
:disabled="!userInput.trim()">
|
||||||
|
<span class="iconfont icon-send"></span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</el-footer>
|
</el-footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, defineComponent, defineProps, onUnmounted } from 'vue';
|
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { User, Comment } from '@element-plus/icons-vue';
|
import { User, Comment } from '@element-plus/icons-vue';
|
||||||
import { useMessageBridge } from "@/api/message-bridge";
|
import { useMessageBridge } from "@/api/message-bridge";
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { llmManager, llms } from '@/views/setting/llm';
|
||||||
|
import { tabs } from '../panel';
|
||||||
|
import { ChatMessage, ChatStorage } from './chat';
|
||||||
|
|
||||||
|
import Setting from './setting.vue';
|
||||||
|
|
||||||
defineComponent({ name: 'chat' });
|
defineComponent({ name: 'chat' });
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
tabId: {
|
tabId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -51,14 +68,58 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ChatMessage {
|
const tab = tabs.content[props.tabId];
|
||||||
role: 'user' | 'assistant';
|
const tabStorage = tab.storage as ChatStorage;
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
const userInput = ref('');
|
const userInput = ref('');
|
||||||
const messages = ref<ChatMessage[]>([]);
|
const inputHeightLines = computed(() => {
|
||||||
|
const currentLines = userInput.value.split('\n').length;
|
||||||
|
return Math.min(12, Math.max(5, currentLines));
|
||||||
|
});
|
||||||
|
const messages = ref<ChatMessage[]>([
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '你好!我是AI助手,有什么可以帮您的吗?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: '你好,能帮我写一封求职信吗?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '当然可以。请问您应聘的是什么职位?需要包含哪些特别的信息吗?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: '我想应聘前端开发工程师,有3年Vue和React经验'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '好的,我已根据您的要求写了一封求职信模板:\n\n尊敬的招聘经理,\n\n您好!我在贵公司官网上看到前端开发工程师的招聘信息...'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: '谢谢!能再帮我优化一下简历中的项目描述部分吗?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '当然可以。建议采用STAR法则(Situation-Task-Action-Result)来描述项目经验,这样更能突出您的贡献和价值。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: '什么是STAR法则?能举个例子吗?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'STAR法则是一种结构化表达方法:\n\n情境(Situation):项目背景\n任务(Task):你的职责\n行动(Action):采取的措施\n结果(Result):取得的成果\n\n例如:开发了基于Vue3的管理系统,优化了页面加载速度30%...'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: '明白了,这样写确实更专业!'
|
||||||
|
}
|
||||||
|
]);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const streamingContent = ref('');
|
const streamingContent = ref('');
|
||||||
const chatContainerRef = ref<HTMLElement>();
|
const chatContainerRef = ref<HTMLElement>();
|
||||||
@ -73,6 +134,14 @@ const updateScrollHeight = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleSend();
|
||||||
|
}
|
||||||
|
// Shift+Enter 允许自然换行
|
||||||
|
};
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!userInput.value.trim() || isLoading.value) return;
|
if (!userInput.value.trim() || isLoading.value) return;
|
||||||
|
|
||||||
@ -150,6 +219,7 @@ onUnmounted(() => {
|
|||||||
.chat-container {
|
.chat-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +227,7 @@ onUnmounted(() => {
|
|||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
padding-bottom: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-item {
|
.message-item {
|
||||||
@ -170,6 +241,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-role {
|
.message-role {
|
||||||
@ -183,6 +255,17 @@ onUnmounted(() => {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user .message-text {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user .message-text > span {
|
||||||
|
border-radius: .9em;
|
||||||
|
background-color: var(--main-light-color);
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -199,24 +282,45 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.assistant {
|
.assistant {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-footer {
|
.chat-footer {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-top: 1px solid var(--el-border-color);
|
border-top: 1px solid var(--el-border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
position: absolute;
|
||||||
|
height: fit-content;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-area {
|
.input-area {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
position: relative;
|
||||||
gap: 12px;
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
padding-right: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input textarea {
|
||||||
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-button {
|
.send-button {
|
||||||
align-self: flex-end;
|
position: absolute;
|
||||||
height: 72px;
|
right: 8px;
|
||||||
|
bottom: 8px;
|
||||||
|
height: auto;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-cursor {
|
.typing-cursor {
|
||||||
@ -224,7 +328,14 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0; }
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
224
renderer/src/components/main-panel/chat/setting.vue
Normal file
224
renderer/src/components/main-panel/chat/setting.vue
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-settings">
|
||||||
|
<el-tooltip content="选择模型" placement="top">
|
||||||
|
<div class="setting-button" size="small" @click="showModelDialog = true">
|
||||||
|
<span class="iconfont icon-model">
|
||||||
|
{{ llms[llmManager.currentModelIndex].name }}/{{
|
||||||
|
llms[llmManager.currentModelIndex].models[selectedModelIndex] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="系统提示词" placement="top">
|
||||||
|
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
|
||||||
|
@click="showSystemPromptDialog = true">
|
||||||
|
<span class="iconfont icon-robot"></span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="工具使用" placement="top">
|
||||||
|
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableTools }" size="small"
|
||||||
|
@click="toggleTools">
|
||||||
|
<span class="iconfont icon-tool"></span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="网络搜索" placement="top">
|
||||||
|
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
|
||||||
|
@click="toggleWebSearch">
|
||||||
|
<span class="iconfont icon-web"></span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="温度参数" placement="top">
|
||||||
|
<div class="setting-button" size="small" @click="showTemperatureSlider = true">
|
||||||
|
<span class="iconfont icon-temperature"></span>
|
||||||
|
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="上下文长度" placement="top">
|
||||||
|
<div class="setting-button" size="small" @click="showContextLengthDialog = true">
|
||||||
|
<span class="iconfont icon-length"></span>
|
||||||
|
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 模型选择对话框 -->
|
||||||
|
<el-dialog v-model="showModelDialog" title="选择模型" width="400px">
|
||||||
|
<el-radio-group v-model="selectedModelIndex">
|
||||||
|
<el-radio v-for="(model, index) in availableModels" :key="index" :label="index">
|
||||||
|
{{ model }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showModelDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmModelChange">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- System Prompt对话框 -->
|
||||||
|
<el-dialog v-model="showSystemPromptDialog" title="系统提示词" width="600px">
|
||||||
|
<el-input v-model="tabStorage.settings.systemPrompt" type="textarea" :rows="8"
|
||||||
|
placeholder="输入系统提示词(例如:你是一个专业的前端开发助手,用中文回答)" clearable />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showSystemPromptDialog = false">关闭</el-button>
|
||||||
|
<el-button type="primary" @click="showSystemPromptDialog = false">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 温度参数滑块 -->
|
||||||
|
<el-dialog v-model="showTemperatureSlider" title="设置温度参数" width="400px">
|
||||||
|
<div class="slider-container">
|
||||||
|
<el-slider v-model="tabStorage.settings.temperature" :min="0" :max="2" :step="0.1" />
|
||||||
|
<div class="slider-tips">
|
||||||
|
<span>精确(0)</span>
|
||||||
|
<span>平衡(1)</span>
|
||||||
|
<span>创意(2)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showTemperatureSlider = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 上下文长度设置 - 改为滑块形式 -->
|
||||||
|
<el-dialog v-model="showContextLengthDialog" title="设置上下文长度" width="400px">
|
||||||
|
<div class="slider-container">
|
||||||
|
<el-slider v-model="tabStorage.settings.contextLength" :min="0" :max="99" :step="1" />
|
||||||
|
<div class="slider-tips">
|
||||||
|
<span>0: 无上下文</span>
|
||||||
|
<span>10: 默认</span>
|
||||||
|
<span>99: 最大</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showContextLengthDialog = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, defineProps } from 'vue';
|
||||||
|
import { llmManager, llms } from '@/views/setting/llm';
|
||||||
|
import { tabs } from '../panel';
|
||||||
|
import { ChatSetting, ChatStorage } from './chat';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const showModelDialog = ref(false);
|
||||||
|
const showTemperatureSlider = ref(false);
|
||||||
|
const showContextLengthDialog = ref(false);
|
||||||
|
const showSystemPromptDialog = ref(false);
|
||||||
|
|
||||||
|
const tab = tabs.content[props.tabId];
|
||||||
|
const tabStorage = tab.storage as ChatStorage & { settings: ChatSetting };
|
||||||
|
|
||||||
|
|
||||||
|
if (!tabStorage.settings) {
|
||||||
|
tabStorage.settings = {
|
||||||
|
modelIndex: llmManager.currentModelIndex,
|
||||||
|
enableTools: true,
|
||||||
|
enableWebSearch: false,
|
||||||
|
temperature: 0.7,
|
||||||
|
contextLength: 10,
|
||||||
|
systemPrompt: ''
|
||||||
|
} as ChatSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const selectedModelIndex = ref(llmManager.currentModelIndex);
|
||||||
|
|
||||||
|
const availableModels = computed(() => {
|
||||||
|
return llms[llmManager.currentModelIndex].models;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasSystemPrompt = computed(() => {
|
||||||
|
return !!tabStorage.settings.systemPrompt?.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleTools = () => {
|
||||||
|
tabStorage.settings.enableTools = !tabStorage.settings.enableTools;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleWebSearch = () => {
|
||||||
|
tabStorage.settings.enableWebSearch = !tabStorage.settings.enableWebSearch;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmModelChange = () => {
|
||||||
|
llmManager.currentModelIndex = selectedModelIndex.value;
|
||||||
|
showModelDialog.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-settings {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-button {
|
||||||
|
padding: 5px 8px;
|
||||||
|
margin-right: 3px;
|
||||||
|
border-radius: .5em;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--animation-3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-button.active {
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
transition: var(--animation-3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-button.active:hover {
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
transition: var(--animation-3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-button:hover {
|
||||||
|
background-color: var(--background);
|
||||||
|
transition: var(--animation-3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-badge {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-temperature {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-length {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-tips {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
@ -72,7 +72,7 @@ function pageAddNewTab() {
|
|||||||
background-color: var(--sidebar);
|
background-color: var(--sidebar);
|
||||||
border-radius: 1.2em;
|
border-radius: 1.2em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 95%;
|
height: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-tabs-container {
|
.scroll-tabs-container {
|
||||||
|
@ -23,6 +23,8 @@ export function setDefaultCss() {
|
|||||||
document.body.style.setProperty('--el-fill-color-dark', 'var(--main-light-color)');
|
document.body.style.setProperty('--el-fill-color-dark', 'var(--main-light-color)');
|
||||||
document.body.style.setProperty('--el-fill-color-darker', 'var(--main-light-color)');
|
document.body.style.setProperty('--el-fill-color-darker', 'var(--main-light-color)');
|
||||||
document.body.style.setProperty('--el-color-primary-light-5', 'var(--button-disabled)');
|
document.body.style.setProperty('--el-color-primary-light-5', 'var(--button-disabled)');
|
||||||
|
document.body.style.setProperty('--el-bg-color', 'var(--background)');
|
||||||
|
document.body.style.setProperty('--el-text-color-primary', 'var(--foreground)');
|
||||||
|
|
||||||
// document.body.style.setProperty('--el-color-white', 'var(--background)');
|
// document.body.style.setProperty('--el-color-white', 'var(--background)');
|
||||||
|
|
||||||
|
22
renderer/src/hook/util.ts
Normal file
22
renderer/src/hook/util.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export function getCurrentTime() {
|
||||||
|
// 创建一个Date对象
|
||||||
|
const date = new Date();
|
||||||
|
// 获取年份
|
||||||
|
const year: string | number = date.getFullYear();
|
||||||
|
// 获取月份(0-11)
|
||||||
|
let month: string | number = date.getMonth() + 1;
|
||||||
|
// 获取日期(1-31)
|
||||||
|
let day: string | number = date.getDate();
|
||||||
|
// 获取小时(0-23)
|
||||||
|
let hour: string | number = date.getHours();
|
||||||
|
// 获取分钟(0-59)
|
||||||
|
let minute: string | number = date.getMinutes();
|
||||||
|
// 如果月份、日期、小时、分钟或秒钟小于10,则在前面补0
|
||||||
|
month = month < 10 ? "0" + month : month;
|
||||||
|
day = day < 10 ? "0" + day : day;
|
||||||
|
hour = hour < 10 ? "0" + hour : hour;
|
||||||
|
minute = minute < 10 ? "0" + minute : minute;
|
||||||
|
// 拼接成字符串
|
||||||
|
const timeStr = year + "年" + month + "月" + day + "日" + " " + hour + ":" + minute;
|
||||||
|
return timeStr;
|
||||||
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "تحديث",
|
"refresh": "تحديث",
|
||||||
"read-prompt": "قراءة المطالبة",
|
"read-prompt": "قراءة المطالبة",
|
||||||
"execute-tool": "تشغيل",
|
"execute-tool": "تشغيل",
|
||||||
"save": "حفظ"
|
"save": "حفظ",
|
||||||
|
"send": "إرسال"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "Aktualisieren",
|
"refresh": "Aktualisieren",
|
||||||
"read-prompt": "Prompt lesen",
|
"read-prompt": "Prompt lesen",
|
||||||
"execute-tool": "Ausführen",
|
"execute-tool": "Ausführen",
|
||||||
"save": "Speichern"
|
"save": "Speichern",
|
||||||
|
"send": "Senden"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"read-prompt": "Read prompt",
|
"read-prompt": "Read prompt",
|
||||||
"execute-tool": "Run",
|
"execute-tool": "Run",
|
||||||
"save": "Save"
|
"save": "Save",
|
||||||
|
"send": "Send"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "Rafraîchir",
|
"refresh": "Rafraîchir",
|
||||||
"read-prompt": "Lire l'invite",
|
"read-prompt": "Lire l'invite",
|
||||||
"execute-tool": "Exécuter",
|
"execute-tool": "Exécuter",
|
||||||
"save": "Enregistrer"
|
"save": "Enregistrer",
|
||||||
|
"send": "Envoyer"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "更新",
|
"refresh": "更新",
|
||||||
"read-prompt": "プロンプトを読み取る",
|
"read-prompt": "プロンプトを読み取る",
|
||||||
"execute-tool": "実行",
|
"execute-tool": "実行",
|
||||||
"save": "保存"
|
"save": "保存",
|
||||||
|
"send": "送信"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "새로 고침",
|
"refresh": "새로 고침",
|
||||||
"read-prompt": "프롬프트 읽기",
|
"read-prompt": "프롬프트 읽기",
|
||||||
"execute-tool": "실행",
|
"execute-tool": "실행",
|
||||||
"save": "저장"
|
"save": "저장",
|
||||||
|
"send": "보내기"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "Обновить",
|
"refresh": "Обновить",
|
||||||
"read-prompt": "Чтение подсказки",
|
"read-prompt": "Чтение подсказки",
|
||||||
"execute-tool": "Запуск",
|
"execute-tool": "Запуск",
|
||||||
"save": "Сохранить"
|
"save": "Сохранить",
|
||||||
|
"send": "Отправить"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"read-prompt": "读取 prompt",
|
"read-prompt": "读取 prompt",
|
||||||
"execute-tool": "运行",
|
"execute-tool": "运行",
|
||||||
"save": "保存"
|
"save": "保存",
|
||||||
|
"send": "发送"
|
||||||
}
|
}
|
@ -116,5 +116,6 @@
|
|||||||
"refresh": "重新整理",
|
"refresh": "重新整理",
|
||||||
"read-prompt": "讀取提示",
|
"read-prompt": "讀取提示",
|
||||||
"execute-tool": "執行",
|
"execute-tool": "執行",
|
||||||
"save": "儲存"
|
"save": "儲存",
|
||||||
|
"send": "傳送"
|
||||||
}
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<Welcome v-show="!tabs.activeTab.component"></Welcome>
|
<Welcome v-show="!tabs.activeTab.component"></Welcome>
|
||||||
|
|
||||||
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
||||||
<div v-show="tabs.activeTab.component">
|
<div v-show="tabs.activeTab.component" style="height: 100%;">
|
||||||
<component
|
<component
|
||||||
v-show="tab === tabs.activeTab"
|
v-show="tab === tabs.activeTab"
|
||||||
v-for="(tab, index) of tabs.content"
|
v-for="(tab, index) of tabs.content"
|
||||||
|
@ -6,7 +6,25 @@
|
|||||||
"icon": "icon-robot",
|
"icon": "icon-robot",
|
||||||
"type": "blank",
|
"type": "blank",
|
||||||
"componentIndex": 3,
|
"componentIndex": 3,
|
||||||
"storage": {}
|
"storage": {
|
||||||
|
"settings": {
|
||||||
|
"modelIndex": 0,
|
||||||
|
"enableTools": true,
|
||||||
|
"enableWebSearch": false,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"contextLength": 41,
|
||||||
|
"systemPrompt": "ad"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "资源",
|
||||||
|
"icon": "icon-file",
|
||||||
|
"type": "blank",
|
||||||
|
"componentIndex": 0,
|
||||||
|
"storage": {
|
||||||
|
"currentResourceName": "greeting"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user