refactor(conversation): 多智能体从全局模式重构为对话不可变类型属性
- 废除「多智能体模式」全局状态,多智能体改为对话创建时确定的不可变属性 - URL 统一为 /<conv_id>,旧 /multiagent/* 由前端 bootstrap 重定向到裸路径 - 前端拆分为三个语义单一状态:currentConversationType(当前对话类型)、 newConversationType(空对话待创建类型)、sidebarConversationType(列表过滤器) - 输入栏底行 + 右侧新增对话类型选择器,发送时按类型选择创建接口 - 侧边栏新增类型切换 UI:平铺模式分段控件、分组模式单字切换按钮, 列表区域左右滑动过渡动画 - 清理跨文件主题补丁:_overlays.scss 中覆盖输入栏/对话区/侧边栏/右侧面板的 dark/light 变体(含 !important)全部删除(-363 行),按组件回迁到 _composer/_chat-area/_conversation/_right-panel 各自文件内的主题变体块 - AGENTS.md §11 同步重写为新模型并新增 11.0 前端对话类型模型说明; §5.5 新增第 10 条「主题变体必须与基础定义同文件(禁止跨文件补丁覆盖)」
This commit is contained in:
parent
59a9f6ab62
commit
1c339d2e6f
15
AGENTS.md
15
AGENTS.md
@ -198,6 +198,7 @@
|
||||
7. **带文字容器固定高度**:所有含内部文字的选项 / 按钮 / 标签 / 容器固定高度,禁止因文字多少被撑大撑高;过长文字用省略号或内部滚动处理。
|
||||
8. **窗口设最大尺寸 + 内部滚动**:所有弹窗 / 面板 / 列表设置 `max-height` / `max-width`,超出由内部容器滚动,不顶大整个窗口;滚动条要么隐藏,要么做样式适配,不暴露原生粗滚动条。
|
||||
9. **实体面板禁止半透明**:实体 UI 容器(对话区 / 侧栏 / 下拉/二级菜单 / 抽屉 / 对话框本体 / 状态条 / tooltip / 输入栏)背景必须不透明,且不加 `backdrop-filter` 磨砂。唯一例外:遮罩层 scrim(`--overlay-scrim`,`position:fixed;inset:0`)和刻意玻璃质感装饰保留半透明。
|
||||
10. **主题变体必须与基础定义同文件(禁止跨文件补丁覆盖)**:组件的 dark/light/classic 主题变体写在该组件自己的样式文件里(文件内统一的 `body[data-theme='…'] { … }` 块),禁止把某组件的主题样式写到别的文件去覆盖。新增主题样式前先确认该组件基础定义所在文件;发现存量跨文件覆盖应顺手回迁并删除原覆盖。`!important` 只准用于遮盖第三方库样式,禁止用于压过项目自有定义(出现这种需求说明应该合并定义,而不是加 `!important`)。审计脚本:`python3 cache/style_audit.py` + `python3 cache/theme_diff.py`。
|
||||
|
||||
## 5.6) 工具结果显示规范(强制)
|
||||
|
||||
@ -362,9 +363,19 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
|
||||
- 开启后写入:`logs/approval_agent/`
|
||||
- 记录以累积 `messages` 为主,便于对齐主/子智能体会话格式
|
||||
|
||||
## 11) 多智能体模式(multi-agent mode)
|
||||
## 11) 多智能体对话类型(multi-agent conversation type)
|
||||
|
||||
> 对话级开关 `metadata.multi_agent_mode = true` 启用,URL 入口 `/multiagent/<conv_id>`,数据目录 `~/.astrion/astrion/host/mutiagents/`(保留原拼写)。完整设计与踩坑记录见项目记忆 `multi_agent_mode_design`,本节只列 Agent 改代码时必须知道的硬约束。
|
||||
> **2026-08 重构**:已废除「多智能体模式」全局状态,多智能体是**对话的不可变属性**(`metadata.multi_agent_mode = true`,创建时确定、不可变)。URL 统一为 `/<conv_id>`(旧 `/multiagent/*` 由前端 bootstrap 重定向到裸路径)。数据目录 `~/.astrion/astrion/host/mutiagents/`(保留原拼写)。完整设计与踩坑记录见项目记忆 `multi_agent_mode_design`,重构方案见 `docs/conversation_type_unification_plan.md`,本节只列 Agent 改代码时必须知道的硬约束。
|
||||
|
||||
### 11.0 前端对话类型模型(重构后)
|
||||
|
||||
- 三个语义单一的状态,**互不复用**:
|
||||
- `currentConversationType`(`static/src/app/state.ts`):当前已打开对话的类型(`'normal' | 'multi_agent' | null`),由 `enterConversation` 从对话 metadata 恢复(`bootstrap.ts`),空对话为 `null`。
|
||||
- `newConversationType`(同上):空对话时输入栏待创建类型(`'agent' | 'multi_agent'`),localStorage 持久化(`agents_new_conversation_type`),由输入栏底行 `+` 右侧的类型选择器修改(`InputComposer.vue` 的 `agent-type-switcher`)。
|
||||
- `sidebarConversationType`(`stores/conversation.ts`):侧边栏列表过滤器(`'normal' | 'multi_agent'`),localStorage 持久化(`agents_sidebar_conversation_type`),驱动列表/搜索的 `multi_agent_mode=0|1` 请求参数与侧边栏新建按钮的对话类型。
|
||||
- `stores/conversation.ts` 的 `multiAgentMode` 字段保留,但语义已变为「当前对话是否多智能体」,仅由 `enterConversation`(meta 落地)与空对话 watcher(复位 false)写入——`stores/subAgent.ts` 的读取点依赖它,不得删除。
|
||||
- 侧边栏类型切换 UI:平铺模式在「搜索对话」下方显示分段控件;分组模式在工作区标题行新建/更多按钮左侧显示单字切换按钮;列表区域用 `<Transition mode="out-in">` 做左右滑动动画(切多智能体向左滑出,切回反向)。
|
||||
- 发送消息创建对话时按 `newConversationType` 选择 `/api/conversations` 或 `/api/multiagent/conversations`(`message/send.ts`);侧边栏新建按 `sidebarConversationType` 选择(`conversation/action.ts`)。
|
||||
|
||||
### 11.1 角色与实例
|
||||
|
||||
|
||||
@ -95,6 +95,7 @@
|
||||
@reveal-workspace="revealHostWorkspace"
|
||||
@rename-workspace="handleRenameWorkspaceFromSidebar"
|
||||
@pin-workspace="handlePinWorkspaceFromSidebar"
|
||||
@conversation-type-change="handleSidebarConversationTypeChange"
|
||||
/>
|
||||
|
||||
<main
|
||||
@ -302,7 +303,9 @@
|
||||
:goal-mode-armed="goalModeArmed"
|
||||
:goal-running="goalRunning"
|
||||
:goal-progress="goalProgress"
|
||||
:multi-agent-mode="multiAgentMode"
|
||||
:current-conversation-type="currentConversationType"
|
||||
:new-conversation-type="newConversationType"
|
||||
:agent-type-menu-open="agentTypeMenuOpen"
|
||||
:main-chat-idle="mainChatIdle"
|
||||
:active-sub-agent-count="activeSubAgentCount"
|
||||
:avatar-status="showStatusAvatar && messages.length > 0 ? avatarStatus : null"
|
||||
@ -345,7 +348,8 @@
|
||||
@delete-runtime-message="handleDeleteRuntimeMessage"
|
||||
@composer-height-change="handleComposerHeightChange"
|
||||
@toggle-goal-mode="handleToggleGoalMode"
|
||||
@toggle-agent-mode="handleToggleAgentMode"
|
||||
@toggle-agent-type-menu="handleToggleAgentTypeMenu"
|
||||
@select-new-conversation-type="handleSelectNewConversationType"
|
||||
@open-goal-dialog="goalDialogOpen = true"
|
||||
@open-git-changes-panel="openGitChangesPanel"
|
||||
/>
|
||||
@ -763,6 +767,7 @@
|
||||
@reveal-workspace="revealHostWorkspace"
|
||||
@rename-workspace="handleRenameWorkspaceFromSidebar"
|
||||
@pin-workspace="handlePinWorkspaceFromSidebar"
|
||||
@conversation-type-change="handleSidebarConversationTypeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { debugLog, traceLog } from '../common';
|
||||
import { usePersonalizationStore } from '../../../stores/personalization';
|
||||
import { persistNewConversationType } from '../../state';
|
||||
import {
|
||||
|
||||
} from './shared';
|
||||
@ -71,7 +72,17 @@ export const actionMethods = {
|
||||
console.warn('读取新建对话按钮行为设置失败,按默认跳转处理:', error);
|
||||
}
|
||||
if (newChatBehavior !== 'blank') {
|
||||
const target = this.multiAgentMode ? '/multiagent/new' : '/new';
|
||||
// 跳转 /new 前把侧边栏过滤器类型同步给输入栏选择器:
|
||||
// 看着哪类对话列表点新建,默认就创建哪类(选择器仍可手动改)。
|
||||
try {
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
this.newConversationType =
|
||||
useConversationStore().sidebarConversationType === 'multi_agent' ? 'multi_agent' : 'agent';
|
||||
persistNewConversationType(this.newConversationType);
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
const target = '/new';
|
||||
const normalize = (p) => String(p || '/').replace(/^\/+|\/+$/g, '');
|
||||
if (normalize(window.location.pathname) !== normalize(target)) {
|
||||
window.location.href = target;
|
||||
@ -144,7 +155,9 @@ export const actionMethods = {
|
||||
this.versioningInitializingBackupToastId = backupToastId;
|
||||
}
|
||||
|
||||
const isMultiAgent = Boolean(this.multiAgentMode);
|
||||
// 侧边栏新建:类型跟随侧边栏过滤器(看着哪类列表就建哪类对话)
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
const isMultiAgent = useConversationStore().sidebarConversationType === 'multi_agent';
|
||||
const createUrl = isMultiAgent ? '/api/multiagent/conversations' : '/api/conversations';
|
||||
// reasoning_effort 随创建权威写入新对话 meta(此处 this.reasoningEffort 已被上方
|
||||
// 重置为个性化默认值,语义与“创建空对话写入默认值一次”一致)
|
||||
@ -278,10 +291,18 @@ export const actionMethods = {
|
||||
if (!workspaceId) return;
|
||||
debugLog('在指定工作区创建新对话:', workspaceId);
|
||||
try {
|
||||
const response = await fetch('/api/conversations', {
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
const conversationStore = useConversationStore();
|
||||
// 项目旁新建:类型跟随侧边栏过滤器(看着哪类列表就建哪类对话)
|
||||
const isMultiAgent = conversationStore.sidebarConversationType === 'multi_agent';
|
||||
const createUrl = isMultiAgent ? '/api/multiagent/conversations' : '/api/conversations';
|
||||
const createPayload = isMultiAgent
|
||||
? { workspace_id: workspaceId, preserve_mode: true }
|
||||
: { workspace_id: workspaceId };
|
||||
const response = await fetch(createUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workspace_id: workspaceId })
|
||||
body: JSON.stringify(createPayload)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { debugLog, traceLog } from '../common';
|
||||
import { useQuickDockStore } from '../../../stores/quickDock';
|
||||
import { useConversationStore } from '../../../stores/conversation';
|
||||
|
||||
/**
|
||||
* 统一加载协议(方案 B):进入对话的单一入口。
|
||||
@ -17,7 +18,6 @@ export const bootstrapMethods = {
|
||||
* @param options.source 'refresh' | 'sidebar'(仅用于日志追踪)
|
||||
* @param options.workspaceId 目标工作区(host/docker 多工作区场景)
|
||||
* @param options.urlMode 'push' | 'replace' | 'none'(缺省 push)
|
||||
* @param options.urlPrefix 显式 URL 前缀(缺省按 multiAgentMode 自适应)
|
||||
* @param options.preserveListPosition 保持侧边栏列表位置不置顶
|
||||
* @param options.resetUI 渲染前 resetAllStates(sidebar 切换保留「清空→填入」语义)
|
||||
*/
|
||||
@ -26,7 +26,6 @@ export const bootstrapMethods = {
|
||||
source = 'sidebar',
|
||||
workspaceId = '',
|
||||
urlMode = 'push',
|
||||
urlPrefix = '',
|
||||
preserveListPosition = false,
|
||||
resetUI = false
|
||||
} = options;
|
||||
@ -63,9 +62,10 @@ export const bootstrapMethods = {
|
||||
}
|
||||
// 恢复会话级推理强度档位(null = 默认,不传参)
|
||||
this.reasoningEffort = typeof meta.reasoning_effort === 'string' ? meta.reasoning_effort : null;
|
||||
if (typeof meta.multi_agent_mode === 'boolean') {
|
||||
this.multiAgentMode = meta.multi_agent_mode;
|
||||
}
|
||||
// 对话类型从 metadata 落地(创建时确定、不可变)
|
||||
this.currentConversationType = meta.multi_agent_mode === true ? 'multi_agent' : 'normal';
|
||||
// conversationStore.multiAgentMode 语义 = 「当前对话是否多智能体」(subAgent store 读取)
|
||||
useConversationStore().$patch({ multiAgentMode: meta.multi_agent_mode === true });
|
||||
|
||||
// 2. 当前对话状态(skip 标记阻止 currentConversationId watch 重复拉历史)
|
||||
this.skipConversationHistoryReload = true;
|
||||
@ -76,12 +76,12 @@ export const bootstrapMethods = {
|
||||
this.promoteConversationToTop(normalizedId);
|
||||
}
|
||||
if (urlMode !== 'none') {
|
||||
const prefix = urlPrefix || (this.multiAgentMode ? '/multiagent/' : '/');
|
||||
// 对话类型不再是路由概念,统一裸路径 /<id>
|
||||
const stateMethod = urlMode === 'replace' ? 'replaceState' : 'pushState';
|
||||
history[stateMethod](
|
||||
{ conversationId: normalizedId },
|
||||
'',
|
||||
`${prefix}${this.stripConversationPrefix(normalizedId)}`
|
||||
`/${this.stripConversationPrefix(normalizedId)}`
|
||||
);
|
||||
}
|
||||
this.skipConversationLoadedEvent = true;
|
||||
|
||||
@ -6,6 +6,32 @@ import {
|
||||
} from './shared';
|
||||
|
||||
export const loadMethods = {
|
||||
/**
|
||||
* 侧边栏对话类型(普通/多智能体)切换:
|
||||
* 重置平铺列表分页并重新加载,同时刷新所有已展开的工作区分组。
|
||||
* 由 ConversationSidebar 的 conversation-type-change 事件触发;
|
||||
* store 中的 sidebarConversationType 已由组件先行写入。
|
||||
*/
|
||||
async handleSidebarConversationTypeChange(_type?: 'normal' | 'multi_agent') {
|
||||
this.conversationsOffset = 0;
|
||||
this.hasMoreConversations = false;
|
||||
await this.loadConversationsList();
|
||||
try {
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
const conversationStore = useConversationStore();
|
||||
const groups = Array.isArray(conversationStore.workspaceGroups)
|
||||
? conversationStore.workspaceGroups
|
||||
: [];
|
||||
for (const group of groups) {
|
||||
if (group && group.workspaceId) {
|
||||
await this.loadWorkspaceConversations(group.workspaceId, { reset: true });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新工作区分组对话失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async loadConversationsList() {
|
||||
const queryOffset = this.conversationsOffset;
|
||||
const queryLimit = this.conversationsLimit;
|
||||
@ -14,8 +40,9 @@ export const loadMethods = {
|
||||
const requestSeq = ++this.conversationListRequestSeq;
|
||||
this.conversationsLoading = true;
|
||||
try {
|
||||
// 多智能体模式下只加载多智能体对话记录;常规模式排除多智能体对话
|
||||
const maParam = this.multiAgentMode ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
// 列表过滤由侧边栏类型过滤器(普通/多智能体)决定
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
const maParam = useConversationStore().sidebarConversationType === 'multi_agent' ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const response = await fetch(`/api/conversations?limit=${queryLimit}&offset=${queryOffset}${maParam}`);
|
||||
const data = await response.json();
|
||||
|
||||
@ -255,7 +282,8 @@ export const loadMethods = {
|
||||
if (group.loading || group.loadingMore) return;
|
||||
group.loading = true;
|
||||
try {
|
||||
const maParam = this.multiAgentMode ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const { useConversationStore: useConvStore } = await import('../../../stores/conversation');
|
||||
const maParam = useConvStore().sidebarConversationType === 'multi_agent' ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const response = await fetch(`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.limit}&offset=${group.offset}${maParam}`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
|
||||
@ -240,7 +240,8 @@ export const sendMethods = {
|
||||
this.versioningInitializingBackupToastId = backupToastId;
|
||||
}
|
||||
|
||||
const isMultiAgent = Boolean(this.multiAgentMode);
|
||||
// 首条消息创建:类型来自输入栏「智能体/多智能体」选择器(newConversationType)
|
||||
const isMultiAgent = this.newConversationType === 'multi_agent';
|
||||
const createUrl = isMultiAgent ? '/api/multiagent/conversations' : '/api/conversations';
|
||||
// reasoning_effort 随创建权威写入新对话 meta:/new 页面用户可能已手动调整档位,
|
||||
// 不能依赖后端 terminal 值(会被 status 轮询的 prefs 应用覆盖)
|
||||
@ -259,6 +260,14 @@ export const sendMethods = {
|
||||
throw new Error(createResult?.message || createResult?.error || '创建对话失败');
|
||||
}
|
||||
targetConversationId = createResult.conversation_id;
|
||||
// 创建即定型:落地对话类型(与后端 metadata 保持一致)
|
||||
this.currentConversationType = isMultiAgent ? 'multi_agent' : 'normal';
|
||||
try {
|
||||
const { useConversationStore } = await import('../../../stores/conversation');
|
||||
useConversationStore().$patch({ multiAgentMode: isMultiAgent });
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
this.skipConversationHistoryReload = true;
|
||||
this.currentConversationId = targetConversationId;
|
||||
this.currentConversationTitle = '新对话';
|
||||
@ -293,8 +302,8 @@ export const sendMethods = {
|
||||
}
|
||||
|
||||
const pathFragment = this.stripConversationPrefix(targetConversationId);
|
||||
const urlPrefix = this.multiAgentMode ? '/multiagent/' : '/';
|
||||
history.replaceState({ conversationId: targetConversationId }, '', `${urlPrefix}${pathFragment}`);
|
||||
// 对话类型不再是路由概念,统一裸路径 /<id>
|
||||
history.replaceState({ conversationId: targetConversationId }, '', `/${pathFragment}`);
|
||||
} catch (error) {
|
||||
this.uiPushToast({
|
||||
title: '发送失败',
|
||||
|
||||
@ -57,7 +57,9 @@ export const searchMethods = {
|
||||
this.searchResults = [];
|
||||
this.searchGroups = [];
|
||||
|
||||
const maParam = this.multiAgentMode ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
// 搜索范围跟随侧边栏类型过滤器(普通/多智能体)
|
||||
const { useConversationStore } = await import('../../stores/conversation');
|
||||
const maParam = useConversationStore().sidebarConversationType === 'multi_agent' ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const grouped = this.isGroupedSidebarSearch();
|
||||
const url = grouped
|
||||
? `/api/conversations/search?q=${encodeURIComponent(trimmed)}&limit=${SEARCH_GROUP_LIMIT}&all_workspaces=1${maParam}`
|
||||
|
||||
@ -134,8 +134,8 @@ export const syncMethods = {
|
||||
|
||||
const pathFragment = this.stripConversationPrefix(data.conversation_id);
|
||||
const currentPath = window.location.pathname.replace(/^\/+/, '');
|
||||
const urlPrefix = this.multiAgentMode ? '/multiagent/' : '/';
|
||||
const targetPath = `${urlPrefix}${pathFragment}`;
|
||||
// 对话类型不再是路由概念,统一裸路径 /<id>
|
||||
const targetPath = `/${pathFragment}`;
|
||||
if (data.created) {
|
||||
history.pushState({ conversationId: data.conversation_id }, '', targetPath);
|
||||
} else if (currentPath !== pathFragment) {
|
||||
|
||||
@ -56,7 +56,7 @@ export const menuMethods = {
|
||||
}
|
||||
},
|
||||
handleClickOutsideQuickMenu(event) {
|
||||
if (!this.quickMenuOpen && !this.permissionMenuOpen) {
|
||||
if (!this.quickMenuOpen && !this.permissionMenuOpen && !this.agentTypeMenuOpen) {
|
||||
return;
|
||||
}
|
||||
const shell =
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { debugLog } from '../common';
|
||||
import { persistNewConversationType } from '../../state';
|
||||
import { usePolicyStore } from '../../../stores/policy';
|
||||
import { useModelStore } from '../../../stores/model';
|
||||
import { usePersonalizationStore } from '../../../stores/personalization';
|
||||
@ -54,6 +55,7 @@ export const modeMethods = {
|
||||
this.inputCloseMenus();
|
||||
this.modeMenuOpen = false;
|
||||
this.modelMenuOpen = false;
|
||||
this.agentTypeMenuOpen = false;
|
||||
},
|
||||
handleToggleGoalMode() {
|
||||
if (!this.isConnected) {
|
||||
@ -75,14 +77,26 @@ export const modeMethods = {
|
||||
this.modeMenuOpen = false;
|
||||
this.modelMenuOpen = false;
|
||||
},
|
||||
handleToggleAgentMode() {
|
||||
if (!this.isConnected) {
|
||||
// 输入栏「智能体/多智能体」类型选择器:空对话态可选(写 newConversationType),
|
||||
// 已有对话为禁用展示态(类型创建时确定、不可变)。
|
||||
handleToggleAgentTypeMenu() {
|
||||
if (!this.isConnected || this.currentConversationId) {
|
||||
return;
|
||||
}
|
||||
this.inputCloseMenus();
|
||||
// 多智能体模式 → 传统模式;传统模式 → 多智能体模式
|
||||
const target = this.multiAgentMode ? '/new' : '/multiagent/new';
|
||||
window.location.href = target;
|
||||
const next = !this.agentTypeMenuOpen;
|
||||
this.agentTypeMenuOpen = next;
|
||||
if (next) {
|
||||
this.permissionMenuOpen = false;
|
||||
this.modelMenuOpen = false;
|
||||
this.modeMenuOpen = false;
|
||||
this.inputCloseMenus?.();
|
||||
}
|
||||
},
|
||||
handleSelectNewConversationType(type) {
|
||||
const normalized = type === 'multi_agent' ? 'multi_agent' : 'agent';
|
||||
this.newConversationType = normalized;
|
||||
persistNewConversationType(normalized);
|
||||
this.agentTypeMenuOpen = false;
|
||||
},
|
||||
toggleModeMenu() {
|
||||
if (!this.isConnected || this.streamingMessage) {
|
||||
@ -256,7 +270,7 @@ export const modeMethods = {
|
||||
}
|
||||
},
|
||||
async handleStopAllSubAgents() {
|
||||
const isMultiAgent = !!this.multiAgentMode;
|
||||
const isMultiAgent = this.currentConversationType === 'multi_agent';
|
||||
const mode = isMultiAgent ? 'soft_stop' : 'terminate';
|
||||
const title = isMultiAgent ? '是否暂停所有子智能体?' : '是否终结所有子智能体?';
|
||||
const message = isMultiAgent
|
||||
|
||||
@ -30,59 +30,18 @@ export const routeMethods = {
|
||||
this.titleReady = false;
|
||||
this.currentConversationTitle = '';
|
||||
this.titleTypingText = '';
|
||||
const path = window.location.pathname.replace(/^\/+/, '');
|
||||
// 检查多智能体模式入口
|
||||
let path = window.location.pathname.replace(/^\/+/, '');
|
||||
// 兼容重定向:旧多智能体路由统一收敛到裸路径。
|
||||
// 对话类型是 metadata 属性(创建时确定),不再是路由概念。
|
||||
if (path === 'multiagent/new' || path === 'multiagent') {
|
||||
this.multiAgentMode = true;
|
||||
this.currentConversationId = null;
|
||||
this.currentConversationTitle = '多智能体模式';
|
||||
this.logMessageState('bootstrapRoute:clear-messages-for-multiagent-new');
|
||||
this.messages = [];
|
||||
this.titleReady = true;
|
||||
this.suppressTitleTyping = false;
|
||||
this.startTitleTyping('多智能体模式', { animate: false });
|
||||
this.initialRouteResolved = true;
|
||||
this.refreshBlankHeroState();
|
||||
// 进入多智能体模式时触发后端重建索引以补全 multi_agent_mode 字段
|
||||
try {
|
||||
await fetch('/api/multiagent/rebuild-index', { method: 'POST' });
|
||||
} catch (_e) {
|
||||
// 重建失败不阻断主流程
|
||||
}
|
||||
await this.restoreComposerDraftState('bootstrap-route:multiagent');
|
||||
return;
|
||||
}
|
||||
// 当 URL 是 /multiagent/conv_xxx 时也走多智能体模式
|
||||
if (path.startsWith('multiagent/')) {
|
||||
this.multiAgentMode = true;
|
||||
history.replaceState({}, '', '/new');
|
||||
path = 'new';
|
||||
} else if (path.startsWith('multiagent/')) {
|
||||
const convPart = path.slice('multiagent/'.length);
|
||||
const convId = convPart.startsWith('conv_') ? convPart : `conv_${convPart}`;
|
||||
try {
|
||||
// 统一加载协议:一次 bootstrap 拿元数据+历史+运行状态(纯只读,不切换后端上下文)
|
||||
const result = await this.enterConversation(convId, {
|
||||
source: 'refresh',
|
||||
urlMode: 'replace',
|
||||
urlPrefix: '/multiagent/'
|
||||
});
|
||||
if (result.success) {
|
||||
this.currentConversationTitle = result.title || '多智能体模式';
|
||||
this.titleReady = true;
|
||||
this.suppressTitleTyping = false;
|
||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||
this.initialRouteResolved = true;
|
||||
await this.restoreComposerDraftState('bootstrap-route:multiagent-existing');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[multiagent] 加载多智能体对话失败:', error);
|
||||
}
|
||||
// 加载失败回退到 multiagent/new 路径
|
||||
history.replaceState({}, '', '/multiagent/new');
|
||||
window.location.reload();
|
||||
return;
|
||||
const bareId = convPart.startsWith('conv_') ? convPart.slice('conv_'.length) : convPart;
|
||||
history.replaceState({}, '', `/${bareId}`);
|
||||
path = bareId;
|
||||
}
|
||||
// 非多智能体模式:清除标志
|
||||
this.multiAgentMode = false;
|
||||
if (!path || this.isExplicitNewConversationRoute()) {
|
||||
this.currentConversationId = null;
|
||||
this.currentConversationTitle = '新对话';
|
||||
@ -93,6 +52,15 @@ export const routeMethods = {
|
||||
this.startTitleTyping('新对话', { animate: false });
|
||||
this.initialRouteResolved = true;
|
||||
this.refreshBlankHeroState();
|
||||
// 触发后端重建索引以补全旧对话的 multi_agent_mode 字段(每次页面加载最多一次)
|
||||
if (!this.multiAgentIndexRebuildTriggered) {
|
||||
this.multiAgentIndexRebuildTriggered = true;
|
||||
try {
|
||||
await fetch('/api/multiagent/rebuild-index', { method: 'POST' });
|
||||
} catch (_e) {
|
||||
// 重建失败不阻断主流程
|
||||
}
|
||||
}
|
||||
await this.restoreComposerDraftState('bootstrap-route:new');
|
||||
return;
|
||||
}
|
||||
@ -102,8 +70,7 @@ export const routeMethods = {
|
||||
// 统一加载协议:一次 bootstrap 拿元数据+历史+运行状态(纯只读,不切换后端上下文)
|
||||
const result = await this.enterConversation(convId, {
|
||||
source: 'refresh',
|
||||
urlMode: 'replace',
|
||||
urlPrefix: '/'
|
||||
urlMode: 'replace'
|
||||
});
|
||||
if (result.success) {
|
||||
this.currentConversationTitle = result.title || '';
|
||||
@ -166,7 +133,8 @@ export const routeMethods = {
|
||||
},
|
||||
isExplicitNewConversationRoute() {
|
||||
const normalizedPath = window.location.pathname.replace(/^\/+|\/+$/g, '');
|
||||
return normalizedPath === 'new' || normalizedPath === 'multiagent/new' || normalizedPath === 'multiagent';
|
||||
// 多智能体旧路由在 bootstrapRoute 已被重定向到 /new,这里只判裸新建路由
|
||||
return normalizedPath === 'new';
|
||||
},
|
||||
stripConversationPrefix(conversationId) {
|
||||
if (!conversationId) return '';
|
||||
|
||||
@ -23,15 +23,44 @@ export const persistWorkspaceMode = (isHostMode: boolean): void => {
|
||||
}
|
||||
};
|
||||
|
||||
/* 待创建对话类型缓存:/new 页输入栏「智能体/多智能体」选择器的上次选择,
|
||||
刷新后恢复,避免每次新建都要重新选。 */
|
||||
const NEW_CONVERSATION_TYPE_STORAGE_KEY = 'agents_new_conversation_type';
|
||||
|
||||
const loadNewConversationType = (): 'agent' | 'multi_agent' => {
|
||||
try {
|
||||
const v = window.localStorage.getItem(NEW_CONVERSATION_TYPE_STORAGE_KEY);
|
||||
return v === 'multi_agent' ? 'multi_agent' : 'agent';
|
||||
} catch {
|
||||
return 'agent';
|
||||
}
|
||||
};
|
||||
|
||||
export const persistNewConversationType = (type: string): void => {
|
||||
try {
|
||||
window.localStorage.setItem(NEW_CONVERSATION_TYPE_STORAGE_KEY, type);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
|
||||
const cachedWorkspaceMode = loadCachedWorkspaceMode();
|
||||
const cachedNewConversationType = loadNewConversationType();
|
||||
|
||||
export function dataState() {
|
||||
return {
|
||||
// 路由相关
|
||||
initialRouteResolved: false,
|
||||
dropToolEvents: false,
|
||||
// 多智能体模式开关
|
||||
multiAgentMode: false,
|
||||
// 当前打开对话的类型('normal' | 'multi_agent',创建时确定、不可变);
|
||||
// 空对话态为 null。权威来源是对话 metadata.multi_agent_mode。
|
||||
currentConversationType: null,
|
||||
// /new 页输入栏选择器的待创建类型('agent' | 'multi_agent',localStorage 持久化)
|
||||
newConversationType: cachedNewConversationType,
|
||||
// 输入栏类型选择器菜单开关
|
||||
agentTypeMenuOpen: false,
|
||||
// 多智能体索引重建触发去重(每次页面加载最多一次)
|
||||
multiAgentIndexRebuildTriggered: false,
|
||||
|
||||
// 轮询模式标志(禁用 WebSocket 事件处理)
|
||||
usePollingMode: true,
|
||||
|
||||
@ -5,9 +5,6 @@ import { useQuickDockStore } from '../stores/quickDock';
|
||||
import { useFileStore } from '../stores/file';
|
||||
|
||||
export const watchers = {
|
||||
multiAgentMode(newValue) {
|
||||
useConversationStore().$patch({ multiAgentMode: !!newValue });
|
||||
},
|
||||
inputMessage() {
|
||||
this.autoResizeInput();
|
||||
if (typeof this.scheduleComposerDraftPersist === 'function') {
|
||||
@ -74,6 +71,11 @@ export const watchers = {
|
||||
if (newValue !== oldValue) {
|
||||
// 同步到 conversationStore(QuickDock 等组件监听 store 侧的 id)
|
||||
useConversationStore().setCurrentConversationId(newValue || null);
|
||||
if (!newValue) {
|
||||
// 空对话态:对话类型复位(进入对话时由 enterConversation 从 metadata 落地)
|
||||
this.currentConversationType = null;
|
||||
useConversationStore().$patch({ multiAgentMode: false });
|
||||
}
|
||||
const quickDock = useQuickDockStore();
|
||||
// 关闭详情/预览/菜单等瞬态(必须立即)
|
||||
quickDock.resetTransient();
|
||||
|
||||
@ -1564,7 +1564,10 @@ function renderAnswerSubAgentQuestion(result: any, args: any): string {
|
||||
}
|
||||
|
||||
function renderCreateCustomAgent(result: any, args: any): string {
|
||||
const status = formatToolStatusLabel(result, '✓ 已创建', '✗ 创建失败');
|
||||
const overwritten = !!result?.overwritten;
|
||||
const status = result?.success
|
||||
? (overwritten ? '✓ 已覆盖更新' : '✓ 已创建')
|
||||
: '✗ 创建失败';
|
||||
const roleId = args.role_id ?? result.role_id ?? '';
|
||||
const name = args.name ?? result.name ?? roleId;
|
||||
|
||||
|
||||
@ -270,6 +270,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-actions">
|
||||
<div class="input-actions-left">
|
||||
<button
|
||||
type="button"
|
||||
class="stadium-btn add-btn"
|
||||
@ -279,6 +280,37 @@
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div class="agent-type-switcher" :class="{ 'is-disabled': agentTypeLocked }">
|
||||
<button
|
||||
type="button"
|
||||
class="agent-type-switcher__btn"
|
||||
@click.stop="$emit('toggle-agent-type-menu')"
|
||||
:disabled="!isConnected || agentTypeLocked"
|
||||
:aria-expanded="agentTypeMenuOpen"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<svg class="agent-type-switcher__icon" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="5" r="2.4" stroke="currentColor" stroke-width="1.3" />
|
||||
<path d="M3.2 13.2c.6-2.3 2.5-3.6 4.8-3.6s4.2 1.3 4.8 3.6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>{{ agentTypeLabel }}</span>
|
||||
<span class="agent-type-switcher__caret" :class="{ open: agentTypeMenuOpen }" aria-hidden="true">›</span>
|
||||
</button>
|
||||
<div v-if="agentTypeMenuOpen && !agentTypeLocked" class="agent-type-switcher__menu" @click.stop>
|
||||
<button
|
||||
v-for="option in agentTypeOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
class="agent-type-switcher__menu-item"
|
||||
:class="{ 'is-active': option.value === newConversationType }"
|
||||
@click.stop="$emit('select-new-conversation-type', option.value)"
|
||||
>
|
||||
<span class="agent-type-switcher__menu-label">{{ option.label }}</span>
|
||||
<span class="agent-type-switcher__menu-desc">{{ option.description }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-actions-right">
|
||||
<button
|
||||
v-if="isVoiceInputSupported"
|
||||
@ -361,7 +393,6 @@
|
||||
:execution-mode-enabled="executionModeEnabled"
|
||||
:goal-mode-armed="goalModeArmed"
|
||||
:goal-running="goalRunning"
|
||||
:multi-agent-mode="multiAgentMode"
|
||||
@quick-upload="triggerQuickUpload"
|
||||
@open-file-manager="$emit('open-file-manager')"
|
||||
@pick-images="$emit('pick-images')"
|
||||
@ -381,7 +412,6 @@
|
||||
@open-review="$emit('open-review')"
|
||||
@open-path-authorization="$emit('open-path-authorization')"
|
||||
@toggle-goal-mode="$emit('toggle-goal-mode')"
|
||||
@toggle-agent-mode="$emit('toggle-agent-mode')"
|
||||
/>
|
||||
<div class="permission-switcher" @click.stop>
|
||||
<button
|
||||
@ -564,7 +594,8 @@ const emit = defineEmits([
|
||||
'toggle-goal-mode',
|
||||
'open-goal-dialog',
|
||||
'open-git-changes-panel',
|
||||
'toggle-agent-mode',
|
||||
'toggle-agent-type-menu',
|
||||
'select-new-conversation-type',
|
||||
'stop-all-sub-agents'
|
||||
]);
|
||||
|
||||
@ -634,7 +665,12 @@ const props = defineProps<{
|
||||
goalModeArmed?: boolean;
|
||||
goalRunning?: boolean;
|
||||
goalProgress?: Record<string, any> | null;
|
||||
multiAgentMode?: boolean;
|
||||
/** 当前已打开对话的类型(创建时确定、不可变;null = 空对话) */
|
||||
currentConversationType?: 'normal' | 'multi_agent' | null;
|
||||
/** 空对话状态下待创建对话的类型(用户可在输入栏切换) */
|
||||
newConversationType?: 'agent' | 'multi_agent';
|
||||
/** 类型选择器菜单是否展开 */
|
||||
agentTypeMenuOpen?: boolean;
|
||||
/**
|
||||
* 主对话是否空闲(composerBusy 可能是 true,但只是因为后台子智能体在跑)。
|
||||
* 多智能体模式下,若主对话空闲,输入栏应处于“正常发送”状态,不是“停止”状态。
|
||||
@ -2723,6 +2759,31 @@ const currentPermissionLabel = computed(() => {
|
||||
return matched ? matched.label : props.currentPermissionMode;
|
||||
});
|
||||
|
||||
/** 对话类型选择器:选项定义 */
|
||||
const agentTypeOptions = [
|
||||
{
|
||||
value: 'agent' as const,
|
||||
label: '智能体',
|
||||
description: '单智能体对话'
|
||||
},
|
||||
{
|
||||
value: 'multi_agent' as const,
|
||||
label: '多智能体',
|
||||
description: 'Team Leader + 子智能体协作'
|
||||
}
|
||||
];
|
||||
|
||||
/** 已打开对话时类型不可变(类型是创建时确定的对话属性) */
|
||||
const agentTypeLocked = computed(() => !!props.currentConversationId);
|
||||
|
||||
/** 按钮文案:有对话显示对话类型,空对话显示待创建类型 */
|
||||
const agentTypeLabel = computed(() => {
|
||||
if (props.currentConversationId) {
|
||||
return props.currentConversationType === 'multi_agent' ? '多智能体' : '智能体';
|
||||
}
|
||||
return props.newConversationType === 'multi_agent' ? '多智能体' : '智能体';
|
||||
});
|
||||
|
||||
const currentExecutionShortLabel = computed(() => {
|
||||
const mode = String(props.currentExecutionMode || '');
|
||||
if (mode === 'direct') return '完全访问';
|
||||
|
||||
@ -73,18 +73,6 @@
|
||||
<template v-else>{{ goalModeArmed ? '已就绪' : '' }}</template>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu-entry"
|
||||
:class="{ 'mode-entry-active': multiAgentMode }"
|
||||
@click.stop="$emit('toggle-agent-mode')"
|
||||
:disabled="!isConnected"
|
||||
>
|
||||
{{ multiAgentMode ? '返回传统模式' : '多智能体模式' }}
|
||||
<span class="entry-arrow">
|
||||
<template v-if="multiAgentMode">当前模式</template>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu-entry has-submenu"
|
||||
@ -223,7 +211,6 @@ const props = defineProps<{
|
||||
goalModeArmed?: boolean;
|
||||
goalRunning?: boolean;
|
||||
goalProgress?: Record<string, any> | null;
|
||||
multiAgentMode?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
@ -244,7 +231,6 @@ defineEmits<{
|
||||
(event: 'pick-video'): void;
|
||||
(event: 'open-path-authorization'): void;
|
||||
(event: 'toggle-goal-mode'): void;
|
||||
(event: 'toggle-agent-mode'): void;
|
||||
}>();
|
||||
|
||||
const getIconStyle = (key: string) => (props.iconStyle ? props.iconStyle(key) : {});
|
||||
|
||||
@ -113,7 +113,28 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!searchActive && !isGroupByWorkspaceActive" class="conversation-type-switch">
|
||||
<button
|
||||
type="button"
|
||||
class="conversation-type-option"
|
||||
:class="{ active: sidebarType === 'normal' }"
|
||||
@click="setSidebarType('normal')"
|
||||
>
|
||||
普通对话
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="conversation-type-option"
|
||||
:class="{ active: sidebarType === 'multi_agent' }"
|
||||
@click="setSidebarType('multi_agent')"
|
||||
>
|
||||
多智能体
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="conversation-list">
|
||||
<Transition :name="slideTransitionName" mode="out-in">
|
||||
<div :key="sidebarType" class="conversation-list-pane">
|
||||
<div v-if="runningTaskItems.length && !searchActive && !isGroupByWorkspaceActive" class="running-task-section">
|
||||
<div class="running-task-section-title">运行中的任务</div>
|
||||
<button
|
||||
@ -182,6 +203,15 @@
|
||||
class="workspace-group-actions"
|
||||
:class="{ visible: hoverWorkspaceId === String(ws.workspace_id || '') || openWorkspaceMenuId === String(ws.workspace_id || '') }"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="workspace-type-btn"
|
||||
:title="sidebarType === 'multi_agent' ? '当前:多智能体对话(点击切换为普通对话)' : '当前:普通对话(点击切换为多智能体对话)'"
|
||||
:aria-label="sidebarType === 'multi_agent' ? '切换为普通对话' : '切换为多智能体对话'"
|
||||
@click.stop="toggleSidebarType"
|
||||
>
|
||||
<span class="workspace-type-btn-label">{{ sidebarType === 'multi_agent' ? '多' : '单' }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="workspace-new-btn"
|
||||
@ -582,6 +612,8 @@
|
||||
</button>
|
||||
<div v-else-if="displayConversations.length" class="search-done">已全部搜索</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@ -727,12 +759,31 @@ const emit = defineEmits<{
|
||||
(event: 'reveal-workspace', workspaceId: string): void;
|
||||
(event: 'rename-workspace', payload: { workspaceId: string; label: string }): void;
|
||||
(event: 'pin-workspace', workspaceId: string): void;
|
||||
(event: 'conversation-type-change', type: 'normal' | 'multi_agent'): void;
|
||||
}>();
|
||||
|
||||
const uiStore = useUiStore();
|
||||
const conversationStore = useConversationStore();
|
||||
const personalizationStore = usePersonalizationStore();
|
||||
|
||||
/** 侧边栏对话类型过滤器(普通/多智能体),唯一真相在 conversation store */
|
||||
const sidebarType = computed(() => conversationStore.sidebarConversationType);
|
||||
|
||||
/** 列表切换动画方向:切到多智能体向左滑出/从右侧进入,切回反向 */
|
||||
const slideTransitionName = computed(() =>
|
||||
sidebarType.value === 'multi_agent' ? 'type-slide-left' : 'type-slide-right'
|
||||
);
|
||||
|
||||
const setSidebarType = (type: 'normal' | 'multi_agent') => {
|
||||
if (conversationStore.sidebarConversationType === type) return;
|
||||
conversationStore.setSidebarConversationType(type);
|
||||
emit('conversation-type-change', type);
|
||||
};
|
||||
|
||||
const toggleSidebarType = () => {
|
||||
setSidebarType(sidebarType.value === 'multi_agent' ? 'normal' : 'multi_agent');
|
||||
};
|
||||
|
||||
const { sidebarCollapsed: collapsed } = storeToRefs(uiStore);
|
||||
const {
|
||||
searchQuery,
|
||||
|
||||
@ -827,8 +827,8 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
ctx.promoteConversationToTop(convId);
|
||||
const pathFragment = ctx.stripConversationPrefix(convId);
|
||||
const currentPath = window.location.pathname.replace(/^\/+/, '');
|
||||
const urlPrefix = ctx.multiAgentMode ? '/multiagent/' : '/';
|
||||
const targetPath = `${urlPrefix}${pathFragment}`;
|
||||
// 对话类型不再是路由概念,统一裸路径 /<id>
|
||||
const targetPath = `/${pathFragment}`;
|
||||
if (data.created) {
|
||||
history.pushState({ conversationId: convId }, '', targetPath);
|
||||
} else if (currentPath !== pathFragment) {
|
||||
@ -1706,7 +1706,7 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
// 多智能体模式下,若仍有运行中的多智能体实例,保持对话运行态,继续接收后续消息;
|
||||
// 但此时主智能体已空闲,不应阻塞输入区(waitingForSubAgent=false)。
|
||||
if (hasRunningMultiAgent) {
|
||||
} else if (ctx.multiAgentMode || !data.has_running_sub_agents) {
|
||||
} else if (ctx.currentConversationType === 'multi_agent' || !data.has_running_sub_agents) {
|
||||
if (ctx.waitingForSubAgent) {
|
||||
ctx.waitingForSubAgent = false;
|
||||
}
|
||||
@ -1734,8 +1734,8 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
}
|
||||
socketLog('等待子智能体完成:', data);
|
||||
|
||||
// 多智能体模式下,子智能体 idle/running 是常态,不阻塞主对话输入区
|
||||
if (!ctx.multiAgentMode) {
|
||||
// 多智能体对话下,子智能体 idle/running 是常态,不阻塞主对话输入区
|
||||
if (ctx.currentConversationType !== 'multi_agent') {
|
||||
// 设置标志:有子智能体在运行,阻止状态重置
|
||||
ctx.waitingForSubAgent = true;
|
||||
|
||||
|
||||
@ -1,5 +1,25 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/* 侧边栏对话类型过滤器缓存(普通/多智能体):记住用户上次查看的列表类型。 */
|
||||
const SIDEBAR_CONVERSATION_TYPE_STORAGE_KEY = 'agents_sidebar_conversation_type';
|
||||
|
||||
const loadSidebarConversationType = (): 'normal' | 'multi_agent' => {
|
||||
try {
|
||||
const v = window.localStorage.getItem(SIDEBAR_CONVERSATION_TYPE_STORAGE_KEY);
|
||||
return v === 'multi_agent' ? 'multi_agent' : 'normal';
|
||||
} catch {
|
||||
return 'normal';
|
||||
}
|
||||
};
|
||||
|
||||
const persistSidebarConversationType = (type: string): void => {
|
||||
try {
|
||||
window.localStorage.setItem(SIDEBAR_CONVERSATION_TYPE_STORAGE_KEY, type);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
|
||||
export interface ConversationSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
@ -54,7 +74,10 @@ interface ConversationState {
|
||||
runningWorkspaceTasks: any[];
|
||||
acknowledgedCompletedTaskIds: string[];
|
||||
workspaceGroups: WorkspaceConversationGroup[];
|
||||
/** 当前打开对话是否为多智能体(由 enterConversation/空对话 watcher 写入,subAgent store 读取) */
|
||||
multiAgentMode: boolean;
|
||||
/** 侧边栏对话类型过滤器:'normal' 普通对话 | 'multi_agent' 多智能体对话(localStorage 持久化) */
|
||||
sidebarConversationType: 'normal' | 'multi_agent';
|
||||
}
|
||||
|
||||
export const useConversationStore = defineStore('conversation', {
|
||||
@ -82,13 +105,21 @@ export const useConversationStore = defineStore('conversation', {
|
||||
runningWorkspaceTasks: [],
|
||||
acknowledgedCompletedTaskIds: [],
|
||||
workspaceGroups: [],
|
||||
multiAgentMode: false
|
||||
multiAgentMode: false,
|
||||
sidebarConversationType: loadSidebarConversationType()
|
||||
}),
|
||||
actions: {
|
||||
/** 当前对话 id 同步(由 app watcher 在 this.currentConversationId 变化时写入) */
|
||||
setCurrentConversationId(id: string | null) {
|
||||
this.currentConversationId = id;
|
||||
},
|
||||
/** 侧边栏对话类型过滤器切换(左右切换控件写入,驱动列表请求的服务端过滤) */
|
||||
setSidebarConversationType(type: 'normal' | 'multi_agent') {
|
||||
const normalized = type === 'multi_agent' ? 'multi_agent' : 'normal';
|
||||
if (this.sidebarConversationType === normalized) return;
|
||||
this.sidebarConversationType = normalized;
|
||||
persistSidebarConversationType(normalized);
|
||||
},
|
||||
resetConversations() {
|
||||
this.conversations = [];
|
||||
this.searchResults = [];
|
||||
@ -204,7 +235,8 @@ export const useConversationStore = defineStore('conversation', {
|
||||
const fetchOffset = refresh ? 0 : group.offset;
|
||||
group.loading = true;
|
||||
try {
|
||||
const maParam = this.multiAgentMode ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
// 列表过滤由侧边栏类型过滤器(普通/多智能体)决定
|
||||
const maParam = this.sidebarConversationType === 'multi_agent' ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const response = await fetch(
|
||||
`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.fetchLimit}&offset=${fetchOffset}${maParam}`
|
||||
);
|
||||
@ -251,8 +283,9 @@ export const useConversationStore = defineStore('conversation', {
|
||||
if (group.hasMore) {
|
||||
try {
|
||||
const fetchOffset = group.conversations.length;
|
||||
const maParam = this.sidebarConversationType === 'multi_agent' ? '&multi_agent_mode=1' : '&multi_agent_mode=0';
|
||||
const response = await fetch(
|
||||
`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.bufferLimit}&offset=${fetchOffset}`
|
||||
`/api/conversations?workspace_id=${encodeURIComponent(workspaceId)}&limit=${group.bufferLimit}&offset=${fetchOffset}${maParam}`
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
|
||||
@ -2428,3 +2428,98 @@ a.md-download-link {
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ========== 深色主题变体(对话区组件) ========== */
|
||||
body[data-theme='dark'] {
|
||||
/* 用户消息块 - 使用浅灰色 */
|
||||
.user-message .message-text {
|
||||
background: var(--badge-bg);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* AI 消息块 - 使用浅灰色 */
|
||||
.assistant-message .message-text {
|
||||
background: var(--badge-bg);
|
||||
border-left-color: var(--border-strong);
|
||||
}
|
||||
|
||||
/* 思考块和工具块 - 使用更深的颜色(类似选中状态) */
|
||||
.thinking-block,
|
||||
.tool-block,
|
||||
.collapsible-block {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
.collapsible-header {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
/* 代码块 - 代码区域纯黑底+白字 */
|
||||
.code-block-wrapper {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.text-output pre {
|
||||
background: var(--surface-base) !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.text-output pre code {
|
||||
background: transparent !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.text-output .text-content table {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.text-output .text-content thead {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
/* 滚动锁定按钮 */
|
||||
.scroll-lock-btn {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn {
|
||||
border-color: color-mix(in srgb, var(--on-accent) 20%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn svg {
|
||||
stroke: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 顶部栏 - 黑色背景,白色字体 */
|
||||
.conversation-ribbon {
|
||||
background: var(--surface-panel) !important;
|
||||
}
|
||||
|
||||
.conversation-ribbon::after {
|
||||
background: var(--surface-panel) !important;
|
||||
}
|
||||
|
||||
/* 模型选择下拉菜单 - 和输入栏一样的底色 */
|
||||
.model-mode-dropdown {
|
||||
background: var(--badge-bg) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dropdown-item:hover:not(.disabled) {
|
||||
background: var(--surface-muted) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,6 +339,51 @@ body[data-theme='dark'] {
|
||||
.stadium-input-highlight .skill-md-link {
|
||||
color: var(--state-info);
|
||||
}
|
||||
|
||||
/* 输入栏壳体 - 灰黑色(覆盖基础 --surface-raised) */
|
||||
.stadium-shell,
|
||||
.stadium-shell.is-focused,
|
||||
.stadium-shell.has-text {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.stadium-input::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 发送按钮 - 白底黑三角,浅阴影做层次 */
|
||||
.stadium-btn.send-btn {
|
||||
background: var(--text-primary);
|
||||
color: var(--surface-raised);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn:hover:not(:disabled) {
|
||||
background: var(--text-primary);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .send-icon {
|
||||
border-left-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .stop-icon {
|
||||
background-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
/* 快捷菜单/子菜单/对话类型菜单 - 与输入栏同一灰黑色 */
|
||||
.quick-menu,
|
||||
.quick-submenu,
|
||||
.agent-type-switcher__menu {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.menu-entry:hover:not(:disabled),
|
||||
.menu-entry.active {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
}
|
||||
|
||||
body[data-theme='light'] {
|
||||
@ -350,6 +395,34 @@ body[data-theme='light'] {
|
||||
.stadium-input-highlight .skill-md-link {
|
||||
color: var(--state-info);
|
||||
}
|
||||
|
||||
/* 发送按钮 - 黑底白三角,浅阴影做层次 */
|
||||
.stadium-btn.send-btn {
|
||||
background: var(--text-primary);
|
||||
color: var(--surface-raised);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn:hover:not(:disabled) {
|
||||
background: var(--text-primary);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .send-icon {
|
||||
border-left-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .stop-icon {
|
||||
background-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.menu-entry:hover:not(:disabled) {
|
||||
background: var(--chip-bg);
|
||||
}
|
||||
|
||||
.menu-entry.active {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.permission-switcher {
|
||||
@ -1049,10 +1122,6 @@ body[data-theme='light'] {
|
||||
}
|
||||
|
||||
/* Blank conversation hero */
|
||||
.chat-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blank-hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -1427,3 +1496,122 @@ body[data-theme='light'] {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ========== 对话类型选择器(输入栏底行 + 按钮右侧) ========== */
|
||||
.input-actions-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.agent-type-switcher {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-type-switcher__btn {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
border-radius: 7px;
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background 140ms ease,
|
||||
color 140ms ease;
|
||||
}
|
||||
|
||||
.agent-type-switcher__btn:hover:not(:disabled) {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.agent-type-switcher__btn:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.agent-type-switcher__icon {
|
||||
color: var(--text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agent-type-switcher__caret {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
transform: rotate(90deg);
|
||||
transition: transform 0.18s ease;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.agent-type-switcher__caret.open {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
/* 菜单底色/边框/圆角与 + 菜单(.quick-menu)一致 */
|
||||
.agent-type-switcher__menu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(100% + 14px);
|
||||
width: 220px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: 18px;
|
||||
background: var(--surface-soft);
|
||||
box-shadow: none;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.agent-type-switcher__menu-item {
|
||||
border: none;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
min-height: 48px;
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.agent-type-switcher__menu-item:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.agent-type-switcher__menu-item.is-active {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.agent-type-switcher__menu-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.agent-type-switcher__menu-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agent-type-switcher__menu {
|
||||
width: min(220px, calc(100vw - 40px));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2869,41 +2869,6 @@ body[data-theme='light'] {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.stadium-shell.is-focused,
|
||||
.stadium-shell.has-text,
|
||||
.stadium-shell.is-multiline.is-focused,
|
||||
.stadium-shell.is-multiline.has-text {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 发送按钮 - 黑底白三角,浅阴影做层次 */
|
||||
.stadium-btn.send-btn {
|
||||
background: var(--text-primary);
|
||||
color: var(--surface-raised);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn:hover:not(:disabled) {
|
||||
background: var(--text-primary);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .send-icon {
|
||||
border-left-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .stop-icon {
|
||||
background-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.menu-entry:hover:not(:disabled) {
|
||||
background: var(--chip-bg);
|
||||
}
|
||||
|
||||
.menu-entry.active {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
/* 机器人图标保持黑色 */
|
||||
.status-logo {
|
||||
color: var(--text-primary);
|
||||
@ -2973,15 +2938,6 @@ body[data-theme='light'] {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
/* 删除按钮 - 使用深灰色 */
|
||||
.conversation-action-btn.delete-btn {
|
||||
background: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.conversation-action-btn.delete-btn:hover {
|
||||
background: color-mix(in srgb, var(--text-secondary) 70%, var(--text-primary)) !important;
|
||||
}
|
||||
|
||||
/* 个人空间 - consideration 删除按钮 */
|
||||
.consideration-remove {
|
||||
color: var(--text-secondary) !important;
|
||||
@ -3025,73 +2981,6 @@ body[data-theme='dark'] {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.stadium-shell.is-focused,
|
||||
.stadium-shell.has-text,
|
||||
.stadium-shell.is-multiline.is-focused,
|
||||
.stadium-shell.is-multiline.has-text {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 输入栏 - 改为灰黑色 */
|
||||
.stadium-shell {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.stadium-shell.is-focused,
|
||||
.stadium-shell.has-text {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
/* 输入框文字颜色 */
|
||||
.stadium-input {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.stadium-input::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 发送按钮 - 白底黑三角,浅阴影做层次 */
|
||||
.stadium-btn.send-btn {
|
||||
background: var(--text-primary);
|
||||
color: var(--surface-raised);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn:hover:not(:disabled) {
|
||||
background: var(--text-primary);
|
||||
box-shadow: 0 1px 3px var(--shadow-color);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .send-icon {
|
||||
border-left-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
.stadium-btn.send-btn .stop-icon {
|
||||
background-color: var(--surface-raised);
|
||||
}
|
||||
|
||||
/* 快捷菜单 - 改为灰黑色 */
|
||||
.quick-menu {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.quick-submenu {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.menu-entry:hover:not(:disabled) {
|
||||
background: var(--theme-tab-active);
|
||||
}
|
||||
|
||||
.menu-entry.active {
|
||||
background: var(--theme-tab-active);
|
||||
}
|
||||
|
||||
/* 机器人图标保持白色 */
|
||||
.status-logo {
|
||||
color: var(--text-primary);
|
||||
@ -3166,29 +3055,6 @@ body[data-theme='dark'] {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
/* 空对话欢迎语 - 白色 */
|
||||
.blank-hero-text {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* 删除按钮 - 使用浅灰色 */
|
||||
.conversation-action-btn.delete-btn {
|
||||
background: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.conversation-action-btn.delete-btn:hover {
|
||||
background: color-mix(in srgb, var(--text-secondary) 70%, var(--text-primary)) !important;
|
||||
}
|
||||
|
||||
/* 顶部栏 - 黑色背景,白色字体 */
|
||||
.conversation-ribbon {
|
||||
background: var(--surface-panel) !important;
|
||||
}
|
||||
|
||||
.conversation-ribbon::after {
|
||||
background: var(--surface-panel) !important;
|
||||
}
|
||||
|
||||
.mobile-topbar-title {
|
||||
color: var(--text-primary) !important;
|
||||
text-shadow: none !important;
|
||||
@ -3269,62 +3135,6 @@ body[data-theme='dark'] {
|
||||
filter: brightness(0) invert(1) !important;
|
||||
}
|
||||
|
||||
/* 用量统计 - 数字白色 */
|
||||
.stat-value {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* 对话区域顶部的模型选择器 - 白色字体 */
|
||||
.conversation-ribbon__selector {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.conversation-ribbon__selector:hover {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.conversation-ribbon__selector.open {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.selector-model {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.selector-mode {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.conversation-ribbon__text {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* 模型选择下拉菜单 - 和输入栏一样的底色 */
|
||||
.model-mode-dropdown {
|
||||
background: var(--badge-bg) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.dropdown-item:hover:not(.disabled) {
|
||||
background: var(--surface-muted) !important;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
/* 个人空间 - consideration 列表项的虚线边框 */
|
||||
.consideration-item {
|
||||
border-color: var(--text-secondary) !important;
|
||||
@ -3360,105 +3170,6 @@ body[data-theme='dark'] {
|
||||
background: var(--surface-panel) !important;
|
||||
}
|
||||
|
||||
/* 快捷菜单 - 和输入栏一样的底色 */
|
||||
.quick-menu {
|
||||
background: var(--badge-bg) !important;
|
||||
border-color: var(--border-card-strong) !important;
|
||||
}
|
||||
|
||||
.quick-submenu {
|
||||
background: var(--badge-bg) !important;
|
||||
border-color: var(--border-card-strong) !important;
|
||||
}
|
||||
|
||||
.menu-entry {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.menu-entry:hover:not(:disabled) {
|
||||
background: var(--theme-tab-active) !important;
|
||||
}
|
||||
|
||||
.menu-entry.active {
|
||||
background: var(--theme-tab-active) !important;
|
||||
}
|
||||
|
||||
.menu-entry .entry-arrow {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.submenu-label {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* 用户消息块 - 使用浅灰色 */
|
||||
.user-message .message-text {
|
||||
background: var(--badge-bg);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* AI 消息块 - 使用浅灰色 */
|
||||
.assistant-message .message-text {
|
||||
background: var(--badge-bg);
|
||||
border-left-color: var(--border-strong);
|
||||
}
|
||||
|
||||
/* 思考块和工具块 - 使用更深的颜色(类似选中状态) */
|
||||
.thinking-block,
|
||||
.tool-block,
|
||||
.collapsible-block {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
.collapsible-header {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
/* 代码块 - 代码区域纯黑底+白字 */
|
||||
.code-block-wrapper {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.text-output pre {
|
||||
background: var(--surface-base) !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.text-output pre code {
|
||||
background: transparent !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 滚动锁定按钮 */
|
||||
.scroll-lock-btn {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn {
|
||||
border-color: color-mix(in srgb, var(--on-accent) 20%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.scroll-lock-toggle.locked .scroll-lock-btn svg {
|
||||
stroke: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 左侧面板头部(文件/待办事项/子智能体标签栏) */
|
||||
.sidebar-header {
|
||||
background: var(--surface-rail);
|
||||
@ -3475,25 +3186,6 @@ body[data-theme='dark'] {
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.sidebar-manage-btn {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.sidebar-manage-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.sidebar-view-toggle {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.sidebar-view-toggle:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.panel-menu {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
@ -3503,18 +3195,6 @@ body[data-theme='dark'] {
|
||||
background: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.todo-empty,
|
||||
.sub-agent-empty,
|
||||
.file-tree-empty {
|
||||
background: var(--surface-panel);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.todo-task {
|
||||
background: var(--badge-bg);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.folder-header,
|
||||
.file-node {
|
||||
background: transparent;
|
||||
@ -3527,49 +3207,6 @@ body[data-theme='dark'] {
|
||||
.file-node.file-leaf:hover {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
/* 聚焦文件面板 */
|
||||
.file-tab {
|
||||
background: var(--surface-panel);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.file-content {
|
||||
background: var(--surface-base);
|
||||
}
|
||||
|
||||
.file-content code {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.focus-close-btn {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.focus-close-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.approval-close-btn {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.approval-close-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.text-output .text-content table {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.text-output .text-content thead {
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-page {
|
||||
|
||||
@ -826,3 +826,36 @@ body[data-theme='dark'] .git-change-file__open-icon {
|
||||
color: var(--theme-surface-strong);
|
||||
background: var(--accent-strong);
|
||||
}
|
||||
|
||||
/* ========== 深色主题变体(右侧面板组件) ========== */
|
||||
body[data-theme='dark'] {
|
||||
/* 聚焦文件面板 */
|
||||
.file-tab {
|
||||
background: var(--surface-panel);
|
||||
border-color: var(--border-card-strong);
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.file-content {
|
||||
background: var(--surface-base);
|
||||
}
|
||||
|
||||
.focus-close-btn {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.focus-close-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.approval-close-btn {
|
||||
background: var(--badge-bg);
|
||||
}
|
||||
|
||||
.approval-close-btn:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -808,6 +808,7 @@ body[data-theme='dark'] .workspace-group-header.search-result-group-header:hover
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.workspace-type-btn,
|
||||
.workspace-new-btn,
|
||||
.workspace-more-btn {
|
||||
width: 28px;
|
||||
@ -824,6 +825,7 @@ body[data-theme='dark'] .workspace-group-header.search-result-group-header:hover
|
||||
color 140ms ease;
|
||||
}
|
||||
|
||||
.workspace-type-btn:hover,
|
||||
.workspace-new-btn:hover,
|
||||
.workspace-more-btn:hover,
|
||||
.workspace-group-actions.visible .workspace-more-btn[aria-expanded='true'] {
|
||||
@ -831,6 +833,12 @@ body[data-theme='dark'] .workspace-group-header.search-result-group-header:hover
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.workspace-type-btn-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workspace-more-btn span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@ -1103,3 +1111,78 @@ body[data-theme='dark'] .conversation-sidebar:not(.collapsed) .personal-page-btn
|
||||
body[data-theme='dark'] .conversation-sidebar .load-more-btn:hover:not(:disabled) {
|
||||
background: var(--hover-bg) !important;
|
||||
}
|
||||
|
||||
/* ========== 侧边栏对话类型切换(普通 / 多智能体) ========== */
|
||||
.conversation-type-switch {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin: 6px 8px 0;
|
||||
padding: 2px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.conversation-type-option {
|
||||
flex: 1 1 0;
|
||||
height: 26px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition:
|
||||
background 140ms ease,
|
||||
color 140ms ease;
|
||||
}
|
||||
|
||||
.conversation-type-option:hover:not(.active) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.conversation-type-option.active {
|
||||
background: var(--surface-soft);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 列表区域整体左右滑动切换动画 */
|
||||
.conversation-list-pane {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.type-slide-left-enter-active,
|
||||
.type-slide-left-leave-active,
|
||||
.type-slide-right-enter-active,
|
||||
.type-slide-right-leave-active {
|
||||
transition:
|
||||
transform 200ms ease,
|
||||
opacity 200ms ease;
|
||||
}
|
||||
|
||||
.type-slide-left-enter-from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.type-slide-left-leave-to {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.type-slide-right-enter-from {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.type-slide-right-leave-to {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user