feat: 完善对话功能,自动设置默认模型账户并优化界面布局

This commit is contained in:
DEV_DSW
2026-04-14 20:54:54 +08:00
parent c61e41049f
commit fbec7088c6
11 changed files with 39 additions and 50 deletions

View File

@@ -550,7 +550,10 @@ contextBridge.exposeInMainWorld('api', {
#### 4.5.2 Chat 页面移除模型选择器 #### 4.5.2 Chat 页面移除模型选择器
-`ChatBox.vue` 中移除 `ModelSelector` 组件及其引用; -`ChatBox.vue` 中移除 `ModelSelector` 组件及其引用;
- 对话功能直接使用 provider 管理中「设置为默认」的模型账户; - 对话功能直接使用 provider 管理中「设置为默认」的模型账户;
- 若未设置默认模型,提示用户前往模型管理页面配置。 - 对话页面 `onMounted` 时主动调用 `providerStore.init()` 加载模型配置。
#### 4.5.3 自动默认模型兜底
`providerStore.init()` 初始化后发现用户已存在 provider accounts 但未设置 `defaultAccountId` 时,自动将第一个 account 设为默认,确保用户无需手动点击"设为默认"即可直接开始对话。若用户没有任何账户,则保持原有提示行为。
--- ---

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn"
/> />
</head> </head>
<body> <body>

View File

@@ -5,17 +5,10 @@ export interface taskCenterItem {
desc: string, desc: string,
id: string, id: string,
icon: string, icon: string,
type: 'sale' | 'close' | 'open' | 'channel' type?: 'channel'
} }
export const taskCenterList: taskCenterItem[] = [ export const taskCenterList: taskCenterItem[] = [
{
title: '每日销售数据',
desc: '分析用于销售渠道每日数据汇总及简要展示',
id: uuidv4(),
icon: '销',
type: 'sale'
},
{ {
title: '一键打开各渠道', title: '一键打开各渠道',
desc: '人工账号登录,为自动化操作做好准备', desc: '人工账号登录,为自动化操作做好准备',
@@ -24,17 +17,9 @@ export const taskCenterList: taskCenterItem[] = [
type: 'channel' type: 'channel'
}, },
{ {
title: '渠道房型', title: '渠道房型',
desc: '关闭销售渠道下的指定房型', desc: '销售渠道下的指定房型,管理开关房型',
id: uuidv4(), id: uuidv4(),
icon: '', icon: ''
type: 'close'
},
{
title: '开渠道房型',
desc: '开启销售渠道下的指定房型',
id: uuidv4(),
icon: '开',
type: 'open'
}, },
] ]

View File

@@ -1,12 +1,8 @@
<template> <template>
<div class="flex flex-col h-full py-6 px-6 overflow-hidden"> <div class="flex flex-col flex-1 py-6 px-6 overflow-hidden">
<!-- 空状态 --> <!-- 空状态 -->
<template v-if="isEmpty"> <template v-if="isEmpty">
<ChatEmpty @click-tag="onQuickTag"> <ChatEmpty @click-tag="onQuickTag" />
<template #task-center>
<TaskCenter />
</template>
</ChatEmpty>
</template> </template>
<!-- 消息列表 --> <!-- 消息列表 -->
@@ -35,11 +31,6 @@
<!-- 输入区 --> <!-- 输入区 -->
<div class="flex flex-col gap-3 mt-4"> <div class="flex flex-col gap-3 mt-4">
<div class="flex items-center gap-3">
<div class="inline-flex items-center justify-center w-[108px] px-3 py-1.5 rounded-2xl border border-[#E5E8EE] text-[13px] text-[#333] cursor-pointer hover:bg-[#2B7FFF] hover:text-[#fff] hover:border-[#2B7FFF]">
智能问数
</div>
</div>
<ChatInput <ChatInput
v-model="inputMessage" v-model="inputMessage"
:is-sending="chatStore.sending" :is-sending="chatStore.sending"
@@ -69,7 +60,6 @@ import ChatEmpty from './components/chat/ChatEmpty.vue'
import ChatErrorBar from './components/chat/ChatErrorBar.vue' import ChatErrorBar from './components/chat/ChatErrorBar.vue'
import ChatTypingIndicator from './components/chat/ChatTypingIndicator.vue' import ChatTypingIndicator from './components/chat/ChatTypingIndicator.vue'
import ChatActivityIndicator from './components/chat/ChatActivityIndicator.vue' import ChatActivityIndicator from './components/chat/ChatActivityIndicator.vue'
import TaskCenter from './TaskCenter.vue'
const chatStore = useChatStore() const chatStore = useChatStore()
const inputMessage = ref('') const inputMessage = ref('')

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="h-full"> <div :class="['h-full transition-all duration-300', sidebarCollapsed ? 'w-16' : 'w-50']">
<aside :class="['h-full box-border flex flex-col transition-all duration-300', sidebarCollapsed ? 'w-16' : 'w-50']"> <aside class="h-full box-border flex flex-col w-full">
<div class="flex items-center justify-center m-2"> <div class="flex items-center justify-center m-2">
<img v-if="!sidebarCollapsed" class="w-10 h-10 rounded-md" src="@assets/images/login/white_logo.png" /> <img v-if="!sidebarCollapsed" class="w-10 h-10 rounded-md" src="@assets/images/login/white_logo.png" />
<div v-if="!sidebarCollapsed" class="font-bold text-gray-80">YINIAN</div> <div v-if="!sidebarCollapsed" class="font-bold text-gray-80">YINIAN</div>

View File

@@ -1,10 +1,7 @@
<template> <template>
<div class="flex-1 pb-6"> <div class="pl-6 pr-6 pb-6">
<div class="flex justify-between items-center py-4"> <div class="flex justify-between items-center pb-4">
<h3 class="text-base font-semibold">任务中心</h3> <h3 class="text-base font-semibold">任务中心</h3>
<!-- <a class="text-[#3b82f6] text-[13px] cursor-pointer">
编辑
</a> -->
</div> </div>
<div class="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1"> <div class="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1">

View File

@@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="mt-auto"> <div class="mt-auto">
<slot name="task-center" /> <slot />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -46,7 +46,7 @@
<!-- Markdown text --> <!-- Markdown text -->
<div <div
v-if="markdownHtml" v-if="markdownHtml"
class="bg-[#f7f9fc] rounded-md px-3 py-2 prose prose-sm max-w-none" class="bg-[#f7f9fc] rounded-md px-3 py-2 prose prose-sm"
v-html="markdownHtml" v-html="markdownHtml"
/> />

View File

@@ -1,9 +1,10 @@
<template> <template>
<layout> <layout>
<div class="flex h-full w-full flex-col md:flex-row"> <div class="flex h-full w-full flex-col md:flex-row">
<ChatHistory class="flex-none w-50" @new-chat="handleNewChat" @select-chat="handleSelectChat" /> <ChatHistory class="flex-none" @new-chat="handleNewChat" @select-chat="handleSelectChat" />
<div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl"> <div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl flex flex-col">
<ChatBox /> <ChatBox class="flex-1" />
<TaskCenter />
</div> </div>
<TaskList /> <TaskList />
</div> </div>
@@ -18,20 +19,23 @@ import TaskList from '@src/components/TaskList/index.vue'
import TaskOperationDialog from './components/TaskOperationDialog.vue' import TaskOperationDialog from './components/TaskOperationDialog.vue'
import ChatHistory from './ChatHistory.vue' import ChatHistory from './ChatHistory.vue'
import ChatBox from './ChatBox.vue' import ChatBox from './ChatBox.vue'
import TaskCenter from './TaskCenter.vue'
import { useChatStore } from '@store/chat' import { useChatStore } from '@store/chat'
import { useProviderStore } from '@store/providers'
import emitter from '@src/utils/emitter' import emitter from '@src/utils/emitter'
const chatStore = useChatStore() const chatStore = useChatStore()
const providerStore = useProviderStore()
const taskOperationDialog = ref() const taskOperationDialog = ref()
onMounted(() => { onMounted(async () => {
await providerStore.init()
chatStore.loadSessions() chatStore.loadSessions()
chatStore.initConnection() chatStore.subscribeToGateway()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
chatStore.cleanupEmptySession() chatStore.cleanupEmptySession()
chatStore.closeConnection()
}) })
const handleNewChat = () => { const handleNewChat = () => {

View File

@@ -323,13 +323,14 @@ export async function stageBuffer(base64: string, fileName: string, mimeType: st
if (result && result.stagedPath) return result if (result && result.stagedPath) return result
} catch { /* fallback */ } } catch { /* fallback */ }
const dataUrl = `data:${mimeType};base64,${base64}`
return { return {
id: crypto.randomUUID(), id: crypto.randomUUID(),
fileName, fileName,
mimeType, mimeType,
fileSize: Math.ceil(base64.length * 0.75), fileSize: Math.ceil(base64.length * 0.75),
stagedPath: '', stagedPath: dataUrl,
preview: mimeType.startsWith('image/') ? `data:${mimeType};base64,${base64}` : null, preview: mimeType.startsWith('image/') ? dataUrl : null,
} }
} }

View File

@@ -19,6 +19,15 @@ export const useProviderStore = defineStore('providers', () => {
const init = async () => { const init = async () => {
await refreshProviderSnapshot(); await refreshProviderSnapshot();
// 自动兜底:如果有账户但没设默认,自动将第一个设为默认
if (accounts.value.length > 0 && !defaultAccountId.value) {
try {
await setDefaultAccount(accounts.value[0].id);
} catch (err) {
console.error('Auto-set default account failed:', err);
}
}
}; };
const refreshProviderSnapshot = async () => { const refreshProviderSnapshot = async () => {