diff --git a/server/files.py b/server/files.py
index f26a254..414b17b 100644
--- a/server/files.py
+++ b/server/files.py
@@ -1,10 +1,11 @@
"""文件与GUI文件管理相关路由。"""
from __future__ import annotations
import os
+import re
import zipfile
from io import BytesIO
from pathlib import Path
-from typing import Dict, Any
+from typing import Dict, Any, List, Optional, Tuple
from flask import Blueprint, jsonify, request, send_file
from werkzeug.utils import secure_filename
@@ -311,4 +312,145 @@ def gui_text_entry(terminal, workspace, username):
return jsonify({"success": False, "error": str(exc)}), 400
+def _score_file_match(path: str, query: str) -> Optional[Tuple[int, ...]]:
+ """为 @文件 搜索计算匹配分数;分数越低越靠前,None 表示不匹配。"""
+ path_lower = path.lower()
+ query_lower = query.lower()
+
+ if path_lower.startswith(query_lower):
+ return (0, len(path))
+
+ query_parts = query_lower.split('/')
+ path_parts = path_lower.split('/')
+
+ if len(query_parts) > 1 and len(path_parts) >= len(query_parts):
+ matched = True
+ for i, q in enumerate(query_parts):
+ p = path_parts[i]
+ if not p.startswith(q) and q not in p:
+ matched = False
+ break
+ if matched:
+ return (1, len(path))
+
+ name = path_parts[-1] if path_parts else path_lower
+ if name.startswith(query_lower):
+ return (2, len(path))
+
+ if query_lower in name:
+ return (3, len(path))
+
+ if query_lower in path_lower:
+ return (4, len(path))
+
+ return None
+
+
+def _scan_project_entries(project_path: Path, max_depth: int = 6) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ """扫描项目目录,返回文件和文件夹列表(包含隐藏目录)。"""
+ files: List[Dict[str, Any]] = []
+ folders: List[Dict[str, Any]] = []
+
+ for root, dirs, filenames in os.walk(project_path):
+ rel_root = Path(root).relative_to(project_path)
+ level = len(rel_root.parts) if str(rel_root) != '.' else 0
+ if level > max_depth:
+ del dirs[:]
+ continue
+
+ for d in list(dirs):
+ dir_path = rel_root / d if str(rel_root) != '.' else Path(d)
+ folders.append({
+ "name": d,
+ "path": str(dir_path).replace('\\', '/'),
+ "type": "directory"
+ })
+
+ for f in filenames:
+ file_path = rel_root / f if str(rel_root) != '.' else Path(f)
+ ext = Path(f).suffix.lower()
+ files.append({
+ "name": f,
+ "path": str(file_path).replace('\\', '/'),
+ "type": "file",
+ "extension": ext
+ })
+
+ return files, folders
+
+
+def _scan_root_entries(project_path: Path) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
+ """只扫描项目根目录的直接子项(包含隐藏目录)。"""
+ files: List[Dict[str, Any]] = []
+ folders: List[Dict[str, Any]] = []
+ if not project_path.exists() or not project_path.is_dir():
+ return files, folders
+ for entry in sorted(project_path.iterdir(), key=lambda p: (p.is_file(), p.name.lower())):
+ if entry.is_dir():
+ folders.append({
+ "name": entry.name,
+ "path": entry.name,
+ "type": "directory"
+ })
+ elif entry.is_file():
+ files.append({
+ "name": entry.name,
+ "path": entry.name,
+ "type": "file",
+ "extension": entry.suffix.lower()
+ })
+ return files, folders
+
+
+@files_bp.route('/api/project/files/search', methods=['GET'])
+@api_login_required
+@with_terminal
+def search_project_files(terminal, workspace, username):
+ """为 @文件 功能提供项目内文件/文件夹搜索(包含隐藏目录)。"""
+ policy = resolve_admin_policy(get_current_user_record())
+ if policy.get("ui_blocks", {}).get("collapse_workspace") or policy.get("ui_blocks", {}).get("block_file_manager"):
+ return jsonify({"success": False, "error": "文件浏览已被管理员禁用"}), 403
+
+ query = str(request.args.get('q') or '').strip()
+
+ try:
+ project_path = Path(getattr(workspace, 'project_path', '') or '').expanduser().resolve()
+ if not project_path.exists():
+ return jsonify({"success": False, "error": "项目路径不存在"}), 400
+ except Exception as exc:
+ return jsonify({"success": False, "error": f"项目路径无效: {exc}"}), 400
+
+ try:
+ files, folders = _scan_project_entries(project_path, max_depth=6)
+ except Exception as exc:
+ return jsonify({"success": False, "error": f"扫描项目失败: {exc}"}), 500
+
+ if not query:
+ root_files, root_folders = _scan_root_entries(project_path)
+ root_items = root_folders + root_files
+ return jsonify({
+ "success": True,
+ "data": {
+ "items": root_items[:50],
+ "total": len(root_items)
+ }
+ })
+
+ scored: List[Tuple[Tuple[int, ...], Dict[str, Any]]] = []
+ for entry in files + folders:
+ score = _score_file_match(entry["path"], query)
+ if score is not None:
+ scored.append((score, entry))
+
+ scored.sort(key=lambda x: (x[0], x[1]["path"].lower()))
+ limit = max(10, min(100, int(request.args.get('limit') or 50)))
+ return jsonify({
+ "success": True,
+ "data": {
+ "items": [entry for _, entry in scored[:limit]],
+ "total": len(scored)
+ }
+ })
+
+
__all__ = ["files_bp"]
diff --git a/static/src/App.vue b/static/src/App.vue
index 3ae5834..c5bcfbc 100644
--- a/static/src/App.vue
+++ b/static/src/App.vue
@@ -295,6 +295,7 @@
:block-tool-toggle="policyUiBlocks.block_tool_toggle"
:block-realtime-terminal="policyUiBlocks.block_realtime_terminal"
:terminal-count="terminalSessions ? Object.keys(terminalSessions).length : 0"
+ :host-mode="versioningHostMode"
:block-focus-panel="policyUiBlocks.block_focus_panel"
:block-token-panel="policyUiBlocks.block_token_panel"
:block-compress-conversation="policyUiBlocks.block_compress_conversation"
diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue
index fdf408e..181e476 100644
--- a/static/src/components/chat/ChatArea.vue
+++ b/static/src/components/chat/ChatArea.vue
@@ -933,16 +933,35 @@ const escapeUserHtml = (value: string): string =>
.replace(/'/g, ''');
const USER_SKILL_LINK_RE = /\[\$([^\]\n]+)\]\(([^)\n]*\/\.agents\/skills\/[^)\n]+\/SKILL\.md)\)/g;
+const USER_FILE_LINK_RE = /\[([^\]\n]+)\]\(file:\/\/([^)\n]+)\)/g;
+const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
+const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/;
+const BG_RUN_COMMAND_DONE_LABEL_RE = /^(?:\[)?后台\s*run_command\s*完成(?:\])?$/;
+const RENDERABLE_ACTION_TYPES = new Set([
+ 'thinking',
+ 'text',
+ 'tool',
+ 'append',
+ 'append_payload',
+ 'system'
+]);
function renderUserMessageContent(content: string): string {
const source = String(content || '');
- USER_SKILL_LINK_RE.lastIndex = 0;
+ const combinedRe = new RegExp(
+ `(?:${USER_SKILL_LINK_RE.source})|(?:${USER_FILE_LINK_RE.source})`,
+ 'g'
+ );
let html = '';
let cursor = 0;
let match: RegExpExecArray | null;
- while ((match = USER_SKILL_LINK_RE.exec(source))) {
+ while ((match = combinedRe.exec(source))) {
html += escapeUserHtml(source.slice(cursor, match.index));
- html += ` ${escapeUserHtml(match[1] || '')} `;
+ if (match[1] !== undefined) {
+ html += ` ${escapeUserHtml(match[1] || '')} `;
+ } else if (match[3] !== undefined) {
+ html += ` ${escapeUserHtml(match[3] || '')} `;
+ }
cursor = match.index + match[0].length;
}
html += escapeUserHtml(source.slice(cursor));
@@ -1040,17 +1059,6 @@ watch(
statusAvatarActiveIndex.value = 0;
}
);
-const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
-const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/;
-const BG_RUN_COMMAND_DONE_LABEL_RE = /^(?:\[)?后台\s*run_command\s*完成(?:\])?$/;
-const RENDERABLE_ACTION_TYPES = new Set([
- 'thinking',
- 'text',
- 'tool',
- 'append',
- 'append_payload',
- 'system'
-]);
const nowMs = ref(Date.now());
let timerHandle: number | null = null;
const debugLoggedSystemActionKeys = new Set();
diff --git a/static/src/components/input/FileAtMenu.vue b/static/src/components/input/FileAtMenu.vue
new file mode 100644
index 0000000..eabf456
--- /dev/null
+++ b/static/src/components/input/FileAtMenu.vue
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
diff --git a/static/src/components/input/InputComposer.vue b/static/src/components/input/InputComposer.vue
index 3154f29..adc6059 100644
--- a/static/src/components/input/InputComposer.vue
+++ b/static/src/components/input/InputComposer.vue
@@ -47,11 +47,7 @@
role="listbox"
:aria-label="slashMenuAriaLabel"
>
-
+
-
+
@@ -108,7 +112,9 @@
@@ -227,7 +239,11 @@
v-if="isVoiceInputSupported"
type="button"
class="stadium-btn voice-btn"
- :class="{ 'voice-btn--recording': isRecording, 'voice-btn--disabled': voiceModelStatus === 'downloading' || voiceModelStatus === 'model_not_ready' }"
+ :class="{
+ 'voice-btn--recording': isRecording,
+ 'voice-btn--disabled':
+ voiceModelStatus === 'downloading' || voiceModelStatus === 'model_not_ready'
+ }"
@click="toggleVoiceRecording"
:disabled="!isConnected || inputLocked"
:title="voiceButtonTitle"
@@ -264,9 +280,9 @@
@click="$emit('send-or-stop')"
:disabled="sendButtonDisabled"
>
-
-
-
+
+
+
@@ -445,6 +461,7 @@ import Mention from '@tiptap/extension-mention';
import Placeholder from '@tiptap/extension-placeholder';
import { TextSelection } from 'prosemirror-state';
import QuickMenu from '@/components/input/QuickMenu.vue';
+import FileAtMenu, { type FileAtItem } from '@/components/input/FileAtMenu.vue';
import RollingNumber from '@/components/input/RollingNumber.vue';
import { useInputStore } from '@/stores/input';
import { usePersonalizationStore } from '@/stores/personalization';
@@ -561,6 +578,7 @@ const props = defineProps<{
goalProgress?: Record | null;
hasPendingRuntimeGuidance?: boolean;
terminalCount?: number;
+ hostMode?: boolean;
}>();
const inputStore = useInputStore();
@@ -572,7 +590,14 @@ const stadiumInput = ref(null);
const fileUploadInput = ref(null);
const baselineComposerVisualHeight = ref(0);
type SkillItem = { name: string; description: string; path: string };
-type SlashMenuMode = 'root' | 'skills' | 'theme' | 'permission' | 'execution' | 'model' | 'run-mode';
+type SlashMenuMode =
+ | 'root'
+ | 'skills'
+ | 'theme'
+ | 'permission'
+ | 'execution'
+ | 'model'
+ | 'run-mode';
type SlashMenuItem = {
id: string;
label: string;
@@ -603,15 +628,27 @@ let slashAnimating = false;
let lastSlashAnimatedScroll: number | null = null;
let lastSlashAnimationEndTime = 0;
+const fileAtMenuRef = ref | null>(null);
+const fileAtOpen = ref(false);
+const fileAtQuery = ref(null);
+const fileAtActiveIndex = ref(0);
+const fileAtItems = ref([]);
+const fileAtLoading = ref(false);
+const fileAtMenuStyle = ref>({});
+const fileAtPickerActive = ref(false);
+let fileAtSearchTimer: number | null = null;
+
const slashHighlightStyle = computed(() => {
if (slashManualScrolled.value) return { display: 'none' };
const isFirst = skillSlashActiveIndex.value === 0 && skillSlashList.value?.scrollTop === 0;
return {
top: `${slashHighlightTop.value}px`,
- ...(isFirst ? {
- borderTopLeftRadius: 'calc(var(--skill-slash-radius) - var(--skill-slash-gap))',
- borderTopRightRadius: 'calc(var(--skill-slash-radius) - var(--skill-slash-gap))',
- } : {}),
+ ...(isFirst
+ ? {
+ borderTopLeftRadius: 'calc(var(--skill-slash-radius) - var(--skill-slash-gap))',
+ borderTopRightRadius: 'calc(var(--skill-slash-radius) - var(--skill-slash-gap))'
+ }
+ : {})
};
});
@@ -715,9 +752,10 @@ const projectGitSummaryForRender = computed(() => {
});
const floatingStatusVisible = computed(() => {
- if (skillSlashMenuOpen.value || props.quickMenuOpen) return false;
+ if (skillSlashMenuOpen.value || fileAtOpen.value || props.quickMenuOpen) return false;
// 消息队列(预输入/引导消息)存在时隐藏git状态栏,与 / 菜单出现时逻辑相同
- if (runtimeQueuedMessagesForRender.value.length > 0 || props.hasPendingRuntimeGuidance) return false;
+ if (runtimeQueuedMessagesForRender.value.length > 0 || props.hasPendingRuntimeGuidance)
+ return false;
return (
!!projectGitSummaryForRender.value ||
!!props.goalRunning ||
@@ -782,6 +820,25 @@ const findSlashToken = () => {
};
};
+const findAtToken = () => {
+ const instance = editor.value;
+ if (!instance) return null;
+ const { state } = instance;
+ const { $from } = state.selection;
+ const beforeInLine = $from.parent.textBetween(0, $from.parentOffset, '\n', '\n');
+ const match = /(^|[ \n])@([^\s@]*)$/.exec(beforeInLine);
+ if (!match) return null;
+ const query = match[2] || '';
+ const start = $from.pos - query.length - 1;
+ const end = $from.pos;
+ return {
+ start,
+ end,
+ query,
+ charStart: start
+ };
+};
+
const closeSlashMenu = () => {
if (slashAnimId !== null) {
cancelAnimationFrame(slashAnimId);
@@ -799,6 +856,192 @@ const closeSlashMenu = () => {
slashManualScrolled.value = false;
};
+const closeFileAtMenu = () => {
+ fileAtOpen.value = false;
+ fileAtQuery.value = null;
+ fileAtActiveIndex.value = 0;
+ fileAtItems.value = [];
+ fileAtLoading.value = false;
+ fileAtMenuStyle.value = {};
+ if (fileAtSearchTimer !== null) {
+ window.clearTimeout(fileAtSearchTimer);
+ fileAtSearchTimer = null;
+ }
+};
+
+const updateFileAtMenuPosition = (tokenEnd: number) => {
+ nextTick(() => {
+ const instance = editor.value;
+ if (!instance) return;
+ try {
+ const coords = instance.view.coordsAtPos(tokenEnd);
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+ const menuWidth = 280;
+ const menuHeight = 250;
+ const gap = 4;
+
+ // 默认:菜单左下角贴着 @ 后面文字的右上角
+ let left = coords.right;
+ let top = coords.top - menuHeight;
+
+ if (left + menuWidth > viewportWidth - gap) {
+ left = Math.max(gap, viewportWidth - menuWidth - gap);
+ }
+
+ if (top < gap) {
+ top = coords.bottom;
+ }
+ if (top + menuHeight > viewportHeight - gap) {
+ top = Math.max(gap, viewportHeight - menuHeight - gap);
+ }
+
+ fileAtMenuStyle.value = {
+ left: `${left}px`,
+ top: `${top}px`,
+ width: `${menuWidth}px`,
+ maxHeight: `${menuHeight}px`
+ };
+ } catch {
+ fileAtMenuStyle.value = {};
+ }
+ });
+};
+
+const isImageFile = (path: string): boolean => {
+ if (!path) return false;
+ const ext = path.split('.').pop()?.toLowerCase() || '';
+ return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico'].includes(ext);
+};
+
+const fetchProjectFileSearch = async (query: string) => {
+ fileAtLoading.value = true;
+ try {
+ const url = `/api/project/files/search?q=${encodeURIComponent(query)}&limit=50`;
+ const response = await fetch(url);
+ const data = await response.json().catch(() => ({}));
+ if (!response.ok || !data?.success) {
+ fileAtItems.value = [];
+ return;
+ }
+ fileAtItems.value = Array.isArray(data?.data?.items) ? data.data.items : [];
+ } catch (error) {
+ console.warn('[FileAt] 搜索文件失败:', error);
+ fileAtItems.value = [];
+ } finally {
+ fileAtLoading.value = false;
+ }
+};
+
+const scheduleFileAtSearch = (query: string) => {
+ if (fileAtSearchTimer !== null) {
+ window.clearTimeout(fileAtSearchTimer);
+ }
+ fileAtSearchTimer = window.setTimeout(() => {
+ void fetchProjectFileSearch(query);
+ }, 120);
+};
+
+const refreshFileAtState = () => {
+ const token = findAtToken();
+ if (!token || !props.isConnected || props.inputLocked) {
+ closeFileAtMenu();
+ return;
+ }
+ closeProjectGitMenu();
+ const queryChanged = fileAtQuery.value !== token.query;
+ fileAtOpen.value = true;
+ if (queryChanged || fileAtItems.value.length === 0) {
+ fileAtActiveIndex.value = 0;
+ fileAtQuery.value = token.query;
+ scheduleFileAtSearch(token.query);
+ }
+ updateFileAtMenuPosition(token.end);
+};
+
+const insertFileAtMention = (item: FileAtItem) => {
+ const token = findAtToken();
+ const instance = editor.value;
+ if (!token || !instance) return;
+ const { state, view, schema } = instance;
+ const mentionType = schema.nodes.mention;
+ if (!mentionType) return;
+ const mentionNode = mentionType.create({
+ id: `file://${item.path}`,
+ label: item.name
+ });
+ let tr = state.tr.delete(token.start, token.end);
+ tr = tr.insert(token.start, mentionNode);
+ const afterMention = token.start + mentionNode.nodeSize;
+ tr = tr.insertText(' ', afterMention);
+ tr = tr.setSelection(TextSelection.near(tr.doc.resolve(afterMention + 1)));
+ view.dispatch(tr);
+ view.focus();
+ closeFileAtMenu();
+ nextTick(() => {
+ adjustTextareaSize();
+ });
+};
+
+const openFilePickerAndReference = () => {
+ closeFileAtMenu();
+ if (!props.hostMode) {
+ return;
+ }
+ fileAtPickerActive.value = true;
+ fileUploadInput.value?.click();
+};
+
+const selectFileAtItemByIndex = (index: number) => {
+ const hasPicker = !!props.hostMode;
+ if (hasPicker && index === 0) {
+ void openFilePickerAndReference();
+ return;
+ }
+ const fileIndex = hasPicker ? index - 1 : index;
+ const item = fileAtItems.value[fileIndex];
+ if (!item) return;
+ if (item.type === 'file' && isImageFile(item.path)) {
+ closeFileAtMenu();
+ emit('pick-images');
+ return;
+ }
+ insertFileAtMention(item);
+};
+
+const handleFileAtKeydown = (event: KeyboardEvent): boolean => {
+ if (!fileAtOpen.value) return false;
+ const hasPicker = !!props.hostMode;
+ const total = (hasPicker ? 1 : 0) + fileAtItems.value.length;
+ if (total <= 0) return false;
+
+ if (event.key === 'ArrowDown') {
+ event.preventDefault();
+ fileAtMenuRef.value?.setManualScroll(false);
+ fileAtActiveIndex.value = (fileAtActiveIndex.value + 1) % total;
+ fileAtMenuRef.value?.focusActive();
+ return true;
+ }
+ if (event.key === 'ArrowUp') {
+ event.preventDefault();
+ fileAtMenuRef.value?.setManualScroll(false);
+ fileAtActiveIndex.value = (fileAtActiveIndex.value - 1 + total) % total;
+ fileAtMenuRef.value?.focusActive();
+ return true;
+ }
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ selectFileAtItemByIndex(fileAtActiveIndex.value);
+ return true;
+ }
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ closeFileAtMenu();
+ return true;
+ }
+ return false;
+};
+
const deleteSlashToken = () => {
const token = findSlashToken();
const instance = editor.value;
@@ -822,12 +1065,15 @@ const scrollSkillSlashSelectionIntoMiddle = () => {
const styles = getComputedStyle(list);
const cssRowHeight = parseFloat(styles.getPropertyValue('--skill-slash-row-height'));
const cssGap = parseFloat(styles.getPropertyValue('--skill-slash-gap'));
- const rowHeight = (Number.isFinite(cssRowHeight) ? cssRowHeight : 38) +
- (Number.isFinite(cssGap) ? cssGap : 5);
+ const rowHeight =
+ (Number.isFinite(cssRowHeight) ? cssRowHeight : 38) + (Number.isFinite(cssGap) ? cssGap : 5);
const visibleRows = Math.max(1, Math.floor(list.clientHeight / rowHeight) || 5);
const middleOffset = Math.floor(visibleRows / 2);
const maxScroll = Math.max(0, list.scrollHeight - list.clientHeight);
- const target = Math.max(0, Math.min(maxScroll, (skillSlashActiveIndex.value - middleOffset) * rowHeight));
+ const target = Math.max(
+ 0,
+ Math.min(maxScroll, (skillSlashActiveIndex.value - middleOffset) * rowHeight)
+ );
animateSlash(list, target);
});
};
@@ -836,7 +1082,11 @@ const filteredSkills = computed(() => {
const query = skillSlashQuery.value.trim().toLowerCase();
const list = Array.isArray(availableSkills.value) ? availableSkills.value : [];
const filtered = query
- ? list.filter((skill) => String(skill.name || '').toLowerCase().includes(query))
+ ? list.filter((skill) =>
+ String(skill.name || '')
+ .toLowerCase()
+ .includes(query)
+ )
: list;
return filtered.slice(0, 100);
});
@@ -938,7 +1188,11 @@ const rootSlashMenuItems = computed(() => {
{
id: 'goal',
label: '目标模式',
- description: props.goalRunning ? '目标模式运行中' : props.goalModeArmed ? '目标模式已就绪' : '切换目标模式',
+ description: props.goalRunning
+ ? '目标模式运行中'
+ : props.goalModeArmed
+ ? '目标模式已就绪'
+ : '切换目标模式',
disabled: !props.isConnected,
action: () => emit('toggle-goal-mode')
},
@@ -979,13 +1233,15 @@ const rootSlashMenuItems = computed(() => {
mode: 'permission'
},
...(props.executionModeEnabled
- ? [{
- id: 'execution',
- label: '执行环境',
- description: '切换 sandbox / direct',
- disabled: !props.isConnected || props.streamingMessage,
- mode: 'execution'
- } as SlashMenuItem]
+ ? [
+ {
+ id: 'execution',
+ label: '执行环境',
+ description: '切换 sandbox / direct',
+ disabled: !props.isConnected || props.streamingMessage,
+ mode: 'execution'
+ } as SlashMenuItem
+ ]
: []),
{
id: 'versioning',
@@ -997,7 +1253,8 @@ const rootSlashMenuItems = computed(() => {
{
id: 'git-bar',
label: 'Git 状态栏',
- description: (personalizationStore?.form?.show_git_status_bar !== false) ? '当前:显示' : '当前:隐藏',
+ description:
+ personalizationStore?.form?.show_git_status_bar !== false ? '当前:显示' : '当前:隐藏',
action: () => {
const currentValue = personalizationStore?.form?.show_git_status_bar !== false;
const newValue = !currentValue;
@@ -1066,7 +1323,7 @@ const themeSlashMenuItems = computed(() => {
});
const permissionSlashMenuItems = computed(() => {
- const options = (props.permissionOptions || []);
+ const options = props.permissionOptions || [];
const current = String(props.currentPermissionMode || '');
return options.map((opt) => ({
id: `perm:${opt.value}`,
@@ -1077,7 +1334,7 @@ const permissionSlashMenuItems = computed(() => {
});
const executionSlashMenuItems = computed(() => {
- const options = (props.executionModeOptions || []);
+ const options = props.executionModeOptions || [];
const current = String(props.currentExecutionMode || '');
return options.map((opt) => ({
id: `exec:${opt.value}`,
@@ -1156,14 +1413,17 @@ const slashMenuAriaLabel = computed(() => {
});
const slashMenuEmptyText = computed(() => {
- if (slashMenuMode.value === 'skills') return skillsLoading.value ? '正在加载 skills...' : '没有匹配的 skill';
+ if (slashMenuMode.value === 'skills')
+ return skillsLoading.value ? '正在加载 skills...' : '没有匹配的 skill';
if (slashMenuMode.value === 'permission') return '无可用权限模式';
if (slashMenuMode.value === 'execution') return '无可用执行环境';
if (slashMenuMode.value === 'model') return '无可用模型';
return '没有匹配的指令';
});
-const skillSlashMenuOpen = computed(() => skillSlashOpen.value && (skillsLoading.value || activeSlashMenuItems.value.length >= 0));
+const skillSlashMenuOpen = computed(
+ () => skillSlashOpen.value && (skillsLoading.value || activeSlashMenuItems.value.length >= 0)
+);
const loadSkills = async () => {
if (skillsLoaded.value || skillsLoading.value) {
@@ -1210,7 +1470,10 @@ const refreshSkillSlashState = () => {
if (slashMenuMode.value === 'skills') {
void loadSkills();
}
- skillSlashActiveIndex.value = Math.min(skillSlashActiveIndex.value, Math.max(0, activeSlashMenuItems.value.length - 1));
+ skillSlashActiveIndex.value = Math.min(
+ skillSlashActiveIndex.value,
+ Math.max(0, activeSlashMenuItems.value.length - 1)
+ );
scrollSkillSlashSelectionIntoMiddle();
};
@@ -1327,10 +1590,16 @@ const handleSlashMenuKeydown = (event: KeyboardEvent): boolean => {
};
const onKeydown = (event: KeyboardEvent): boolean => {
+ if (handleFileAtKeydown(event)) {
+ return true;
+ }
if (handleSlashMenuKeydown(event)) {
return true;
}
- requestAnimationFrame(refreshSkillSlashState);
+ requestAnimationFrame(() => {
+ refreshSkillSlashState();
+ refreshFileAtState();
+ });
return false;
};
@@ -1338,6 +1607,7 @@ const onInputBlur = () => {
emit('input-blur');
window.setTimeout(() => {
closeSlashMenu();
+ closeFileAtMenu();
}, 120);
};
@@ -1365,8 +1635,15 @@ const editorJsonToMessage = (json?: JSONContent | null) => {
if (node.type === 'text') return node.text || '';
if (node.type === 'mention') {
const attrs = node.attrs || {};
- const name = String(attrs.label || attrs.id || '').trim();
- const path = String(attrs.path || attrs.id || '').trim();
+ const id = String(attrs.id || '');
+ const label = String(attrs.label || id).trim();
+ const isFile = id.startsWith('file://');
+ if (isFile) {
+ const filePath = id.slice(7);
+ return label && filePath ? `[${label}](file://${filePath})` : label;
+ }
+ const name = label;
+ const path = String(attrs.path || id).trim();
if (name && path && !skillRefs.some((item) => item.path === path)) {
skillRefs.push({ name, path });
}
@@ -1376,7 +1653,10 @@ const editorJsonToMessage = (json?: JSONContent | null) => {
return Array.isArray(node.content) ? node.content.map(readInline).join('') : '';
};
const blocks = Array.isArray(json?.content) ? json.content : [];
- const message = blocks.map((block) => readInline(block)).join('\n').trim();
+ const message = blocks
+ .map((block) => readInline(block))
+ .join('\n')
+ .trim();
return { message, skillRefs };
};
@@ -1406,12 +1686,19 @@ const editor = useEditor({
},
renderHTML({ node, HTMLAttributes }) {
const attrs = HTMLAttributes || {};
+ const id = String(node.attrs.id || '');
+ const isFile = id.startsWith('file://');
+ const className = isFile
+ ? `${attrs.class || ''} file-md-link`.trim()
+ : `${attrs.class || ''} skill-md-link`.trim();
+ const dataAttr = isFile ? 'data-file-path' : 'data-skill-path';
+ const dataValue = isFile ? id.slice(7) : node.attrs.path || node.attrs.id || '';
return [
'span',
{
...attrs,
- class: `${attrs.class || ''} skill-md-link`.trim(),
- 'data-skill-path': node.attrs.path || node.attrs.id || ''
+ class: className,
+ [dataAttr]: dataValue
},
String(node.attrs.label || node.attrs.id || '')
];
@@ -1442,6 +1729,7 @@ const editor = useEditor({
nextTick(() => {
adjustTextareaSize();
refreshSkillSlashState();
+ refreshFileAtState();
});
},
onFocus() {
@@ -1492,12 +1780,19 @@ const setupVoiceBridgeCallbacks = () => {
return;
}
bridge?.debugLog('__onVoiceResult: editor.value 存在,准备插入文字');
- bridge?.debugLog('__onVoiceResult: voiceAnchorPos=' + voiceAnchorPos + ' lastVoiceTextLength=' + lastVoiceTextLength);
+ bridge?.debugLog(
+ '__onVoiceResult: voiceAnchorPos=' +
+ voiceAnchorPos +
+ ' lastVoiceTextLength=' +
+ lastVoiceTextLength
+ );
const { state, view } = ed;
const docSize = state.doc.content.size;
const safeAnchor = Math.max(0, Math.min(voiceAnchorPos, docSize));
const oldEnd = Math.min(safeAnchor + lastVoiceTextLength, docSize);
- bridge?.debugLog('__onVoiceResult: docSize=' + docSize + ' safeAnchor=' + safeAnchor + ' oldEnd=' + oldEnd);
+ bridge?.debugLog(
+ '__onVoiceResult: docSize=' + docSize + ' safeAnchor=' + safeAnchor + ' oldEnd=' + oldEnd
+ );
try {
let tr = state.tr;
if (oldEnd > safeAnchor) tr = tr.delete(safeAnchor, oldEnd);
@@ -1575,7 +1870,7 @@ const isWebSpeechSupported = computed(() => {
const getSpeechRecognitionConstructor = (): typeof SpeechRecognition | null => {
if (typeof window === 'undefined') return null;
const Ctor = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition || null;
- console.log('[VoiceInput] 构造函数:', Ctor ? (Ctor.name || 'webkitSpeechRecognition') : 'null');
+ console.log('[VoiceInput] 构造函数:', Ctor ? Ctor.name || 'webkitSpeechRecognition' : 'null');
return Ctor;
};
@@ -1623,7 +1918,11 @@ const initSpeechRecognition = () => {
let fullText = '';
for (let i = 0; i < event.results.length; i++) {
const r = event.results[i];
- console.log(`[VoiceInput] result[${i}]:`, { transcript: r[0]?.transcript, isFinal: r.isFinal, confidence: r[0]?.confidence });
+ console.log(`[VoiceInput] result[${i}]:`, {
+ transcript: r[0]?.transcript,
+ isFinal: r.isFinal,
+ confidence: r[0]?.confidence
+ });
fullText += r[0].transcript;
}
@@ -1806,7 +2105,10 @@ const keepRuntimeQueueTransitionCollapsed = () => {
const cancelAnim = (el: HTMLElement) => {
const timer = runtimeQueueFallbackTimers.get(el);
- if (timer) { clearTimeout(timer); runtimeQueueFallbackTimers.delete(el); }
+ if (timer) {
+ clearTimeout(timer);
+ runtimeQueueFallbackTimers.delete(el);
+ }
el.style.removeProperty('transition');
el.style.removeProperty('transform');
el.style.removeProperty('opacity');
@@ -1831,14 +2133,18 @@ const scheduleTransition = (
};
// Apply from state immediately (no transition)
el.style.transition = 'none';
- Object.entries(from).forEach(([k, v]) => { el.style.setProperty(k, v); });
+ Object.entries(from).forEach(([k, v]) => {
+ el.style.setProperty(k, v);
+ });
// Defer to next frame: enable transition + apply to state
// All scheduleTransition calls in the same microtask share this RAF
requestAnimationFrame(() => {
el.style.transition = props
.map((p) => `${p} ${RUNTIME_QUEUE_ANIM_DURATION}ms ${RUNTIME_QUEUE_ANIM_EASING}`)
.join(', ');
- Object.entries(to).forEach(([k, v]) => { el.style.setProperty(k, v); });
+ Object.entries(to).forEach(([k, v]) => {
+ el.style.setProperty(k, v);
+ });
const timer = window.setTimeout(finish, RUNTIME_QUEUE_ANIM_DURATION + 40);
runtimeQueueFallbackTimers.set(el, timer);
});
@@ -1894,7 +2200,11 @@ onUpdated(() => {
// Clean up any stale ghost with same id
const existing = runtimeQueueGhosts.get(id);
- if (existing) { cancelAnim(existing); existing.remove(); runtimeQueueGhosts.delete(id); }
+ if (existing) {
+ cancelAnim(existing);
+ existing.remove();
+ runtimeQueueGhosts.delete(id);
+ }
const ghost = src.cloneNode(true) as HTMLElement;
const lr = list.getBoundingClientRect();
@@ -1914,10 +2224,15 @@ onUpdated(() => {
// Schedule ghost sink — shares RAF with item animations below
const offset = resolveRuntimeQueueOffset(ghost);
- scheduleTransition(ghost, ['transform'],
+ scheduleTransition(
+ ghost,
+ ['transform'],
{ transform: 'translateY(0)' },
{ transform: `translateY(${offset}px)` },
- () => { runtimeQueueGhosts.delete(id); ghost.remove(); }
+ () => {
+ runtimeQueueGhosts.delete(id);
+ ghost.remove();
+ }
);
runtimeQueueItemRefs.delete(id);
@@ -1932,7 +2247,9 @@ onUpdated(() => {
if (enteringIds.includes(id)) {
// New item: rise from below
const offset = resolveRuntimeQueueOffset(el);
- scheduleTransition(el, ['transform'],
+ scheduleTransition(
+ el,
+ ['transform'],
{ transform: `translateY(${offset}px)` },
{ transform: 'translateY(0)' }
);
@@ -1945,7 +2262,9 @@ onUpdated(() => {
const next = el.getBoundingClientRect();
const deltaY = prev.top - next.top;
if (Math.abs(deltaY) < 0.5) return;
- scheduleTransition(el, ['transform'],
+ scheduleTransition(
+ el,
+ ['transform'],
{ transform: `translateY(${deltaY}px)` },
{ transform: 'translateY(0)' }
);
@@ -1990,10 +2309,23 @@ const hasRuntimeLayoutExpansion = computed(() => {
const hasQueue = runtimeQueuedMessagesForRender.value.length > 0;
const hasImages = Array.isArray(props.selectedImages) && props.selectedImages.length > 0;
const hasVideos = Array.isArray(props.selectedVideos) && props.selectedVideos.length > 0;
- return hasQueue || floatingStatusVisible.value || skillSlashOpen.value || hasImages || hasVideos || !!props.inputIsMultiline || !!props.goalModeArmed || !!props.goalRunning || goalCompleted.value;
+ return (
+ hasQueue ||
+ floatingStatusVisible.value ||
+ skillSlashOpen.value ||
+ fileAtOpen.value ||
+ hasImages ||
+ hasVideos ||
+ !!props.inputIsMultiline ||
+ !!props.goalModeArmed ||
+ !!props.goalRunning ||
+ goalCompleted.value
+ );
});
-const goalCompleted = computed(() => String(props.goalProgress?.status || '').toLowerCase() === 'done');
+const goalCompleted = computed(
+ () => String(props.goalProgress?.status || '').toLowerCase() === 'done'
+);
const handleSlashMenuTransitionStart = () => {
slashMenuTransitioning.value = true;
@@ -2047,7 +2379,9 @@ const collectComposerVisualHeight = () => {
}
// 收起态横幅是脱离流、浮在角上的小圆点,不应计入为聊天区预留的高度,
// 否则会把消息区可滚动范围顶高。故排除 .goal-mode-banner--collapsed。
- const nodes = root.querySelectorAll('.runtime-queue-list:not(.runtime-queue-list--empty), .floating-project-status');
+ const nodes = root.querySelectorAll(
+ '.runtime-queue-list:not(.runtime-queue-list--empty), .floating-project-status'
+ );
nodes.forEach((node) => {
if (!(node instanceof HTMLElement)) return;
// 离开态:floatingStatusVisible 已翻 false 但元素仍在 DOM 中做退场动画。
@@ -2175,9 +2509,58 @@ const onInput = (event: Event) => {
nextTick(refreshSkillSlashState);
};
-const onFileChange = (event: Event) => {
+const insertFileAtMentionByPath = (path: string, name: string) => {
+ const token = findAtToken();
+ const instance = editor.value;
+ if (!instance || !token) return;
+ const mentionType = instance.schema.nodes.mention;
+ if (!mentionType) return;
+ const mentionNode = mentionType.create({
+ id: `file://${path}`,
+ label: name
+ });
+ const { state, view } = instance;
+ let tr = state.tr.delete(token.start, token.end);
+ tr = tr.insert(token.start, mentionNode);
+ const afterMention = token.start + mentionNode.nodeSize;
+ tr = tr.insertText(' ', afterMention);
+ tr = tr.setSelection(TextSelection.near(tr.doc.resolve(afterMention + 1)));
+ view.dispatch(tr);
+ view.focus();
+ closeFileAtMenu();
+ nextTick(() => {
+ adjustTextareaSize();
+ });
+};
+
+const onFileChange = async (event: Event) => {
const target = event.target as HTMLInputElement;
- emit('file-selected', target?.files || null);
+ const files = target?.files || null;
+
+ if (fileAtPickerActive.value && files && files.length > 0) {
+ fileAtPickerActive.value = false;
+ const file = files[0];
+ try {
+ const response = await fetch(
+ `/api/project/files/search?q=${encodeURIComponent(file.name)}&limit=10`
+ );
+ const data = await response.json().catch(() => ({}));
+ const items = Array.isArray(data?.data?.items) ? data.data.items : [];
+ const match = items.find((item: FileAtItem) => item.name === file.name) || items[0];
+ if (match) {
+ insertFileAtMention(match);
+ } else {
+ insertFileAtMentionByPath(file.name, file.name);
+ }
+ } catch (error) {
+ console.warn('[FileAt] 通过文件选择器查找路径失败:', error);
+ insertFileAtMentionByPath(file.name, file.name);
+ }
+ if (target) target.value = '';
+ return;
+ }
+
+ emit('file-selected', files);
if (target) {
target.value = '';
}
@@ -2190,7 +2573,9 @@ const getComposerDraftMeta = () => ({
skill_refs: editorJsonToMessage(editor.value?.getJSON() as JSONContent).skillRefs
});
-const restoreComposerDraftMeta = (meta?: { skill_refs?: SkillItem[]; editor_json?: JSONContent } | null) => {
+const restoreComposerDraftMeta = (
+ meta?: { skill_refs?: SkillItem[]; editor_json?: JSONContent } | null
+) => {
const instance = editor.value;
if (!instance) return;
if (meta?.editor_json && typeof meta.editor_json === 'object') {
@@ -2298,7 +2683,11 @@ watch(
() => props.inputMessage,
async () => {
const instance = editor.value;
- if (instance && !syncingEditorFromProps && getEditorPlainText() !== (props.inputMessage || '')) {
+ if (
+ instance &&
+ !syncingEditorFromProps &&
+ getEditorPlainText() !== (props.inputMessage || '')
+ ) {
syncingEditorFromProps = true;
instance.commands.setContent(textToTiptapContent(props.inputMessage || ''));
syncingEditorFromProps = false;
@@ -2510,7 +2899,9 @@ onBeforeUnmount(() => {
min-width: 0;
padding: 0 9px;
cursor: pointer;
- transition: background 140ms ease, color 140ms ease;
+ transition:
+ background 140ms ease,
+ color 140ms ease;
}
.floating-project-status__button:hover,
@@ -2554,7 +2945,9 @@ onBeforeUnmount(() => {
line-height: 1.2;
font-variant-numeric: tabular-nums;
cursor: pointer;
- transition: background 140ms ease, color 140ms ease;
+ transition:
+ background 140ms ease,
+ color 140ms ease;
}
.floating-project-status__stats:hover {
@@ -2586,7 +2979,9 @@ onBeforeUnmount(() => {
font-size: 12px;
line-height: 1.2;
cursor: pointer;
- transition: background 140ms ease, color 140ms ease;
+ transition:
+ background 140ms ease,
+ color 140ms ease;
}
.floating-project-status__terminal:hover {
@@ -2689,7 +3084,9 @@ onBeforeUnmount(() => {
background: color-mix(in srgb, var(--theme-surface-soft) 92%, var(--claude-text-tertiary) 8%);
border: 1px solid var(--theme-chip-border);
user-select: none;
- transition: gap 0.2s ease, padding 0.2s ease;
+ transition:
+ gap 0.2s ease,
+ padding 0.2s ease;
}
/* 收起态:仅保留圆点与外圈,文字消失、右侧收拢,圆点位置(左侧 padding)不变。
关键:切到 absolute 脱离文档流,避免横幅占用一行流高度把队列/skill 菜单顶起来;
@@ -2708,7 +3105,9 @@ onBeforeUnmount(() => {
white-space: nowrap;
max-width: 200px;
opacity: 1;
- transition: max-width 0.2s ease, opacity 0.18s ease;
+ transition:
+ max-width 0.2s ease,
+ opacity 0.18s ease;
}
.goal-mode-banner--collapsed .goal-mode-banner__text {
max-width: 0;
@@ -2759,12 +3158,19 @@ onBeforeUnmount(() => {
background: var(--claude-success);
}
@keyframes goal-banner-pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.35; }
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.35;
+ }
}
.goal-banner-fade-enter-active,
.goal-banner-fade-leave-active {
- transition: opacity 0.18s ease, transform 0.18s ease;
+ transition:
+ opacity 0.18s ease,
+ transform 0.18s ease;
}
.goal-banner-fade-enter-from,
.goal-banner-fade-leave-to {
diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss
index c77a77b..b66d925 100644
--- a/static/src/styles/components/chat/_chat-area.scss
+++ b/static/src/styles/components/chat/_chat-area.scss
@@ -662,7 +662,8 @@
opacity: 0.9;
}
-.user-message .message-text .user-skill-link {
+.user-message .message-text .user-skill-link,
+.user-message .message-text .user-file-link {
color: var(--state-info);
font-weight: 600;
display: inline-block;
diff --git a/static/src/styles/components/input/_composer.scss b/static/src/styles/components/input/_composer.scss
index b7d8021..ae8a83c 100644
--- a/static/src/styles/components/input/_composer.scss
+++ b/static/src/styles/components/input/_composer.scss
@@ -1,1404 +1,1421 @@
/* 输入区域 */
.input-area {
- position: absolute;
- left: 0;
- right: 0;
- bottom: 32px;
- background: transparent;
- padding: 0 24px;
- flex-shrink: 0;
- pointer-events: none;
- z-index: 30;
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 32px;
+ background: transparent;
+ padding: 0 24px;
+ flex-shrink: 0;
+ pointer-events: none;
+ z-index: 30;
}
.compact-input-area {
- display: flex;
- justify-content: center;
- padding-bottom: 0;
- pointer-events: none;
+ display: flex;
+ justify-content: center;
+ padding-bottom: 0;
+ pointer-events: none;
}
.stadium-input-wrapper {
- position: relative;
- width: var(--chat-rail-width, min(900px, 94%));
- pointer-events: auto;
+ position: relative;
+ width: var(--chat-rail-width, min(900px, 94%));
+ pointer-events: auto;
}
.runtime-queue-list {
- --runtime-queue-bg: var(--theme-surface-soft);
- --runtime-queue-divider: rgba(15, 23, 42, 0.12);
- --runtime-queue-border: rgba(15, 23, 42, 0.12);
- position: absolute;
- left: 50%;
- bottom: calc(100% + 1px);
- transform: translateX(-50%);
- z-index: 1;
- display: flex;
- flex-direction: column;
- width: 90%;
- max-width: 90%;
- margin: 0;
- overflow: visible;
- background: transparent;
- border: none;
- border-radius: 0;
- box-shadow: none;
- pointer-events: auto;
+ --runtime-queue-bg: var(--theme-surface-soft);
+ --runtime-queue-divider: rgba(15, 23, 42, 0.12);
+ --runtime-queue-border: rgba(15, 23, 42, 0.12);
+ position: absolute;
+ left: 50%;
+ bottom: calc(100% + 1px);
+ transform: translateX(-50%);
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ width: 90%;
+ max-width: 90%;
+ margin: 0;
+ overflow: visible;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ pointer-events: auto;
}
.runtime-queue-list--empty {
- height: 0;
- border: none;
- box-shadow: none;
- background: transparent;
- margin: 0;
- overflow: visible;
- pointer-events: none;
+ height: 0;
+ border: none;
+ box-shadow: none;
+ background: transparent;
+ margin: 0;
+ overflow: visible;
+ pointer-events: none;
}
.runtime-queue-item {
- position: relative;
- z-index: var(--runtime-queue-z, 1);
- min-height: 34px;
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- align-items: center;
- gap: 10px;
- padding: 6px 10px 6px 12px;
- border-left: 1px solid var(--runtime-queue-border);
- border-right: 1px solid var(--runtime-queue-border);
- border-top: 1px solid var(--runtime-queue-border);
- border-bottom: none;
- border-radius: 0;
- background: var(--runtime-queue-bg);
- overflow: hidden;
+ position: relative;
+ z-index: var(--runtime-queue-z, 1);
+ min-height: 34px;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 10px 6px 12px;
+ border-left: 1px solid var(--runtime-queue-border);
+ border-right: 1px solid var(--runtime-queue-border);
+ border-top: 1px solid var(--runtime-queue-border);
+ border-bottom: none;
+ border-radius: 0;
+ background: var(--runtime-queue-bg);
+ overflow: hidden;
}
.runtime-queue-item--top {
- border-top-left-radius: 12px;
- border-top-right-radius: 12px;
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
}
.runtime-queue-item__text {
- flex: 1 1 auto;
- min-width: 0;
- display: block;
- font-size: 13px;
- line-height: 1.3;
- color: var(--claude-text);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ flex: 1 1 auto;
+ min-width: 0;
+ display: block;
+ font-size: 13px;
+ line-height: 1.3;
+ color: var(--claude-text);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.runtime-queue-item__action {
- flex-shrink: 0;
- border: none;
- background: transparent;
- color: var(--claude-text-secondary);
- font-size: 12px;
- line-height: 1.2;
- padding: 3px 4px;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- z-index: 1;
+ flex-shrink: 0;
+ border: none;
+ background: transparent;
+ color: var(--claude-text-secondary);
+ font-size: 12px;
+ line-height: 1.2;
+ padding: 3px 4px;
+ border-radius: 6px;
+ cursor: pointer;
+ position: relative;
+ z-index: 1;
}
.runtime-queue-item__action:hover {
- background: var(--theme-chip-bg);
- color: var(--claude-text);
+ background: var(--theme-chip-bg);
+ color: var(--claude-text);
}
.runtime-queue-item__action--danger:hover {
- color: var(--state-danger-strong);
- background: color-mix(in srgb, var(--state-danger) 12%, transparent);
+ color: var(--state-danger-strong);
+ background: color-mix(in srgb, var(--state-danger) 12%, transparent);
}
.runtime-queue-item + .runtime-queue-item::before {
- content: '';
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- height: 1px;
- background: var(--runtime-queue-divider);
- pointer-events: none;
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ height: 1px;
+ background: var(--runtime-queue-divider);
+ pointer-events: none;
}
.skill-slash-menu-wrapper {
- --skill-slash-row-height: 28px;
- --skill-slash-gap: 3px;
- --skill-slash-radius: 9px;
- --skill-slash-pad-x: 4px;
- --skill-slash-motion-duration: 300ms;
- --skill-slash-motion-easing: cubic-bezier(0.25, 0.8, 0.25, 1);
- --skill-slash-visible-rows: 7;
- position: absolute;
- left: 50%;
- bottom: calc(100% - 1px);
- transform: translateX(-50%);
- z-index: 1;
- width: 90%;
- max-width: 90%;
- box-sizing: border-box;
- max-height: calc(
- (var(--skill-slash-row-height) * var(--skill-slash-visible-rows)) +
- (var(--skill-slash-gap) * (var(--skill-slash-visible-rows) + 1)) +
- 2px + 2px - 2px
- );
- border: 1px solid var(--claude-border);
- border-top-left-radius: var(--skill-slash-radius);
- border-top-right-radius: var(--skill-slash-radius);
- background: var(--surface-soft);
- box-shadow: none;
- pointer-events: auto;
+ --skill-slash-row-height: 28px;
+ --skill-slash-gap: 3px;
+ --skill-slash-radius: 9px;
+ --skill-slash-pad-x: 4px;
+ --skill-slash-motion-duration: 300ms;
+ --skill-slash-motion-easing: cubic-bezier(0.25, 0.8, 0.25, 1);
+ --skill-slash-visible-rows: 7;
+ position: absolute;
+ left: 50%;
+ bottom: calc(100% - 1px);
+ transform: translateX(-50%);
+ z-index: 1;
+ width: 90%;
+ max-width: 90%;
+ box-sizing: border-box;
+ max-height: calc(
+ (var(--skill-slash-row-height) * var(--skill-slash-visible-rows)) +
+ (var(--skill-slash-gap) * (var(--skill-slash-visible-rows) + 1)) + 2px + 2px - 2px
+ );
+ border: 1px solid var(--claude-border);
+ border-top-left-radius: var(--skill-slash-radius);
+ border-top-right-radius: var(--skill-slash-radius);
+ background: var(--surface-soft);
+ box-shadow: none;
+ pointer-events: auto;
}
.skill-slash-menu-motion-enter-active,
.skill-slash-menu-motion-leave-active {
- transition:
- transform var(--skill-slash-motion-duration, 300ms) var(--skill-slash-motion-easing, cubic-bezier(0.25, 0.8, 0.25, 1)),
- clip-path var(--skill-slash-motion-duration, 300ms) var(--skill-slash-motion-easing, cubic-bezier(0.25, 0.8, 0.25, 1));
- transform-origin: bottom center;
- will-change: transform, clip-path;
+ transition:
+ transform var(--skill-slash-motion-duration, 300ms)
+ var(--skill-slash-motion-easing, cubic-bezier(0.25, 0.8, 0.25, 1)),
+ clip-path var(--skill-slash-motion-duration, 300ms)
+ var(--skill-slash-motion-easing, cubic-bezier(0.25, 0.8, 0.25, 1));
+ transform-origin: bottom center;
+ will-change: transform, clip-path;
}
.skill-slash-menu-motion-enter-active,
.skill-slash-menu-motion-leave-active {
- z-index: 1;
+ z-index: 1;
}
.skill-slash-menu-motion-enter-from,
.skill-slash-menu-motion-leave-to {
- transform: translateX(-50%) translateY(100%);
- clip-path: inset(0 0 100% 0 round 12px 12px 0 0);
+ transform: translateX(-50%) translateY(100%);
+ clip-path: inset(0 0 100% 0 round 12px 12px 0 0);
}
.skill-slash-menu-motion-enter-to,
.skill-slash-menu-motion-leave-from {
- transform: translateX(-50%) translateY(0);
- clip-path: inset(0 0 0 0 round 12px 12px 0 0);
+ transform: translateX(-50%) translateY(0);
+ clip-path: inset(0 0 0 0 round 12px 12px 0 0);
}
.skill-slash-menu {
- height: 100%;
- max-height: inherit;
- overflow-y: auto;
- scrollbar-width: none;
- padding: 4px var(--skill-slash-pad-x) 6px;
- display: flex;
- flex-direction: column;
- gap: var(--skill-slash-gap);
+ height: 100%;
+ max-height: inherit;
+ overflow-y: auto;
+ scrollbar-width: none;
+ padding: 4px var(--skill-slash-pad-x) 6px;
+ display: flex;
+ flex-direction: column;
+ gap: var(--skill-slash-gap);
}
.skill-slash-menu::-webkit-scrollbar {
- width: 0;
- height: 0;
+ width: 0;
+ height: 0;
}
.skill-slash-menu__highlight {
- position: absolute;
- left: var(--skill-slash-pad-x);
- right: var(--skill-slash-pad-x);
- top: var(--skill-slash-gap);
- height: var(--skill-slash-row-height);
- border-radius: 7px;
- background: var(--hover-bg);
- box-shadow: 0 1px 2px var(--shadow-color);
- pointer-events: none;
- z-index: 1;
+ position: absolute;
+ left: var(--skill-slash-pad-x);
+ right: var(--skill-slash-pad-x);
+ top: var(--skill-slash-gap);
+ height: var(--skill-slash-row-height);
+ border-radius: 7px;
+ background: var(--hover-bg);
+ box-shadow: 0 1px 2px var(--shadow-color);
+ pointer-events: none;
+ z-index: 1;
}
.skill-slash-item {
- width: 100%;
- height: var(--skill-slash-row-height);
- min-height: var(--skill-slash-row-height);
- flex: 0 0 var(--skill-slash-row-height);
- display: flex;
- align-items: center;
- gap: 7px;
- border: none;
- border-radius: 7px;
- padding: 3px 9px;
- background: transparent;
- color: var(--claude-text);
- text-align: left;
- cursor: pointer;
+ width: 100%;
+ height: var(--skill-slash-row-height);
+ min-height: var(--skill-slash-row-height);
+ flex: 0 0 var(--skill-slash-row-height);
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ border: none;
+ border-radius: 7px;
+ padding: 3px 9px;
+ background: transparent;
+ color: var(--claude-text);
+ text-align: left;
+ cursor: pointer;
}
.skill-slash-item:first-child {
- border-top-left-radius: calc(var(--skill-slash-radius) - var(--skill-slash-gap));
- border-top-right-radius: calc(var(--skill-slash-radius) - var(--skill-slash-gap));
+ border-top-left-radius: calc(var(--skill-slash-radius) - var(--skill-slash-gap));
+ border-top-right-radius: calc(var(--skill-slash-radius) - var(--skill-slash-gap));
}
.skill-slash-item:hover {
- background: var(--hover-bg);
- box-shadow: 0 1px 2px var(--shadow-color);
+ background: var(--hover-bg);
+ box-shadow: 0 1px 2px var(--shadow-color);
}
.skill-slash-item--active {
- background: transparent;
- box-shadow: none;
+ background: transparent;
+ box-shadow: none;
}
.skill-slash-item--active:hover {
- background: transparent;
- box-shadow: none;
+ background: transparent;
+ box-shadow: none;
}
.skill-slash-item--disabled,
.skill-slash-item:disabled {
- opacity: 0.45;
- cursor: not-allowed;
+ opacity: 0.45;
+ cursor: not-allowed;
}
.skill-slash-item--disabled:hover,
.skill-slash-item:disabled:hover {
- background: transparent;
- box-shadow: none;
+ background: transparent;
+ box-shadow: none;
}
.skill-slash-item__name {
- flex: 0 0 auto;
- max-width: 160px;
- color: var(--accent);
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ flex: 0 0 auto;
+ max-width: 160px;
+ color: var(--accent);
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.skill-slash-item__description {
- flex: 1 1 auto;
- min-width: 0;
- color: var(--claude-text-secondary);
- font-size: 12px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ flex: 1 1 auto;
+ min-width: 0;
+ color: var(--claude-text-secondary);
+ font-size: 12px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.skill-slash-empty {
- height: var(--skill-slash-row-height);
- min-height: var(--skill-slash-row-height);
- flex: 0 0 var(--skill-slash-row-height);
- display: flex;
- align-items: center;
- padding: 3px 9px;
- color: var(--claude-text-secondary);
- font-size: 11px;
+ height: var(--skill-slash-row-height);
+ min-height: var(--skill-slash-row-height);
+ flex: 0 0 var(--skill-slash-row-height);
+ display: flex;
+ align-items: center;
+ padding: 3px 9px;
+ color: var(--claude-text-secondary);
+ font-size: 11px;
}
body[data-theme='dark'] {
- .runtime-queue-list {
- --runtime-queue-bg: var(--badge-bg);
- --runtime-queue-divider: rgba(255, 255, 255, 0.12);
- --runtime-queue-border: rgba(255, 255, 255, 0.12);
- background: transparent;
- }
+ .runtime-queue-list {
+ --runtime-queue-bg: var(--badge-bg);
+ --runtime-queue-divider: rgba(255, 255, 255, 0.12);
+ --runtime-queue-border: rgba(255, 255, 255, 0.12);
+ background: transparent;
+ }
- .runtime-queue-item {
- background: var(--runtime-queue-bg);
- }
+ .runtime-queue-item {
+ background: var(--runtime-queue-bg);
+ }
- .runtime-queue-list.runtime-queue-list--empty {
- background: transparent;
- border: none;
- box-shadow: none;
- }
+ .runtime-queue-list.runtime-queue-list--empty {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ }
- .skill-slash-menu-wrapper {
- background: var(--badge-bg);
- border-color: var(--claude-border);
- }
+ .skill-slash-menu-wrapper {
+ background: var(--badge-bg);
+ border-color: var(--claude-border);
+ }
- .skill-slash-item__name {
- color: var(--state-info);
- }
+ .skill-slash-item__name {
+ color: var(--state-info);
+ }
- .skill-slash-item:hover {
- background: var(--hover-bg);
- box-shadow: 0 1px 2px var(--shadow-color);
- }
+ .skill-slash-item:hover {
+ background: var(--hover-bg);
+ box-shadow: 0 1px 2px var(--shadow-color);
+ }
- .skill-slash-item--active {
- background: transparent;
- box-shadow: none;
- }
+ .skill-slash-item--active {
+ background: transparent;
+ box-shadow: none;
+ }
- .skill-slash-item--active:hover {
- background: transparent;
- box-shadow: none;
- }
+ .skill-slash-item--active:hover {
+ background: transparent;
+ box-shadow: none;
+ }
- .stadium-input-editor .skill-md-link,
- .stadium-input-highlight .skill-md-link {
- color: var(--state-info);
- }
+ .stadium-input-editor .skill-md-link,
+ .stadium-input-highlight .skill-md-link {
+ color: var(--state-info);
+ }
}
body[data-theme='light'] {
- .skill-slash-item__name {
- color: var(--state-info);
- }
+ .skill-slash-item__name {
+ color: var(--state-info);
+ }
- .stadium-input-editor .skill-md-link,
- .stadium-input-highlight .skill-md-link {
- color: var(--state-info);
- }
+ .stadium-input-editor .skill-md-link,
+ .stadium-input-highlight .skill-md-link {
+ color: var(--state-info);
+ }
}
.permission-switcher {
- --composer-side-control-offset: 20px;
- position: absolute;
- left: var(--composer-side-control-offset);
- bottom: -28px;
- z-index: 35;
- display: inline-flex;
- align-items: center;
- gap: 6px;
+ --composer-side-control-offset: 20px;
+ position: absolute;
+ left: var(--composer-side-control-offset);
+ bottom: -28px;
+ z-index: 35;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
}
.permission-switcher__block {
- position: relative;
+ position: relative;
}
.context-usage-switcher {
- --composer-side-control-offset: 20px;
- --context-ring-visual-inset: 5px;
- position: absolute;
- right: calc(var(--composer-side-control-offset) - var(--context-ring-visual-inset));
- bottom: -28px;
- z-index: 35;
+ --composer-side-control-offset: 20px;
+ --context-ring-visual-inset: 5px;
+ position: absolute;
+ right: calc(var(--composer-side-control-offset) - var(--context-ring-visual-inset));
+ bottom: -28px;
+ z-index: 35;
}
.context-usage-switcher__btn {
- border: none;
- background: transparent;
- padding: 0;
- width: 28px;
- height: 28px;
- border-radius: 999px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: none;
+ border: none;
+ background: transparent;
+ padding: 0;
+ width: 28px;
+ height: 28px;
+ border-radius: 999px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ box-shadow: none;
}
.context-usage-switcher__btn:hover:not(:disabled) {
- background: var(--theme-chip-bg);
+ background: var(--theme-chip-bg);
}
.context-usage-switcher__btn:disabled {
- opacity: 0.45;
- cursor: not-allowed;
+ opacity: 0.45;
+ cursor: not-allowed;
}
.context-usage-ring {
- --context-progress: 0%;
- --context-ring-color: var(--claude-accent);
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background:
- conic-gradient(var(--context-ring-color) var(--context-progress), var(--progress-track) 0);
- display: inline-flex;
- align-items: center;
- justify-content: center;
+ --context-progress: 0%;
+ --context-ring-color: var(--claude-accent);
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: conic-gradient(
+ var(--context-ring-color) var(--context-progress),
+ var(--progress-track) 0
+ );
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
.context-usage-ring__inner {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- background: var(--theme-surface-soft);
- border: 1px solid var(--theme-control-border);
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: var(--theme-surface-soft);
+ border: 1px solid var(--theme-control-border);
}
.context-usage-switcher__tooltip {
- position: absolute;
- right: 0;
- bottom: calc(100% + 8px);
- min-width: 94px;
- padding: 6px 8px;
- border-radius: 10px;
- border: 1px solid var(--theme-control-border);
- background: var(--theme-surface-soft);
- box-shadow: none;
- color: var(--claude-text);
- font-size: 12px;
- line-height: 1.35;
- opacity: 0;
- transform: translateY(4px);
- pointer-events: none;
- transition: opacity 0.16s ease, transform 0.16s ease;
+ position: absolute;
+ right: 0;
+ bottom: calc(100% + 8px);
+ min-width: 94px;
+ padding: 6px 8px;
+ border-radius: 10px;
+ border: 1px solid var(--theme-control-border);
+ background: var(--theme-surface-soft);
+ box-shadow: none;
+ color: var(--claude-text);
+ font-size: 12px;
+ line-height: 1.35;
+ opacity: 0;
+ transform: translateY(4px);
+ pointer-events: none;
+ transition:
+ opacity 0.16s ease,
+ transform 0.16s ease;
}
.context-usage-switcher:hover .context-usage-switcher__tooltip {
- opacity: 1;
- transform: translateY(0);
+ opacity: 1;
+ transform: translateY(0);
}
.permission-switcher__btn {
- border: none;
- background: transparent;
- color: var(--claude-text);
- border-radius: 999px;
- padding: 4px 12px;
- font-size: 14px;
- line-height: 1.2;
- cursor: pointer;
- box-shadow: none;
- text-decoration: none;
- display: inline-flex;
- align-items: center;
- gap: 6px;
+ border: none;
+ background: transparent;
+ color: var(--claude-text);
+ border-radius: 999px;
+ padding: 4px 12px;
+ font-size: 14px;
+ line-height: 1.2;
+ cursor: pointer;
+ box-shadow: none;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
}
.permission-switcher__btn:hover:not(:disabled) {
- color: var(--claude-text);
- text-decoration: none;
- background: var(--theme-chip-bg);
- box-shadow: none;
+ color: var(--claude-text);
+ text-decoration: none;
+ background: var(--theme-chip-bg);
+ box-shadow: none;
}
.permission-switcher__btn:disabled {
- opacity: 0.45;
- cursor: not-allowed;
+ opacity: 0.45;
+ cursor: not-allowed;
}
.permission-switcher__menu {
- position: absolute;
- left: 0;
- bottom: calc(100% + 8px);
- width: 250px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding: 8px;
- border: 1px solid var(--theme-control-border);
- border-radius: 14px;
- background: var(--theme-surface-soft);
- box-shadow: none;
+ position: absolute;
+ left: 0;
+ bottom: calc(100% + 8px);
+ width: 250px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 8px;
+ border: 1px solid var(--theme-control-border);
+ border-radius: 14px;
+ background: var(--theme-surface-soft);
+ box-shadow: none;
}
.permission-switcher__menu--split {
- width: 560px;
- max-width: min(560px, calc(100vw - 40px));
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
+ width: 560px;
+ max-width: min(560px, calc(100vw - 40px));
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
- > :first-child.permission-switcher__group {
- grid-row: span 2;
- }
+ > :first-child.permission-switcher__group {
+ grid-row: span 2;
+ }
}
.permission-switcher__group {
- display: flex;
- flex-direction: column;
- gap: 6px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
- &--disabled {
- opacity: 0.35;
- pointer-events: none;
- }
+ &--disabled {
+ opacity: 0.35;
+ pointer-events: none;
+ }
}
.permission-switcher__group-title {
- font-size: 12px;
- color: var(--claude-text-secondary);
- font-weight: 600;
- padding: 2px 4px;
+ font-size: 12px;
+ color: var(--claude-text-secondary);
+ font-weight: 600;
+ padding: 2px 4px;
}
.permission-switcher__caret {
- font-size: 14px;
- color: var(--claude-text-secondary);
- transform: rotate(90deg);
- transition: transform 0.18s ease;
+ font-size: 14px;
+ color: var(--claude-text-secondary);
+ transform: rotate(90deg);
+ transition: transform 0.18s ease;
}
.permission-switcher__caret.open {
- transform: rotate(270deg);
+ transform: rotate(270deg);
}
.permission-switcher__item {
- border: none;
- background: transparent;
- text-align: left;
- border-radius: 10px;
- padding: 8px 10px;
- cursor: pointer;
- color: var(--claude-text);
- display: flex;
- flex-direction: column;
- gap: 2px;
+ border: none;
+ background: transparent;
+ text-align: left;
+ border-radius: 10px;
+ padding: 8px 10px;
+ cursor: pointer;
+ color: var(--claude-text);
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
.permission-switcher__item:hover {
- background: var(--theme-chip-bg);
+ background: var(--theme-chip-bg);
}
.permission-switcher__item.active {
- background: var(--theme-tab-active);
+ background: var(--theme-tab-active);
}
.permission-switcher__item-label {
- font-size: 13px;
- font-weight: 600;
+ font-size: 13px;
+ font-weight: 600;
}
.permission-switcher__item-desc {
- font-size: 11px;
- color: var(--claude-text-secondary);
- line-height: 1.35;
+ font-size: 11px;
+ color: var(--claude-text-secondary);
+ line-height: 1.35;
}
.permission-switcher__item-label--warn {
- color: var(--state-warning);
+ color: var(--state-warning);
}
@media (max-width: 768px) {
- .permission-switcher {
- left: var(--composer-side-control-offset);
- bottom: -28px;
- }
+ .permission-switcher {
+ left: var(--composer-side-control-offset);
+ bottom: -28px;
+ }
- .context-usage-switcher {
- right: calc(var(--composer-side-control-offset) - var(--context-ring-visual-inset));
- bottom: -28px;
- }
+ .context-usage-switcher {
+ right: calc(var(--composer-side-control-offset) - var(--context-ring-visual-inset));
+ bottom: -28px;
+ }
- /* 权限菜单在手机端自适应位置,避免跑出屏幕 */
- .permission-switcher__menu {
- left: auto;
- right: 0;
- width: min(250px, calc(100vw - 40px));
- max-width: 250px;
- }
- .permission-switcher__menu--split {
- width: min(560px, calc(100vw - 40px));
- max-width: min(560px, calc(100vw - 40px));
- grid-template-columns: 1fr;
- }
+ /* 权限菜单在手机端自适应位置,避免跑出屏幕 */
+ .permission-switcher__menu {
+ left: auto;
+ right: 0;
+ width: min(250px, calc(100vw - 40px));
+ max-width: 250px;
+ }
+ .permission-switcher__menu--split {
+ width: min(560px, calc(100vw - 40px));
+ max-width: min(560px, calc(100vw - 40px));
+ grid-template-columns: 1fr;
+ }
}
.stadium-shell {
- --stadium-radius: 18px;
- position: relative;
- z-index: 2;
- width: 100%;
- padding: 10px;
- border-radius: var(--stadium-radius);
- border: 1px solid var(--border-default);
- background: var(--surface-raised);
- box-shadow: none;
- display: flex;
- gap: 12px;
- transition:
- box-shadow 0.45s cubic-bezier(0.4, 0, 0.2, 1),
- border-color 0.45s cubic-bezier(0.4, 0, 0.2, 1);
+ --stadium-radius: 18px;
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ padding: 10px;
+ border-radius: var(--stadium-radius);
+ border: 1px solid var(--border-default);
+ background: var(--surface-raised);
+ box-shadow: none;
+ display: flex;
+ gap: 12px;
+ transition:
+ box-shadow 0.45s cubic-bezier(0.4, 0, 0.2, 1),
+ border-color 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.stadium-shell.is-multiline {
- border-color: var(--border-strong);
- box-shadow: none;
+ border-color: var(--border-strong);
+ box-shadow: none;
}
.stadium-shell.is-focused,
.stadium-shell.has-text {
- border-color: var(--border-default);
- box-shadow: none;
+ border-color: var(--border-default);
+ box-shadow: none;
}
.stadium-shell.is-multiline.is-focused,
.stadium-shell.is-multiline.has-text {
- border-color: var(--border-strong);
- box-shadow: none;
+ border-color: var(--border-strong);
+ box-shadow: none;
}
.input-stack {
- display: flex;
- flex-direction: column;
- flex: 1 1 auto;
- gap: 6px;
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+ gap: 6px;
}
.input-row {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- width: 100%;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ width: 100%;
}
.input-actions {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
}
.stadium-input-rich {
- position: relative;
- flex: 1 1 auto;
- min-width: 0;
- display: grid;
- padding: 2px 0 0 6px;
+ position: relative;
+ flex: 1 1 auto;
+ min-width: 0;
+ display: grid;
+ padding: 2px 0 0 6px;
}
.stadium-input-rich .stadium-input {
- grid-area: 1 / 1;
+ grid-area: 1 / 1;
}
-.stadium-input-editor .skill-md-link {
- color: var(--accent);
- font-weight: inherit;
- border-radius: 4px;
- padding: 0 1px;
- white-space: nowrap;
+.stadium-input-editor .skill-md-link,
+.stadium-input-editor .file-md-link {
+ color: var(--accent);
+ font-weight: inherit;
+ border-radius: 4px;
+ padding: 0 1px;
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+.stadium-input-editor .file-md-link {
+ color: var(--state-info);
}
.stadium-input-editor p.is-editor-empty:first-child::before {
- content: attr(data-placeholder);
- color: var(--claude-text-tertiary);
- float: left;
- height: 0;
- pointer-events: none;
+ content: attr(data-placeholder);
+ color: var(--claude-text-tertiary);
+ float: left;
+ height: 0;
+ pointer-events: none;
}
.image-inline-row {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- padding: 0 4px 0;
- font-size: 12px;
- color: var(--text-secondary);
- line-height: 1.4;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ padding: 0 4px 0;
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.4;
}
.image-name {
- white-space: nowrap;
- display: inline-flex;
- align-items: center;
- gap: 6px;
+ white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
}
.image-remove-btn {
- border: none;
- background: transparent;
- color: var(--text-secondary);
- cursor: pointer;
- padding: 0 4px;
- font-size: 12px;
- line-height: 1;
- transition: color 0.15s ease, transform 0.15s ease;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ cursor: pointer;
+ padding: 0 4px;
+ font-size: 12px;
+ line-height: 1;
+ transition:
+ color 0.15s ease,
+ transform 0.15s ease;
}
.image-remove-btn:hover {
- color: var(--state-danger);
- transform: scale(1.05);
+ color: var(--state-danger);
+ transform: scale(1.05);
}
.stadium-input {
- flex: 1 1 auto;
- width: 100%;
- border: none;
- resize: none;
- background: transparent;
- font-size: 14px;
- line-height: 1.4;
- font-family: inherit;
- color: var(--claude-text);
- white-space: pre-wrap;
- word-break: break-all;
- overflow-wrap: normal;
- padding: 0;
- min-height: 20px;
- outline: none;
- overflow-y: auto;
- scrollbar-width: none;
- transition: none;
- position: relative;
- z-index: 1;
+ flex: 1 1 auto;
+ width: 100%;
+ border: none;
+ resize: none;
+ background: transparent;
+ font-size: 14px;
+ line-height: 1.4;
+ font-family: inherit;
+ color: var(--claude-text);
+ white-space: pre-wrap;
+ word-break: break-all;
+ overflow-wrap: normal;
+ padding: 0;
+ min-height: 20px;
+ outline: none;
+ overflow-y: auto;
+ scrollbar-width: none;
+ transition: none;
+ position: relative;
+ z-index: 1;
}
.stadium-input-tiptap {
- display: block;
+ display: block;
}
.stadium-input-tiptap.is-disabled {
- opacity: 0.5;
- cursor: not-allowed;
+ opacity: 0.5;
+ cursor: not-allowed;
}
.stadium-input-editor {
- cursor: text;
- min-height: 20px;
- outline: none;
- overflow-y: auto;
- scrollbar-width: none;
- white-space: pre-wrap;
- word-break: break-all;
- overflow-wrap: normal;
+ cursor: text;
+ min-height: 20px;
+ outline: none;
+ overflow-y: auto;
+ scrollbar-width: none;
+ white-space: pre-wrap;
+ word-break: break-all;
+ overflow-wrap: normal;
}
.stadium-input-editor p {
- margin: 0;
+ margin: 0;
}
.stadium-input-editor::-webkit-scrollbar {
- width: 0;
- height: 0;
+ width: 0;
+ height: 0;
}
.stadium-input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
+ opacity: 0.5;
+ cursor: not-allowed;
}
.stadium-input::-webkit-scrollbar {
- width: 0;
- height: 0;
+ width: 0;
+ height: 0;
}
.stadium-btn {
- flex: 0 0 28px;
- width: 28px;
- height: 28px;
- border: none;
- border-radius: 7px;
- background: transparent;
- color: var(--claude-text);
- font-size: 16px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background 140ms ease, color 140ms ease;
+ flex: 0 0 28px;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: 7px;
+ background: transparent;
+ color: var(--claude-text);
+ font-size: 16px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition:
+ background 140ms ease,
+ color 140ms ease;
}
.stadium-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
+ opacity: 0.4;
+ cursor: not-allowed;
}
.stadium-btn:hover:not(:disabled) {
- background: var(--hover-bg);
+ background: var(--hover-bg);
}
.add-btn {
- font-size: 20px;
+ font-size: 20px;
}
.stadium-btn.send-btn {
- background: var(--claude-accent);
- color: var(--on-accent);
- box-shadow: none;
+ background: var(--claude-accent);
+ color: var(--on-accent);
+ box-shadow: none;
}
.stadium-btn.send-btn:hover:not(:disabled) {
- background: var(--claude-button-hover);
+ background: var(--claude-button-hover);
}
.stadium-btn.send-btn:disabled {
- opacity: 0.4;
- box-shadow: none;
+ opacity: 0.4;
+ box-shadow: none;
}
.stadium-btn.send-btn span {
- position: relative;
- top: 0px;
+ position: relative;
+ top: 0px;
}
.stadium-btn.send-btn .send-icon {
- width: 0;
- height: 0;
- border-top: 5px solid transparent;
- border-bottom: 5px solid transparent;
- border-left: 9px solid var(--on-accent);
- margin-left: 4px;
+ width: 0;
+ height: 0;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 9px solid var(--on-accent);
+ margin-left: 4px;
}
.stadium-btn.send-btn .stop-icon {
- width: 11px;
- height: 11px;
- border-radius: 2px;
- background-color: var(--on-accent);
- display: block;
+ width: 11px;
+ height: 11px;
+ border-radius: 2px;
+ background-color: var(--on-accent);
+ display: block;
}
.stadium-btn.send-btn:disabled .send-icon {
- border-left-color: color-mix(in srgb, var(--on-accent) 40%, transparent);
+ border-left-color: color-mix(in srgb, var(--on-accent) 40%, transparent);
}
.stadium-btn.send-btn:disabled .stop-icon {
- background-color: color-mix(in srgb, var(--on-accent) 40%, transparent);
+ background-color: color-mix(in srgb, var(--on-accent) 40%, transparent);
}
.input-actions-right {
- display: flex;
- align-items: center;
- gap: 6px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
}
/* 语音输入按钮 */
.voice-btn {
- color: var(--text-primary);
+ color: var(--text-primary);
}
:root[data-theme='classic'] .voice-btn,
:root:not([data-theme]) .voice-btn {
- color: var(--accent);
+ color: var(--accent);
}
.voice-btn:hover:not(:disabled) {
- background: var(--hover-bg);
+ background: var(--hover-bg);
}
.voice-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
+ opacity: 0.4;
+ cursor: not-allowed;
}
.voice-btn .mic-icon {
- display: block;
- flex-shrink: 0;
+ display: block;
+ flex-shrink: 0;
}
/* 声纹动画 */
.voice-btn--recording {
- background: var(--hover-bg) !important;
+ background: var(--hover-bg) !important;
}
.voice-btn--disabled {
- opacity: 0.5;
- cursor: not-allowed;
- color: var(--text-secondary);
+ opacity: 0.5;
+ cursor: not-allowed;
+ color: var(--text-secondary);
}
.voice-wave {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 2.5px;
- height: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 2.5px;
+ height: 18px;
}
.voice-wave-bar {
- display: inline-block;
- width: 2.5px;
- height: 16px;
- border-radius: 2px;
- background: currentColor;
- transform-origin: center;
- animation: voice-wave-pulse 0.9s ease-in-out infinite;
+ display: inline-block;
+ width: 2.5px;
+ height: 16px;
+ border-radius: 2px;
+ background: currentColor;
+ transform-origin: center;
+ animation: voice-wave-pulse 0.9s ease-in-out infinite;
}
.voice-wave-bar:nth-child(1) {
- animation-delay: 0s;
- animation-duration: 0.75s;
+ animation-delay: 0s;
+ animation-duration: 0.75s;
}
.voice-wave-bar:nth-child(2) {
- animation-delay: 0.15s;
- animation-duration: 0.9s;
+ animation-delay: 0.15s;
+ animation-duration: 0.9s;
}
.voice-wave-bar:nth-child(3) {
- animation-delay: 0.3s;
- animation-duration: 1.05s;
+ animation-delay: 0.3s;
+ animation-duration: 1.05s;
}
.voice-wave-bar:nth-child(4) {
- animation-delay: 0.1s;
- animation-duration: 0.85s;
+ animation-delay: 0.1s;
+ animation-duration: 0.85s;
}
@keyframes voice-wave-pulse {
- 0%, 100% {
- transform: scaleY(0.25);
- opacity: 0.6;
- }
- 50% {
- transform: scaleY(1);
- opacity: 1;
- }
+ 0%,
+ 100% {
+ transform: scaleY(0.25);
+ opacity: 0.6;
+ }
+ 50% {
+ transform: scaleY(1);
+ opacity: 1;
+ }
}
.file-input-hidden {
- position: absolute;
- opacity: 0;
- width: 0;
- height: 0;
- pointer-events: none;
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+ pointer-events: none;
}
.quick-menu {
- position: absolute;
- left: 0;
- bottom: calc(100% + 14px);
- display: flex;
- flex-direction: column;
- gap: 6px;
- width: 230px;
- padding: 12px;
- background: var(--surface-soft);
- border: 1px solid var(--claude-border);
- border-radius: 18px;
- box-shadow: none;
- z-index: 30;
- pointer-events: auto;
+ position: absolute;
+ left: 0;
+ bottom: calc(100% + 14px);
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ width: 230px;
+ padding: 12px;
+ background: var(--surface-soft);
+ border: 1px solid var(--claude-border);
+ border-radius: 18px;
+ box-shadow: none;
+ z-index: 30;
+ pointer-events: auto;
}
.menu-entry {
- border: none;
- background: transparent;
- -webkit-appearance: none;
- appearance: none;
- -webkit-tap-highlight-color: transparent;
- padding: 10px 12px;
- border-radius: 12px;
- font-size: 14px;
- text-align: left;
- color: var(--claude-text);
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- transition: background 0.15s ease;
- min-height: 44px;
+ border: none;
+ background: transparent;
+ -webkit-appearance: none;
+ appearance: none;
+ -webkit-tap-highlight-color: transparent;
+ padding: 10px 12px;
+ border-radius: 12px;
+ font-size: 14px;
+ text-align: left;
+ color: var(--claude-text);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ min-height: 44px;
}
.menu-entry:focus,
.menu-entry:focus-visible {
- outline: none;
+ outline: none;
}
.menu-entry:hover:not(:disabled) {
- background: var(--hover-bg);
+ background: var(--hover-bg);
}
.menu-entry.active {
- background: var(--chip-bg);
- color: var(--claude-text);
- font-weight: 600;
+ background: var(--chip-bg);
+ color: var(--claude-text);
+ font-weight: 600;
}
.menu-entry:disabled {
- opacity: 0.45;
- cursor: not-allowed;
+ opacity: 0.45;
+ cursor: not-allowed;
}
.menu-entry.has-submenu .entry-arrow {
- margin-left: 10px;
- color: var(--claude-text-secondary);
+ margin-left: 10px;
+ color: var(--claude-text-secondary);
}
.quick-submenu {
- position: absolute;
- top: 0;
- left: calc(100% + 12px);
- width: 230px;
- min-width: 0;
- padding: 12px;
- border-radius: 18px;
- border: 1px solid var(--claude-border);
- background: var(--surface-soft);
- box-shadow: none;
- z-index: 31;
+ position: absolute;
+ top: 0;
+ left: calc(100% + 12px);
+ width: 230px;
+ min-width: 0;
+ padding: 12px;
+ border-radius: 18px;
+ border: 1px solid var(--claude-border);
+ background: var(--surface-soft);
+ box-shadow: none;
+ z-index: 31;
}
.submenu-status,
.submenu-empty {
- font-size: 13px;
- color: var(--claude-text-secondary);
+ font-size: 13px;
+ color: var(--claude-text-secondary);
}
.submenu-list {
- display: flex;
- flex-direction: column;
- gap: 6px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
}
/* Blank conversation hero */
.chat-container {
- position: relative;
+ position: relative;
}
.blank-hero-overlay {
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- pointer-events: none;
- z-index: 1;
- gap: 10px;
- padding-bottom: 160px;
- color: var(--text-primary);
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ pointer-events: none;
+ z-index: 1;
+ gap: 10px;
+ padding-bottom: 160px;
+ color: var(--text-primary);
}
.blank-hero-text {
- font-size: 32px;
- color: var(--text-primary);
- font-weight: 600;
- margin: 0;
- max-width: min(88vw, 680px);
- text-align: center;
- line-height: 1.3;
- text-wrap: balance;
+ font-size: 32px;
+ color: var(--text-primary);
+ font-weight: 600;
+ margin: 0;
+ max-width: min(88vw, 680px);
+ text-align: center;
+ line-height: 1.3;
+ text-wrap: balance;
}
.composer-container {
- position: relative;
- height: var(--composer-base-height, calc(90px + var(--app-bottom-inset)));
- flex: 0 0 var(--composer-base-height, calc(90px + var(--app-bottom-inset)));
- transition: transform 0.3s ease;
- z-index: 2;
+ position: relative;
+ height: var(--composer-base-height, calc(90px + var(--app-bottom-inset)));
+ flex: 0 0 var(--composer-base-height, calc(90px + var(--app-bottom-inset)));
+ transition: transform 0.3s ease;
+ z-index: 2;
}
.composer-container.blank-hero-mode {
- // 新对话居中态:当输入栏上方出现 git 状态栏或输入变多行时,整体可视高度会向上增长,
- // 顶到欢迎文字。用已计算好的 --composer-growth-height 把输入栏整体下移同样的距离,
- // 让栈顶回到「裸单行输入」时的位置,从而保证不遮挡欢迎文字。
- transform: translateY(calc(-38vh + var(--composer-growth-height, 0px)));
+ // 新对话居中态:当输入栏上方出现 git 状态栏或输入变多行时,整体可视高度会向上增长,
+ // 顶到欢迎文字。用已计算好的 --composer-growth-height 把输入栏整体下移同样的距离,
+ // 让栈顶回到「裸单行输入」时的位置,从而保证不遮挡欢迎文字。
+ transform: translateY(calc(-38vh + var(--composer-growth-height, 0px)));
}
@media (max-width: 768px) {
- .input-area {
- padding: 0 12px;
- }
+ .input-area {
+ padding: 0 12px;
+ }
- .blank-hero-text {
- font-size: 28px;
- }
- .blank-hero-overlay .icon-lg {
- width: 44px;
- height: 44px;
- }
- .composer-container.blank-hero-mode {
- transform: translateY(calc(-32vh + var(--composer-growth-height, 0px)));
- }
- .blank-hero-overlay {
- padding-bottom: 136px;
- }
+ .blank-hero-text {
+ font-size: 28px;
+ }
+ .blank-hero-overlay .icon-lg {
+ width: 44px;
+ height: 44px;
+ }
+ .composer-container.blank-hero-mode {
+ transform: translateY(calc(-32vh + var(--composer-growth-height, 0px)));
+ }
+ .blank-hero-overlay {
+ padding-bottom: 136px;
+ }
}
.quick-submenu.tool-submenu {
- top: auto;
- bottom: 0;
+ top: auto;
+ bottom: 0;
}
.quick-submenu.settings-submenu {
- top: auto;
- bottom: 0;
+ top: auto;
+ bottom: 0;
}
.menu-entry.submenu-entry {
- width: 100%;
- justify-content: space-between;
+ width: 100%;
+ justify-content: space-between;
}
.menu-entry.submenu-entry .entry-arrow {
- color: var(--claude-text-secondary);
+ color: var(--claude-text-secondary);
}
.tool-submenu-list {
- // 显示约 5 条工具项,其余通过滚动查看
- max-height: calc(44px * 5 + 6px * 4);
- overflow-y: auto;
- overscroll-behavior: contain;
- padding-right: 2px;
- scrollbar-width: none;
- -ms-overflow-style: none;
+ // 显示约 5 条工具项,其余通过滚动查看
+ max-height: calc(44px * 5 + 6px * 4);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ padding-right: 2px;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
}
.tool-submenu-list::-webkit-scrollbar {
- width: 0;
- height: 0;
- display: none;
+ width: 0;
+ height: 0;
+ display: none;
}
.menu-entry.disabled {
- opacity: 0.5;
+ opacity: 0.5;
}
-
-
.quick-menu-enter-active,
.quick-menu-leave-active {
- transition: opacity 0.2s ease, transform 0.2s ease;
+ transition:
+ opacity 0.2s ease,
+ transform 0.2s ease;
}
.quick-menu-enter-from,
.quick-menu-leave-to {
- opacity: 0;
- transform: translateY(8px);
+ opacity: 0;
+ transform: translateY(8px);
}
.submenu-slide-enter-active,
.submenu-slide-leave-active {
- transition: opacity 0.2s ease, transform 0.2s ease;
+ transition:
+ opacity 0.2s ease,
+ transform 0.2s ease;
}
.submenu-slide-enter-from,
.submenu-slide-leave-to {
- opacity: 0;
- transform: translateX(10px);
+ opacity: 0;
+ transform: translateX(10px);
}
/* 适应折叠屏/矮屏幕,保证输入区完整可见 */
@media (max-height: 900px) {
- .input-area {
- bottom: 12px;
- }
+ .input-area {
+ bottom: 12px;
+ }
}
.settings-menu {
- position: absolute;
- right: 0;
- bottom: calc(100% + 12px);
- background: var(--surface-soft);
- border: 1px solid var(--claude-border);
- border-radius: 12px;
- box-shadow: none;
- padding: 12px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 150px;
- z-index: 40;
+ position: absolute;
+ right: 0;
+ bottom: calc(100% + 12px);
+ background: var(--surface-soft);
+ border: 1px solid var(--claude-border);
+ border-radius: 12px;
+ box-shadow: none;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 150px;
+ z-index: 40;
}
.settings-menu::before {
- content: '';
- position: absolute;
- bottom: -10px;
- right: 20px;
- border-width: 10px 10px 0 10px;
- border-style: solid;
- border-color: var(--surface-soft) transparent transparent transparent;
- filter: drop-shadow(0 3px 4px rgba(61, 57, 41, 0.12));
+ content: '';
+ position: absolute;
+ bottom: -10px;
+ right: 20px;
+ border-width: 10px 10px 0 10px;
+ border-style: solid;
+ border-color: var(--surface-soft) transparent transparent transparent;
+ filter: drop-shadow(0 3px 4px rgba(61, 57, 41, 0.12));
}
.settings-menu.tool-menu {
- right: auto;
- left: 0;
- min-width: 520px;
- max-width: 580px;
- padding: 16px 20px;
+ right: auto;
+ left: 0;
+ min-width: 520px;
+ max-width: 580px;
+ padding: 16px 20px;
}
.settings-menu.tool-menu::before {
- left: 32px;
- right: auto;
+ left: 32px;
+ right: auto;
}
.tool-menu .tool-menu-status,
.tool-menu .tool-menu-empty {
- font-size: 13px;
- color: var(--text-secondary);
- text-align: left;
+ font-size: 13px;
+ color: var(--text-secondary);
+ text-align: left;
}
.tool-menu .tool-menu-list {
- display: grid;
- grid-template-columns: repeat(4, minmax(110px, 1fr));
- gap: 12px;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(110px, 1fr));
+ gap: 12px;
}
.tool-menu .tool-category-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 10px;
- padding: 14px 10px;
- border: 1px solid var(--border-default);
- border-radius: 10px;
- background: var(--surface-soft);
- font-size: 13px;
- aspect-ratio: 1 / 1;
- min-height: 0;
- justify-content: space-between;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+ padding: 14px 10px;
+ border: 1px solid var(--border-default);
+ border-radius: 10px;
+ background: var(--surface-soft);
+ font-size: 13px;
+ aspect-ratio: 1 / 1;
+ min-height: 0;
+ justify-content: space-between;
}
.tool-menu .tool-category-item.disabled {
- opacity: 0.55;
+ opacity: 0.55;
}
.tool-menu .tool-category-label {
- flex: 1;
- font-size: 13px;
- font-weight: 500;
- color: var(--claude-text);
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 6px;
- text-align: center;
- white-space: nowrap;
- line-height: 1.4;
+ flex: 1;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--claude-text);
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ text-align: center;
+ white-space: nowrap;
+ line-height: 1.4;
}
.tool-category-icon {
- font-size: 20px;
+ font-size: 20px;
}
.tool-menu .tool-category-toggle {
- width: 100% !important;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 6px 12px;
- text-align: center;
- white-space: nowrap;
- margin-top: auto;
+ width: 100% !important;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 12px;
+ text-align: center;
+ white-space: nowrap;
+ margin-top: auto;
}
.menu-btn {
- width: 100%;
- padding: 8px 14px;
- border: 1px solid var(--border-default);
- border-radius: 8px;
- font-size: 13px;
- font-weight: 500;
- text-align: left;
- background: var(--surface-soft);
- color: var(--claude-text);
- cursor: pointer;
- transition: all 0.2s ease;
+ width: 100%;
+ padding: 8px 14px;
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ text-align: left;
+ background: var(--surface-soft);
+ color: var(--claude-text);
+ cursor: pointer;
+ transition: all 0.2s ease;
}
.menu-btn:not(:disabled):hover {
- background: var(--surface-soft);
- transform: translateY(-1px);
+ background: var(--surface-soft);
+ transform: translateY(-1px);
}
.menu-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
+ opacity: 0.5;
+ cursor: not-allowed;
}
.menu-btn.compress-entry {
- background: var(--surface-soft);
- color: var(--claude-success);
+ background: var(--surface-soft);
+ color: var(--claude-success);
}
.menu-btn.compress-entry:not(:disabled):hover {
- background: var(--surface-soft);
+ background: var(--surface-soft);
}
.menu-btn.clear-entry {
- background: var(--surface-soft);
- color: var(--state-danger);
+ background: var(--surface-soft);
+ color: var(--state-danger);
}
.menu-btn.clear-entry:not(:disabled):hover {
- background: var(--surface-soft);
+ background: var(--surface-soft);
}
.settings-menu-enter-active,
.settings-menu-leave-active {
- transition: opacity 0.18s ease, transform 0.18s ease;
+ transition:
+ opacity 0.18s ease,
+ transform 0.18s ease;
}
.settings-menu-enter-from,
.settings-menu-leave-to {
- opacity: 0;
- transform: translateY(6px);
+ opacity: 0;
+ transform: translateY(6px);
}
/* 手机端审批面板样式 - 与电脑端保持一致 */
.mobile-approval-overlay {
- pointer-events: auto;
- justify-content: center !important;
+ pointer-events: auto;
+ justify-content: center !important;
}
.mobile-approval-overlay .mobile-panel-sheet--approval {
- pointer-events: auto;
- background: var(--claude-panel);
+ pointer-events: auto;
+ background: var(--claude-panel);
}
/* 手机端审批面板按钮样式 - 与电脑端一致 */
@media (max-width: 768px) {
- .mobile-panel-sheet--approval .approval-btn {
- border-radius: 8px;
- padding: 8px 14px;
- font-size: 13px;
- }
+ .mobile-panel-sheet--approval .approval-btn {
+ border-radius: 8px;
+ padding: 8px 14px;
+ font-size: 13px;
+ }
- .mobile-panel-sheet--approval .approval-btn--approve {
- background: var(--claude-accent);
- color: var(--on-accent);
- border-color: var(--claude-accent);
- }
+ .mobile-panel-sheet--approval .approval-btn--approve {
+ background: var(--claude-accent);
+ color: var(--on-accent);
+ border-color: var(--claude-accent);
+ }
- .mobile-panel-sheet--approval .approval-btn--reject {
- background: transparent;
- border-color: var(--theme-control-border-strong);
- }
+ .mobile-panel-sheet--approval .approval-btn--reject {
+ background: transparent;
+ border-color: var(--theme-control-border-strong);
+ }
- .mobile-panel-sheet--approval .approval-btn--switch {
- background: transparent;
- border-color: var(--claude-accent);
- color: var(--claude-accent);
- }
+ .mobile-panel-sheet--approval .approval-btn--switch {
+ background: transparent;
+ border-color: var(--claude-accent);
+ color: var(--claude-accent);
+ }
- /* 审批卡片样式保持一致 */
- .mobile-panel-sheet--approval .approval-card {
- border-color: var(--theme-control-border);
- background: var(--theme-surface-soft);
- }
+ /* 审批卡片样式保持一致 */
+ .mobile-panel-sheet--approval .approval-card {
+ border-color: var(--theme-control-border);
+ background: var(--theme-surface-soft);
+ }
- /* 审批面板样式 - 移除背景色让内容顶头 */
- .mobile-panel-sheet--approval .sidebar.right-sidebar {
- background: transparent !important;
- }
+ /* 审批面板样式 - 移除背景色让内容顶头 */
+ .mobile-panel-sheet--approval .sidebar.right-sidebar {
+ background: transparent !important;
+ }
- .mobile-panel-sheet--approval .sidebar-header,
- .mobile-panel-sheet--approval .sidebar .sidebar-header {
- background: transparent !important;
- border-color: var(--claude-border);
- padding: 0 16px !important;
- }
+ .mobile-panel-sheet--approval .sidebar-header,
+ .mobile-panel-sheet--approval .sidebar .sidebar-header {
+ background: transparent !important;
+ border-color: var(--claude-border);
+ padding: 0 16px !important;
+ }
- /* 移动端审批面板关闭按钮 - 移除圆形外框 */
- .mobile-panel-sheet--approval .approval-close-btn {
- background: transparent !important;
- border-radius: 0 !important;
- width: auto !important;
- height: auto !important;
- padding: 4px !important;
- font-size: 24px !important;
- }
+ /* 移动端审批面板关闭按钮 - 移除圆形外框 */
+ .mobile-panel-sheet--approval .approval-close-btn {
+ background: transparent !important;
+ border-radius: 0 !important;
+ width: auto !important;
+ height: auto !important;
+ padding: 4px !important;
+ font-size: 24px !important;
+ }
}
-
-
-
.user-question-mini-dot {
- position: absolute;
- right: 22px;
- bottom: calc(100% + 6px);
- z-index: 12;
- min-width: 18px;
- height: 18px;
- padding: 0 5px;
- border: 1.5px solid var(--theme-surface-strong);
- border-radius: 999px;
- background: var(--mac-close);
- color: var(--on-accent);
- cursor: pointer;
- font-size: 11px;
- line-height: 15px;
- font-weight: 700;
- display: inline-flex;
- align-items: center;
- justify-content: center;
+ position: absolute;
+ right: 22px;
+ bottom: calc(100% + 6px);
+ z-index: 12;
+ min-width: 18px;
+ height: 18px;
+ padding: 0 5px;
+ border: 1.5px solid var(--theme-surface-strong);
+ border-radius: 999px;
+ background: var(--mac-close);
+ color: var(--on-accent);
+ cursor: pointer;
+ font-size: 11px;
+ line-height: 15px;
+ font-weight: 700;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}