feat(desktop): ✨ 优化一些逻辑
1. 优化通知配置 2. 优化命名规范 3. 优化代码逻辑
This commit is contained in:
2
src/renderer/components.d.ts
vendored
2
src/renderer/components.d.ts
vendored
@@ -12,6 +12,7 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||
ACollapse: typeof import('@arco-design/web-vue')['Collapse']
|
||||
ACollapseItem: typeof import('@arco-design/web-vue')['CollapseItem']
|
||||
ActiveMenu: typeof import('./src/components/ActiveMenu.vue')['default']
|
||||
@@ -23,6 +24,7 @@ declare module 'vue' {
|
||||
AOption: typeof import('@arco-design/web-vue')['Option']
|
||||
ASelect: typeof import('@arco-design/web-vue')['Select']
|
||||
ASlider: typeof import('@arco-design/web-vue')['Slider']
|
||||
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
||||
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
||||
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Electron</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
/>
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>读书心得助手 | 让每一篇阅读者享受阅读与思考的乐趣</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
src/renderer/src/assets/application.png
Normal file
BIN
src/renderer/src/assets/application.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
@@ -16,7 +16,6 @@ const navButtons = [
|
||||
]
|
||||
|
||||
const active = (key: string) => {
|
||||
console.log(key)
|
||||
go('/' + key)
|
||||
}
|
||||
</script>
|
||||
@@ -40,7 +39,7 @@ const active = (key: string) => {
|
||||
:fill="activeBtn === btn.key ? '#7816ff' : '#64748b'"
|
||||
/>
|
||||
|
||||
<span class="text-[10px] ml-1.5 hidden group-hover:inline-block text-slate-500 font-medium">
|
||||
<span class="text-[10px] ml-1.5 group-hover:inline-block text-slate-500 font-medium">
|
||||
{{ btn.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouterHook } from '@renderer/hooks/useRouterHook'
|
||||
import {
|
||||
AdProduct,
|
||||
ArrowRight,
|
||||
Github,
|
||||
HoldInterface,
|
||||
InternalData,
|
||||
Mail,
|
||||
Twitter
|
||||
} from '@icon-park/vue-next'
|
||||
import { AdProduct, Github, HoldInterface, InternalData, Mail, Twitter } from '@icon-park/vue-next'
|
||||
import applicationImg from '@renderer/assets/application.png'
|
||||
|
||||
const { goBack } = useRouterHook()
|
||||
const { go } = useRouterHook()
|
||||
|
||||
const coreValues = [
|
||||
{
|
||||
icon: InternalData,
|
||||
title: '数据驱动',
|
||||
desc: '基于自研深度学习模型,精准提取每一本书籍的灵魂。'
|
||||
desc: '基于大模型,精准提取每一本书籍的灵魂。'
|
||||
},
|
||||
{
|
||||
icon: AdProduct,
|
||||
@@ -34,15 +27,9 @@ const coreValues = [
|
||||
<template>
|
||||
<div class="flex-1 h-full overflow-y-auto bg-[#FAFAFB] custom-scroll">
|
||||
<section class="pt-20 pb-16 px-6 text-center bg-white border-b border-slate-50">
|
||||
<div class="inline-flex items-center space-x-2 bg-purple-50 px-3 py-1 rounded-full mb-6">
|
||||
<span class="w-2 h-2 bg-[#7816ff] rounded-full animate-pulse"></span>
|
||||
<span class="text-[10px] font-bold text-[#7816ff] uppercase tracking-widest"
|
||||
>Version 2.0.0 Now Live</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="text-4xl font-black text-slate-800 mb-4 tracking-tight">
|
||||
让每一页文字<br />
|
||||
<span class="text-[#7816ff]">都有迹可循</span>
|
||||
<h1 class="text-4xl font-black text-slate-800 mb-4 tracking-tight flex flex-col gap-4">
|
||||
<span>让每一篇阅读者</span>
|
||||
<span class="text-[#7816ff]">享受阅读与思考的乐趣</span>
|
||||
</h1>
|
||||
<p class="max-w-xl mx-auto text-slate-400 text-sm leading-relaxed mb-8">
|
||||
我们致力于通过最前沿的 AI 技术,帮助深度阅读者高效消化知识。
|
||||
@@ -53,12 +40,10 @@ const coreValues = [
|
||||
type="primary"
|
||||
shape="round"
|
||||
class="bg-[#7816ff] border-none px-8 h-10 shadow-lg shadow-purple-100"
|
||||
@click="go('/task/create')"
|
||||
>
|
||||
开始体验
|
||||
</a-button>
|
||||
<a-button shape="round" class="px-8 h-10 border-slate-200" @click="goBack()">
|
||||
返回探索
|
||||
</a-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -90,10 +75,7 @@ const coreValues = [
|
||||
class="bg-white rounded-[32px] border border-slate-100 shadow-sm overflow-hidden flex flex-col md:flex-row items-center"
|
||||
>
|
||||
<div class="flex-1 p-12 md:p-16">
|
||||
<div class="text-[11px] font-bold text-[#7816ff] uppercase tracking-[0.2em] mb-4">
|
||||
Our Mission
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 mb-6">为了那些真正热爱文字的人</h2>
|
||||
<h2 class="text-2xl font-bold text-slate-800 mb-6">让那些真正热爱文字的人</h2>
|
||||
<p class="text-sm text-slate-500 leading-relaxed mb-8">
|
||||
在一个信息碎片化的时代,深度阅读正变得前所未有的奢侈。我们不希望 AI
|
||||
替代阅读,而是希望它能作为你的“数字笔友”,帮你梳理逻辑、捕捉灵感,让你从繁琐的摘要工作中解放,回归思考本身。
|
||||
@@ -101,16 +83,16 @@ const coreValues = [
|
||||
<div class="flex items-center space-x-6">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-black text-slate-800">12,400+</span>
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase">Active Users</span>
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase">使用用户</span>
|
||||
</div>
|
||||
<div class="w-[1px] h-8 bg-slate-100"></div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-black text-slate-800">8.5M</span>
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase">Words Generated</span>
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase">阅读量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 w-full h-[400px] bg-slate-50 flex items-center justify-center relative">
|
||||
<div class="flex-1 w-full h-[400px] flex items-center justify-center relative">
|
||||
<div
|
||||
class="absolute inset-0 opacity-20"
|
||||
style="
|
||||
@@ -119,19 +101,10 @@ const coreValues = [
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
class="relative z-10 w-64 h-64 bg-white rounded-3xl shadow-xl border border-slate-100 flex flex-col p-6 animate-bounce-slow"
|
||||
class="relative z-10 w-90 bg-white rounded-3xl shadow-xl border border-slate-100 flex flex-col p-6 animate-bounce-slow"
|
||||
>
|
||||
<div class="w-8 h-8 bg-purple-50 rounded-lg mb-4"></div>
|
||||
<div class="h-2 w-3/4 bg-slate-100 rounded-full mb-3"></div>
|
||||
<div class="h-2 w-full bg-slate-50 rounded-full mb-3"></div>
|
||||
<div class="h-2 w-2/3 bg-slate-50 rounded-full"></div>
|
||||
<div class="mt-auto flex justify-end">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-[#7816ff] flex items-center justify-center text-white"
|
||||
>
|
||||
<arrow-right size="14" />
|
||||
</div>
|
||||
</div>
|
||||
<img :src="applicationImg" alt="" />
|
||||
<div class="mt-auto flex justify-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
45
src/renderer/src/pages/faq/data/faqData.ts
Normal file
45
src/renderer/src/pages/faq/data/faqData.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export const faqList = [
|
||||
{
|
||||
id: 1,
|
||||
category: 'general',
|
||||
q: '这个 AI 阅读工具是如何工作的?',
|
||||
a: '我们利用深度学习模型对书籍文本进行语义分析。它不仅能总结全文,还能根据你选择的“职业背景”提取特定的知识点。'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: 'usage',
|
||||
q: '生成的字数可以超过 5000 字吗?',
|
||||
a: '目前单次生成上限为 5000 字,以确保内容的逻辑连贯性。如果需要更长篇幅,建议分章节创建任务。'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
category: 'billing',
|
||||
q: '当前有订阅计划吗?',
|
||||
a: '当前工具为免费使用,不需要任何费用,但是需要自己使用大模型KEY'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
category: 'privacy',
|
||||
q: '如何查看我的数据?',
|
||||
a: '你可以在设置页面查看你的数据。'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
category: 'usage',
|
||||
q: '如何设置大模型?',
|
||||
a: `
|
||||
您可以在<span style="color: red">『设置中心』-『大模型配置』中分别设置读书心得与摘要模型</span>。
|
||||
<br/>推荐使用【阿里云-心流平台 (iflow.cn)】提供的免费 API,其单次生成支持长文本处理。若生成内容接近 5000 字上限,建议分章节创建任务以获得最佳逻辑效果。
|
||||
<br/><br/>可以参考以下操作指南:
|
||||
<br/>
|
||||
<ul>
|
||||
<li>获取 API Key:访问 阿里云心流平台 (iflow.cn)。</li>
|
||||
注册并登录后,在后台创建一个新的 API Key。心流平台目前提供稳定的免费额度,非常适合个人阅读助手使用。
|
||||
<li>配置接口地址:</li>
|
||||
在应用设置的 Base URL 处填入:https://apis.iflow.cn/v1。
|
||||
<li>模型选择:</li>
|
||||
平台提供的最新模型 ID。
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
]
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Security,
|
||||
Wallet
|
||||
} from '@icon-park/vue-next'
|
||||
import { faqList } from './data/faqData'
|
||||
|
||||
// 分类配置
|
||||
const categories = [
|
||||
@@ -21,34 +22,6 @@ const categories = [
|
||||
const activeCategory = ref('general')
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 问题数据
|
||||
const faqList = [
|
||||
{
|
||||
id: 1,
|
||||
category: 'general',
|
||||
q: '这个 AI 阅读工具是如何工作的?',
|
||||
a: '我们利用深度学习模型对书籍文本进行语义分析。它不仅能总结全文,还能根据你选择的“职业背景”提取特定的知识点。'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: 'usage',
|
||||
q: '生成的字数可以超过 5000 字吗?',
|
||||
a: '目前单次生成上限为 5000 字,以确保内容的逻辑连贯性。如果需要更长篇幅,建议分章节创建任务。'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
category: 'billing',
|
||||
q: '订阅后可以申请退款吗?',
|
||||
a: '在订阅后的 24 小时内且未使用过算力资源的情况下,您可以联系客服申请全额退款。'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
category: 'general',
|
||||
q: '支持哪些格式的书籍?',
|
||||
a: '目前支持 PDF, EPUB, TXT 格式。为了获得最佳效果,建议上传排版清晰的文档。'
|
||||
}
|
||||
]
|
||||
|
||||
// 搜索过滤逻辑
|
||||
const filteredFaqs = computed(() => {
|
||||
return faqList.filter((item) => {
|
||||
@@ -110,7 +83,7 @@ const filteredFaqs = computed(() => {
|
||||
<span class="text-[14px] font-bold text-slate-700">{{ faq.q }}</span>
|
||||
</template>
|
||||
<div class="text-[13px] leading-relaxed text-slate-500 py-2">
|
||||
{{ faq.a }}
|
||||
<span v-html="faq.a"></span>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
|
||||
@@ -1,62 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, reactive, ref, toRaw } from 'vue'
|
||||
import { SettingConfig } from '@icon-park/vue-next'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { trpc } from '@renderer/lib/trpc'
|
||||
import { modelOptions } from '../data/ModelData'
|
||||
import { ModelForm } from '@renderer/pages/setting/types/IModel'
|
||||
|
||||
interface ModelOption {
|
||||
id: string // 这里的 id 即 modelName
|
||||
name: string
|
||||
desc: string
|
||||
tag?: string
|
||||
const activeTab = ref<'reading' | 'summary'>('reading') // 初始设为 reading
|
||||
|
||||
const modelForm = reactive<ModelForm>({
|
||||
reading: {
|
||||
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
|
||||
baseURL: 'https://apis.iflow.cn/v1',
|
||||
modelName: 'deepseek-v3.2',
|
||||
temperature: 0.7
|
||||
},
|
||||
summary: {
|
||||
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
|
||||
baseURL: 'https://apis.iflow.cn/v1',
|
||||
modelName: 'deepseek-v3.2',
|
||||
temperature: 0.3
|
||||
}
|
||||
})
|
||||
|
||||
// --- 逻辑方法 ---
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const savedConfig = (await trpc.config.getChatConfigs.query()) as any
|
||||
if (savedConfig) {
|
||||
// 这里的 Key 必须与后端存储的 chatModels.reading 对应
|
||||
if (savedConfig.reading) Object.assign(modelForm.reading, savedConfig.reading)
|
||||
if (savedConfig.summary) Object.assign(modelForm.summary, savedConfig.summary)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
interface ModelConfig {
|
||||
apiKey: string
|
||||
baseURL: string
|
||||
modelName: string
|
||||
temperature: number
|
||||
const handleSave = async (type: 'reading' | 'summary') => {
|
||||
try {
|
||||
// 关键点:使用 toRaw 转换,剥离 Vue 的响应式代理
|
||||
const configToSave = toRaw(modelForm[type])
|
||||
|
||||
// 打印检查一下,确保是普通对象而非 Proxy
|
||||
console.log('正在保存纯对象数据:', configToSave)
|
||||
|
||||
await trpc.config.saveChatConfig.mutate({
|
||||
type,
|
||||
config: configToSave
|
||||
})
|
||||
|
||||
Message.success({
|
||||
content: `${type === 'reading' ? '读书心得' : '摘要总结'}配置已安全保存`,
|
||||
showIcon: true
|
||||
})
|
||||
} catch (error) {
|
||||
Message.error('保存失败,无法克隆对象')
|
||||
console.error('tRPC 错误详情:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 包含两种类型的配置
|
||||
interface ModelForm {
|
||||
reflection: ModelConfig
|
||||
summary: ModelConfig
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: ModelForm // 必填,父组件传 reactive 对象
|
||||
options?: ModelOption[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
|
||||
// 当前激活的标签页
|
||||
const activeTab = ref<'reflection' | 'summary'>('reflection')
|
||||
|
||||
// 处理模型卡片点击:更新当前 Tab 下的 modelName
|
||||
const selectModel = (modelId: string) => {
|
||||
const newData = { ...props.modelValue }
|
||||
newData[activeTab.value].modelName = modelId
|
||||
emit('update:modelValue', newData)
|
||||
modelForm[activeTab.value].modelName = modelId
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="modelValue" class="space-y-6 animate-in">
|
||||
<div class="space-y-6 animate-in pb-10">
|
||||
<a-tabs v-model:active-key="activeTab" type="capsule" class="custom-tabs">
|
||||
<a-tab-pane key="reflection" title="读书心得模型" />
|
||||
<a-tab-pane key="reading" title="读书心得模型" />
|
||||
<a-tab-pane key="summary" title="摘要总结模型" />
|
||||
</a-tabs>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div class="flex items-center space-x-2 mb-8">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">接口配置</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="setting-label">Base URL (API 代理地址)</label>
|
||||
<a-input
|
||||
v-model="modelValue[activeTab].baseURL"
|
||||
v-model="modelForm[activeTab].baseURL"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
class="custom-input"
|
||||
/>
|
||||
@@ -64,7 +93,7 @@ const selectModel = (modelId: string) => {
|
||||
<div class="space-y-2">
|
||||
<label class="setting-label">API Key</label>
|
||||
<a-input-password
|
||||
v-model="modelValue[activeTab].apiKey"
|
||||
v-model="modelForm[activeTab].apiKey"
|
||||
placeholder="sk-..."
|
||||
class="custom-input"
|
||||
/>
|
||||
@@ -77,37 +106,34 @@ const selectModel = (modelId: string) => {
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">模型名称 (modelName)</h3>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<a-input
|
||||
v-model="modelValue[activeTab].modelName"
|
||||
placeholder="或者手动输入模型 ID,如 gpt-4o"
|
||||
v-model="modelForm[activeTab].modelName"
|
||||
placeholder="手动输入模型 ID,如 gpt-4o"
|
||||
class="custom-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div
|
||||
v-for="m in options"
|
||||
v-for="m in modelOptions"
|
||||
:key="m.id"
|
||||
@click="selectModel(m.id)"
|
||||
:class="[
|
||||
'p-4 rounded-xl border-2 cursor-pointer transition-all relative overflow-hidden',
|
||||
modelValue[activeTab].modelName === m.id
|
||||
modelForm[activeTab].modelName === m.id
|
||||
? 'border-[#7816ff] bg-purple-50/30'
|
||||
: 'border-slate-50 bg-slate-50/50 hover:border-slate-200'
|
||||
]"
|
||||
>
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<span class="text-xs font-bold text-slate-800">{{ m.name }}</span>
|
||||
<span v-if="m.tag" class="text-[9px] bg-[#7816ff] text-white px-1.5 py-0.5 rounded">
|
||||
{{ m.tag }}
|
||||
</span>
|
||||
<span v-if="m.tag" class="text-[9px] bg-[#7816ff] text-white px-1.5 py-0.5 rounded">{{
|
||||
m.tag
|
||||
}}</span>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 leading-relaxed">{{ m.desc }}</p>
|
||||
|
||||
<div
|
||||
v-if="modelValue[activeTab].modelName === m.id"
|
||||
v-if="modelForm[activeTab].modelName === m.id"
|
||||
class="absolute -right-2 -bottom-2 text-[#7816ff]/10"
|
||||
>
|
||||
<SettingConfig theme="outline" size="48" />
|
||||
@@ -120,33 +146,31 @@ const selectModel = (modelId: string) => {
|
||||
<div class="flex items-center space-x-2 mb-8">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">运行参数</h3>
|
||||
<p class="text-[11px] text-[#7816ff] leading-relaxed">
|
||||
<p class="text-[11px] text-[#7816ff] leading-relaxed ml-4 italic">
|
||||
正在配置
|
||||
<span class="font-bold underline">{{
|
||||
activeTab === 'reflection' ? '心得生成' : '文本总结'
|
||||
activeTab === 'reading' ? '心得生成' : '文本总结'
|
||||
}}</span>
|
||||
专用模型。 建议为总结模型设置较低的 Temperature 以保证稳定性。
|
||||
专用模型。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<label class="setting-label"
|
||||
>随机性 (Temperature): {{ modelValue[activeTab].temperature }}</label
|
||||
>随机性 (Temperature): {{ modelForm[activeTab].temperature }}</label
|
||||
>
|
||||
<span class="text-[10px] text-slate-400">建议心得 0.7-0.8,总结 0.2-0.3</span>
|
||||
</div>
|
||||
<a-slider v-model="modelValue[activeTab].temperature" :min="0" :max="1.2" :step="0.1" />
|
||||
<a-slider v-model="modelForm[activeTab].temperature" :min="0" :max="1.2" :step="0.1" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-4 border-t border-slate-50">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="rounded-lg bg-[#7816ff] border-none px-8 font-bold"
|
||||
@click="emit('save', activeTab)"
|
||||
@click="handleSave(activeTab)"
|
||||
>
|
||||
保存 {{ activeTab === 'reflection' ? '心得' : '总结' }} 配置
|
||||
保存 {{ activeTab === 'reading' ? '心得' : '总结' }} 配置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,7 +179,7 @@ const selectModel = (modelId: string) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 继承原有样式 */
|
||||
/* 样式部分保持不变... */
|
||||
.setting-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
@@ -165,17 +189,24 @@ const selectModel = (modelId: string) => {
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* Tabs 风格微调适配你的紫色主题 */
|
||||
:deep(.custom-tabs .arco-tabs-nav-capsule) {
|
||||
background-color: #f1f5f9;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
:deep(.custom-input) {
|
||||
background-color: #fcfcfd !important;
|
||||
border: 1px solid #f1f5f9 !important;
|
||||
border-radius: 12px !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
:deep(.custom-tabs .arco-tabs-nav-capsule-light) {
|
||||
background-color: #fff;
|
||||
color: #7816ff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-in {
|
||||
animation: fadeIn 0.4s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
87
src/renderer/src/pages/setting/components/NotFound.vue
Normal file
87
src/renderer/src/pages/setting/components/NotFound.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { Hourglass } from '@icon-park/vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[600px] flex flex-col items-center justify-center animate-in">
|
||||
<div
|
||||
class="relative bg-white rounded-3xl border border-slate-100 shadow-xl shadow-slate-200/50 p-12 max-w-md w-full text-center overflow-hidden"
|
||||
>
|
||||
<div class="space-y-3 relative z-10">
|
||||
<h2 class="text-xl font-bold text-slate-800 tracking-tight">
|
||||
<span class="text-[#7816ff]">正在构建中</span>
|
||||
</h2>
|
||||
<p class="text-sm text-slate-400 leading-relaxed px-3">
|
||||
我们正在为该模块编写最后一行代码,以确保为您提供最极致的体验。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 space-y-4">
|
||||
<div class="h-2 w-full bg-slate-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-gradient-to-r from-[#7816ff] to-[#a855f7] w-[50%] rounded-full relative"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 right-0 h-full w-2 bg-white/30 skew-x-12 animate-shimmer"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pt-8 border-t border-slate-50 flex items-center justify-center gap-6">
|
||||
<div class="flex items-center gap-2 text-[11px] text-slate-400 font-medium">
|
||||
<hourglass theme="outline" size="14" fill="#94a3b8" />
|
||||
预计下个版本上线
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
from {
|
||||
transform: translateX(-100%) skewX(-12deg);
|
||||
}
|
||||
to {
|
||||
transform: translateX(200%) skewX(-12deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-shimmer {
|
||||
animation: shimmer 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-in {
|
||||
animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue'
|
||||
import { Remind, Tips, SettingTwo, MessageSuccess } from '@icon-park/vue-next'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { trpc } from '@renderer/lib/trpc'
|
||||
import { INoticeConfig } from '@shared/types/IConfig'
|
||||
|
||||
// 1. 数据结构定义
|
||||
const notificationForm = reactive<INoticeConfig>({
|
||||
masterSwitch: true, // 总开关
|
||||
taskCompleted: true, // 任务完成时提醒
|
||||
taskFailed: true, // 任务失败时提醒
|
||||
silentMode: false // 静默模式(仅弹窗无声音)
|
||||
})
|
||||
|
||||
// 2. 加载与保存逻辑
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const saved = await trpc.config.getNoticeConfigs.query()
|
||||
if (saved) Object.assign(notificationForm, saved)
|
||||
} catch (e) {
|
||||
console.error('加载通知设置失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggle = async () => {
|
||||
try {
|
||||
await trpc.config.saveNoticeConfigs.mutate(toRaw(notificationForm))
|
||||
Message.success({ content: '通知偏好已更新', showIcon: true })
|
||||
} catch (e) {
|
||||
Message.error('保存设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6 animate-in">
|
||||
<div
|
||||
class="bg-gradient-to-br from-[#7816ff] to-[#a855f7] rounded-2xl p-8 text-white shadow-lg shadow-purple-200/50"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="space-y-2">
|
||||
<h2 class="text-xl font-bold flex items-center gap-2">
|
||||
<Remind theme="filled" size="24" fill="#fff" /> 通知管理中心
|
||||
</h2>
|
||||
<p class="text-purple-100 text-xs opacity-80">
|
||||
实时掌握读书心得生成进度,确保每一份灵感都能及时送达。
|
||||
</p>
|
||||
</div>
|
||||
<a-switch
|
||||
v-model="notificationForm.masterSwitch"
|
||||
type="line"
|
||||
@change="handleToggle"
|
||||
class="custom-white-switch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8"
|
||||
:class="{ 'opacity-60 grayscale-[0.5] pointer-events-none': !notificationForm.masterSwitch }"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">任务状态通知</h3>
|
||||
</div>
|
||||
<a-tag size="small" color="arcoblue" class="rounded-md font-bold text-[10px]"
|
||||
>实时推送</a-tag
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
class="flex items-center justify-between p-4 rounded-xl bg-slate-50/50 border border-transparent hover:border-slate-100 transition-all"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center">
|
||||
<message-success theme="outline" size="20" fill="#00b42a" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-bold text-slate-700">生成成功提醒</div>
|
||||
<div class="text-[11px] text-slate-400">
|
||||
当读书心得、摘要生成完毕时,发送桌面通知。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-checkbox v-model="notificationForm.taskCompleted" @change="handleToggle" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between p-4 rounded-xl bg-slate-50/50 border border-transparent hover:border-slate-100 transition-all"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-red-50 flex items-center justify-center">
|
||||
<tips theme="outline" size="20" fill="#f53f3f" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-bold text-slate-700">异常中断提醒</div>
|
||||
<div class="text-[11px] text-slate-400">
|
||||
若因网络波动或 API 余额不足导致生成失败时提醒。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-checkbox v-model="notificationForm.taskFailed" @change="handleToggle" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between p-4 rounded-xl bg-slate-50/50 border border-transparent hover:border-slate-100 transition-all"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-purple-50 flex items-center justify-center">
|
||||
<setting-two theme="outline" size="20" fill="#7816ff" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-bold text-slate-700">静默模式</div>
|
||||
<div class="text-[11px] text-slate-400">通知弹出时不再播放系统提示音。</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-switch v-model="notificationForm.silentMode" size="small" @change="handleToggle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-3 p-4 bg-blue-50/50 rounded-xl border border-blue-100">
|
||||
<remind theme="outline" size="16" fill="#0066ff" class="mt-0.5" />
|
||||
<p class="text-[11px] text-blue-500 leading-relaxed">
|
||||
提示:通知效果受操作系统“专注模式”或“勿扰模式”影响。如果在设置开启后仍未收到通知,请检查系统的通知管理权限。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-white-switch :deep(.arco-switch-dot) {
|
||||
background-color: #7816ff;
|
||||
}
|
||||
.custom-white-switch :deep(.arco-switch-checked) {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 继承你之前的动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-in {
|
||||
animation: fadeIn 0.4s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
8
src/renderer/src/pages/setting/data/ModelData.ts
Normal file
8
src/renderer/src/pages/setting/data/ModelData.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IModelOption } from '../types/IModel'
|
||||
|
||||
export const modelOptions: IModelOption[] = [
|
||||
{ id: 'gpt-4o', name: 'GPT-4o', desc: '能力最强,适合复杂文学分析', tag: '推荐' },
|
||||
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5', desc: '响应速度极快', tag: '超快' },
|
||||
{ id: 'claude-3-5-sonnet-20240620', name: 'Claude 3.5', desc: '文笔细腻', tag: '文笔佳' },
|
||||
{ id: 'deepseek-v3.2', name: 'deepseekV3.2', desc: '适合快速生成内容', tag: '推荐' }
|
||||
]
|
||||
@@ -1,112 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, toRaw } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Remind, Right, SettingConfig, ShieldAdd, Wallet } from '@icon-park/vue-next'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { trpc } from '@renderer/lib/trpc'
|
||||
|
||||
// --- 类型定义 ---
|
||||
interface ModelOption {
|
||||
id: string
|
||||
name: string
|
||||
desc: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
interface ModelConfig {
|
||||
apiKey: string
|
||||
baseURL: string
|
||||
modelName: string
|
||||
temperature: number
|
||||
}
|
||||
|
||||
interface ModelForm {
|
||||
reading: ModelConfig // 统一使用 reading
|
||||
summary: ModelConfig
|
||||
}
|
||||
import ModelSetting from '@renderer/pages/setting/components/ModelSetting.vue'
|
||||
import NotFound from '@renderer/pages/setting/components/NotFound.vue'
|
||||
import NotificationSetting from '@renderer/pages/setting/components/NotificationSetting.vue'
|
||||
|
||||
// --- 状态管理 ---
|
||||
const activeMenu = ref('model')
|
||||
const activeTab = ref<'reading' | 'summary'>('reading') // 初始设为 reading
|
||||
const activeMenuItem = computed(() => {
|
||||
return menuItems.find((item) => item.key === activeMenu.value)?.component
|
||||
})
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'model', name: '大模型配置', icon: SettingConfig },
|
||||
{ key: 'model', name: '大模型配置', icon: SettingConfig, component: ModelSetting },
|
||||
{ key: 'account', name: '账号安全', icon: ShieldAdd },
|
||||
{ key: 'billing', name: '订阅与账单', icon: Wallet },
|
||||
{ key: 'notification', name: '通知设置', icon: Remind }
|
||||
{ key: 'notification', name: '通知设置', icon: Remind, component: NotificationSetting }
|
||||
]
|
||||
|
||||
const modelForm = reactive<ModelForm>({
|
||||
reading: {
|
||||
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
|
||||
baseURL: 'https://apis.iflow.cn/v1',
|
||||
modelName: 'deepseek-v3.2',
|
||||
temperature: 0.7
|
||||
},
|
||||
summary: {
|
||||
apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
|
||||
baseURL: 'https://apis.iflow.cn/v1',
|
||||
modelName: 'deepseek-v3.2',
|
||||
temperature: 0.3
|
||||
}
|
||||
})
|
||||
|
||||
const modelOptions: ModelOption[] = [
|
||||
{ id: 'gpt-4o', name: 'GPT-4o', desc: '能力最强,适合复杂文学分析', tag: '推荐' },
|
||||
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5', desc: '响应速度极快', tag: '超快' },
|
||||
{ id: 'claude-3-5-sonnet-20240620', name: 'Claude 3.5', desc: '文笔细腻', tag: '文笔佳' },
|
||||
{ id: 'deepseek-v3.2', name: 'deepseekV3.2', desc: '适合快速生成内容', tag: '推荐' }
|
||||
]
|
||||
|
||||
// --- 逻辑方法 ---
|
||||
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const savedConfig = (await trpc.config.getChatConfigs.query()) as any
|
||||
if (savedConfig) {
|
||||
// 这里的 Key 必须与后端存储的 chatModels.reading 对应
|
||||
if (savedConfig.reading) Object.assign(modelForm.reading, savedConfig.reading)
|
||||
if (savedConfig.summary) Object.assign(modelForm.summary, savedConfig.summary)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async (type: 'reading' | 'summary') => {
|
||||
try {
|
||||
// 关键点:使用 toRaw 转换,剥离 Vue 的响应式代理
|
||||
const configToSave = toRaw(modelForm[type])
|
||||
|
||||
// 打印检查一下,确保是普通对象而非 Proxy
|
||||
console.log('正在保存纯对象数据:', configToSave)
|
||||
|
||||
await trpc.config.saveChatConfig.mutate({
|
||||
type,
|
||||
config: configToSave
|
||||
})
|
||||
|
||||
Message.success({
|
||||
content: `${type === 'reading' ? '读书心得' : '摘要总结'}配置已安全保存`,
|
||||
showIcon: true
|
||||
})
|
||||
} catch (error) {
|
||||
Message.error('保存失败,无法克隆对象')
|
||||
console.error('tRPC 错误详情:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const selectModel = (modelId: string) => {
|
||||
modelForm[activeTab.value].modelName = modelId
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 h-full overflow-y-auto bg-[#FAFAFB] p-8 custom-scroll">
|
||||
<div class="max-w-5xl mx-auto flex gap-8">
|
||||
<div class="flex-1 h-full bg-[#FAFAFB] p-8 custom-scroll">
|
||||
<div class="max-w-5xl mx-auto h-full flex gap-8">
|
||||
<aside class="w-64 shrink-0">
|
||||
<div class="flex items-center space-x-2 mb-8 px-2">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
@@ -137,171 +53,16 @@ onMounted(() => {
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1">
|
||||
<div v-if="activeMenu === 'model'" class="space-y-6 animate-in">
|
||||
<a-tabs v-model:active-key="activeTab" type="capsule" class="custom-tabs">
|
||||
<a-tab-pane key="reading" title="读书心得模型" />
|
||||
<a-tab-pane key="summary" title="摘要总结模型" />
|
||||
</a-tabs>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div class="flex items-center space-x-2 mb-8">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">接口配置</h3>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="setting-label">Base URL (API 代理地址)</label>
|
||||
<a-input
|
||||
v-model="modelForm[activeTab].baseURL"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
class="custom-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="setting-label">API Key</label>
|
||||
<a-input-password
|
||||
v-model="modelForm[activeTab].apiKey"
|
||||
placeholder="sk-..."
|
||||
class="custom-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div class="flex items-center space-x-2 mb-6">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">模型名称 (modelName)</h3>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<a-input
|
||||
v-model="modelForm[activeTab].modelName"
|
||||
placeholder="手动输入模型 ID,如 gpt-4o"
|
||||
class="custom-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div
|
||||
v-for="m in modelOptions"
|
||||
:key="m.id"
|
||||
@click="selectModel(m.id)"
|
||||
:class="[
|
||||
'p-4 rounded-xl border-2 cursor-pointer transition-all relative overflow-hidden',
|
||||
modelForm[activeTab].modelName === m.id
|
||||
? 'border-[#7816ff] bg-purple-50/30'
|
||||
: 'border-slate-50 bg-slate-50/50 hover:border-slate-200'
|
||||
]"
|
||||
>
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<span class="text-xs font-bold text-slate-800">{{ m.name }}</span>
|
||||
<span
|
||||
v-if="m.tag"
|
||||
class="text-[9px] bg-[#7816ff] text-white px-1.5 py-0.5 rounded"
|
||||
>{{ m.tag }}</span
|
||||
>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 leading-relaxed">{{ m.desc }}</p>
|
||||
<div
|
||||
v-if="modelForm[activeTab].modelName === m.id"
|
||||
class="absolute -right-2 -bottom-2 text-[#7816ff]/10"
|
||||
>
|
||||
<SettingConfig theme="outline" size="48" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div class="flex items-center space-x-2 mb-8">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h3 class="text-sm font-bold text-slate-800">运行参数</h3>
|
||||
<p class="text-[11px] text-[#7816ff] leading-relaxed ml-4 italic">
|
||||
正在配置
|
||||
<span class="font-bold underline">{{
|
||||
activeTab === 'reading' ? '心得生成' : '文本总结'
|
||||
}}</span>
|
||||
专用模型。
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<label class="setting-label"
|
||||
>随机性 (Temperature): {{ modelForm[activeTab].temperature }}</label
|
||||
>
|
||||
<span class="text-[10px] text-slate-400">建议心得 0.7-0.8,总结 0.2-0.3</span>
|
||||
</div>
|
||||
<a-slider
|
||||
v-model="modelForm[activeTab].temperature"
|
||||
:min="0"
|
||||
:max="1.2"
|
||||
:step="0.1"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end pt-4 border-t border-slate-50">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="rounded-lg bg-[#7816ff] border-none px-8 font-bold"
|
||||
@click="handleSave(activeTab)"
|
||||
>
|
||||
保存 {{ activeTab === 'reading' ? '心得' : '总结' }} 配置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<template v-if="activeMenuItem">
|
||||
<component :is="activeMenuItem"></component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NotFound />
|
||||
</template>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 样式部分保持不变... */
|
||||
.setting-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0 4px;
|
||||
}
|
||||
:deep(.custom-input) {
|
||||
background-color: #fcfcfd !important;
|
||||
border: 1px solid #f1f5f9 !important;
|
||||
border-radius: 12px !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
:deep(.custom-tabs .arco-tabs-nav-capsule) {
|
||||
background-color: #f1f5f9;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
:deep(.custom-tabs .arco-tabs-nav-capsule-light) {
|
||||
background-color: #fff;
|
||||
color: #7816ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.custom-scroll::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scroll::-webkit-scrollbar-thumb {
|
||||
background: #e5e7eb;
|
||||
border-radius: 10px;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.animate-in {
|
||||
animation: fadeIn 0.4s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Right } from '@icon-park/vue-next'
|
||||
|
||||
defineProps<{
|
||||
menuItems: Array<{ key: string; name: string; icon: any }>
|
||||
activeMenu: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:activeMenu'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 h-full overflow-y-auto bg-[#FAFAFB] p-8 custom-scroll">
|
||||
<div class="max-w-5xl mx-auto flex gap-8">
|
||||
<aside class="w-64 shrink-0">
|
||||
<div class="flex items-center space-x-2 mb-8 px-2">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h2 class="text-base font-bold text-slate-800">设置中心</h2>
|
||||
</div>
|
||||
<nav class="space-y-1">
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
@click="emit('update:activeMenu', item.key)"
|
||||
:class="[
|
||||
'flex items-center justify-between px-4 py-3 rounded-xl cursor-pointer transition-all duration-300',
|
||||
activeMenu === item.key
|
||||
? 'bg-white shadow-sm text-[#7816ff] font-bold border border-slate-100'
|
||||
: 'text-slate-500 hover:bg-white/60 hover:text-slate-700'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<component
|
||||
:is="item.icon"
|
||||
theme="outline"
|
||||
size="18"
|
||||
:fill="activeMenu === item.key ? '#7816ff' : '#94a3b8'"
|
||||
/>
|
||||
<span class="text-[13px]">{{ item.name }}</span>
|
||||
</div>
|
||||
<right v-if="activeMenu === item.key" theme="outline" size="14" />
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="flex-1">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
19
src/renderer/src/pages/setting/types/IModel.ts
Normal file
19
src/renderer/src/pages/setting/types/IModel.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ILLMConfig } from '@shared/types/IConfig'
|
||||
|
||||
/**
|
||||
* 模型类型定义
|
||||
* */
|
||||
export interface IModelOption {
|
||||
id: string
|
||||
name: string
|
||||
desc: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义模型表单类型
|
||||
* */
|
||||
export interface ModelForm {
|
||||
reading: ILLMConfig['config']
|
||||
summary: ILLMConfig['config']
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Message } from '@arco-design/web-vue'
|
||||
import { useRouterHook } from '@renderer/hooks/useRouterHook'
|
||||
import { Refresh, Send } from '@icon-park/vue-next'
|
||||
import { trpc } from '@renderer/lib/trpc'
|
||||
import { ReadingReflectionsTask } from '@shared/types/reflections'
|
||||
import { IReadingReflectionsTask } from '@shared/types/IReadingReflectionTask'
|
||||
import { eventBus } from '@renderer/lib/eventBus'
|
||||
|
||||
const { go } = useRouterHook()
|
||||
@@ -17,13 +17,14 @@ const form = reactive({
|
||||
quantity: 1,
|
||||
prompt: '',
|
||||
wordCount: 500
|
||||
} as ReadingReflectionsTask)
|
||||
} as IReadingReflectionsTask)
|
||||
|
||||
const occupationOptions = [
|
||||
{ label: '学生', value: 'student' },
|
||||
{ label: '职场白领', value: 'professional' },
|
||||
{ label: '学者/研究员', value: 'scholar' },
|
||||
{ label: '自由职业者', value: 'freelancer' }
|
||||
{ label: '自由职业者', value: 'freelancer' },
|
||||
{ label: '教师', value: 'teacher' }
|
||||
]
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@@ -60,18 +61,12 @@ const handleReset = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 overflow-y-auto bg-[#FAFAFB] p-6 custom-scroll">
|
||||
<div class="flex items-center justify-between mb-6 mx-auto">
|
||||
<div class="flex-1 p-6">
|
||||
<div class="flex-1 bg-white rounded-xl border border-slate-100 shadow-sm p-8 custom-scroll">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
|
||||
<h2 class="text-base font-bold text-slate-800">创建读书心得任务</h2>
|
||||
</div>
|
||||
<a-button type="text" size="small" class="text-slate-400" @click="go('/task/list')">
|
||||
返回列表
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="x-auto bg-white rounded-xl border border-slate-100 shadow-sm p-8">
|
||||
<a-form :model="form" layout="vertical" @submit="handleSubmit">
|
||||
<a-form-item field="bookName" label="书籍名称" required>
|
||||
<template #label><span class="text-xs font-bold text-slate-700">书籍名称</span></template>
|
||||
|
||||
Reference in New Issue
Block a user