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:
JOJO 2026-08-11 11:21:16 +08:00
parent 59a9f6ab62
commit 1c339d2e6f
24 changed files with 743 additions and 484 deletions

View File

@ -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 角色与实例

View File

@ -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>

View File

@ -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) {

View File

@ -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 resetAllStatessidebar
*/
@ -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;

View File

@ -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) {

View File

@ -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: '发送失败',

View File

@ -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}`

View File

@ -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) {

View File

@ -56,7 +56,7 @@ export const menuMethods = {
}
},
handleClickOutsideQuickMenu(event) {
if (!this.quickMenuOpen && !this.permissionMenuOpen) {
if (!this.quickMenuOpen && !this.permissionMenuOpen && !this.agentTypeMenuOpen) {
return;
}
const shell =

View File

@ -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

View File

@ -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 '';

View File

@ -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,

View File

@ -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) {
// 同步到 conversationStoreQuickDock 等组件监听 store 侧的 id
useConversationStore().setCurrentConversationId(newValue || null);
if (!newValue) {
// 空对话态:对话类型复位(进入对话时由 enterConversation 从 metadata 落地)
this.currentConversationType = null;
useConversationStore().$patch({ multiAgentMode: false });
}
const quickDock = useQuickDockStore();
// 关闭详情/预览/菜单等瞬态(必须立即)
quickDock.resetTransient();

View File

@ -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;

View File

@ -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 '完全访问';

View File

@ -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) : {});

View File

@ -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,

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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;
}