feat: replace ui emoji with svg icons

This commit is contained in:
JOJO 2025-11-21 23:33:35 +08:00
parent b2cfabcd1b
commit 411cbf71ee
38 changed files with 818 additions and 116 deletions

View File

@ -36,6 +36,86 @@ const SOCKET_IO_CDN_SOURCES = [
'https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js'
];
const ICONS = Object.freeze({
bot: '/static/icons/bot.svg',
book: '/static/icons/book.svg',
brain: '/static/icons/brain.svg',
camera: '/static/icons/camera.svg',
check: '/static/icons/check.svg',
checkbox: '/static/icons/checkbox.svg',
circleAlert: '/static/icons/circle-alert.svg',
clipboard: '/static/icons/clipboard.svg',
clock: '/static/icons/clock.svg',
eye: '/static/icons/eye.svg',
file: '/static/icons/file.svg',
flag: '/static/icons/flag.svg',
folder: '/static/icons/folder.svg',
folderOpen: '/static/icons/folder-open.svg',
globe: '/static/icons/globe.svg',
hammer: '/static/icons/hammer.svg',
info: '/static/icons/info.svg',
laptop: '/static/icons/laptop.svg',
menu: '/static/icons/menu.svg',
monitor: '/static/icons/monitor.svg',
octagon: '/static/icons/octagon.svg',
pencil: '/static/icons/pencil.svg',
python: '/static/icons/python.svg',
recycle: '/static/icons/recycle.svg',
save: '/static/icons/save.svg',
search: '/static/icons/search.svg',
settings: '/static/icons/settings.svg',
sparkles: '/static/icons/sparkles.svg',
stickyNote: '/static/icons/sticky-note.svg',
terminal: '/static/icons/terminal.svg',
trash: '/static/icons/trash.svg',
triangleAlert: '/static/icons/triangle-alert.svg',
user: '/static/icons/user.svg',
wrench: '/static/icons/wrench.svg',
x: '/static/icons/x.svg'
});
const TOOL_ICON_MAP = Object.freeze({
append_to_file: 'pencil',
close_sub_agent: 'octagon',
create_file: 'file',
create_folder: 'folder',
create_sub_agent: 'bot',
delete_file: 'trash',
extract_webpage: 'globe',
focus_file: 'eye',
modify_file: 'pencil',
ocr_image: 'camera',
read_file: 'book',
rename_file: 'pencil',
run_command: 'terminal',
run_python: 'python',
save_webpage: 'save',
sleep: 'clock',
todo_create: 'stickyNote',
todo_finish: 'flag',
todo_finish_confirm: 'circleAlert',
todo_update_task: 'check',
terminal_input: 'terminal',
terminal_reset: 'recycle',
terminal_session: 'monitor',
terminal_snapshot: 'clipboard',
unfocus_file: 'eye',
update_memory: 'brain',
wait_sub_agent: 'clock',
web_search: 'search'
});
const TOOL_CATEGORY_ICON_MAP = Object.freeze({
network: 'globe',
file_edit: 'pencil',
read_focus: 'eye',
terminal_realtime: 'monitor',
terminal_command: 'terminal',
memory: 'brain',
todo: 'stickyNote',
sub_agent: 'bot'
});
function injectScriptSequentially(urls, onSuccess, onFailure) {
let index = 0;
const tryLoad = () => {
@ -220,20 +300,8 @@ async function bootstrapApp() {
// TODO 列表
todoList: null,
todoEmoji: '🗒️',
fileEmoji: '📁',
todoDoneEmoji: '☑️',
todoPendingEmoji: '⬜️',
toolCategoryEmojis: {
network: '🌐',
file_edit: '📝',
read_focus: '🔍',
terminal_realtime: '🖥️',
terminal_command: '⌨️',
memory: '🧠',
todo: '🗒️',
sub_agent: '🤖'
},
icons: ICONS,
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,
// 右键菜单相关
contextMenu: {
@ -330,6 +398,22 @@ async function bootstrapApp() {
},
methods: {
iconStyle(iconKey, size) {
const iconPath = this.icons ? this.icons[iconKey] : null;
if (!iconPath) {
return {};
}
const style = { '--icon-src': `url(${iconPath})` };
if (size) {
style['--icon-size'] = size;
}
return style;
},
toolCategoryIcon(categoryId) {
return this.toolCategoryIcons[categoryId] || 'settings';
},
openGuiFileManager() {
window.open('/file-manager', '_blank');
},
@ -1303,7 +1387,7 @@ async function bootstrapApp() {
this.activeTools.clear();
this.toolActionIndex.clear();
// 新增:将所有未完成的工具标记为已完成
// 新增:将所有未完成的工具标记为已完成
this.messages.forEach(msg => {
if (msg.role === 'assistant' && msg.actions) {
msg.actions.forEach(action => {
@ -2195,19 +2279,6 @@ async function bootstrapApp() {
}
},
formatTaskStatus(task) {
if (!task) {
return '';
}
return task.status === 'done'
? `${this.todoDoneEmoji} 完成`
: `${this.todoPendingEmoji} 未完成`;
},
toolCategoryEmoji(categoryId) {
return this.toolCategoryEmojis[categoryId] || '⚙️';
},
async fetchSubAgents() {
try {
const resp = await fetch('/api/sub_agents');
@ -2666,38 +2737,8 @@ async function bootstrapApp() {
// 修复:工具相关方法 - 接收tool对象而不是name
getToolIcon(tool) {
const toolName = typeof tool === 'string' ? tool : tool.name;
const icons = {
'create_file': '📄',
'sleep': '⏱️',
'read_file': '📖',
'ocr_image': '📸',
'delete_file': '🗑️',
'rename_file': '✏️',
'modify_file': '✏️',
'append_to_file': '✏️',
'create_folder': '📁',
'focus_file': '👁️',
'unfocus_file': '👁️',
'web_search': '🔍',
'extract_webpage': '🌐',
'save_webpage': '💾',
'run_python': '🐍',
'run_command': '$',
'update_memory': '🧠',
'terminal_session': '💻',
'terminal_input': '⌨️',
'terminal_snapshot': '📋',
'terminal_reset': '♻️',
'todo_create': '🗒️',
'todo_update_task': '☑️',
'todo_finish': '🏁',
'todo_finish_confirm': '❗',
'create_sub_agent': '🤖',
'wait_sub_agent': '⏳',
'close_sub_agent': '🛑'
};
return icons[toolName] || '⚙️';
const toolName = typeof tool === 'string' ? tool : (tool && tool.name);
return TOOL_ICON_MAP[toolName] || 'settings';
},
getToolAnimationClass(tool) {
@ -3066,7 +3107,7 @@ async function bootstrapApp() {
<div class="code-block-wrapper">
<div class="code-block-header">
<span class="code-language">${language}</span>
<button class="copy-code-btn" data-code="${blockId}" title="复制代码">📋</button>
<button class="copy-code-btn" data-code="${blockId}" title="复制代码" aria-label="复制代码"></button>
</div>
<pre><code${attributes} data-code-id="${blockId}" data-original-code="${escapedContent}">${content}</code></pre>
</div>`;
@ -3243,6 +3284,10 @@ async function bootstrapApp() {
}
},
methods: {
iconStyle(iconKey) {
const iconPath = ICONS[iconKey];
return iconPath ? { '--icon-src': `url(${iconPath})` } : {};
},
toggle() {
if (this.node.type === 'folder') {
this.$emit('toggle-folder', this.node.path);
@ -3254,7 +3299,9 @@ async function bootstrapApp() {
<div v-if="node.type === 'folder'" class="file-node folder-node">
<button class="folder-header" type="button" :style="folderPadding" @click="toggle">
<span class="folder-arrow">{{ isExpanded ? '▾' : '▸' }}</span>
<span class="folder-icon">{{ isExpanded ? '📂' : '📁' }}</span>
<span class="icon icon-sm folder-icon"
:style="iconStyle(isExpanded ? 'folderOpen' : 'folder')"
aria-hidden="true"></span>
<span class="folder-name">{{ node.name }}</span>
</button>
<div v-show="isExpanded" class="folder-children">
@ -3270,7 +3317,9 @@ async function bootstrapApp() {
</div>
</div>
<div v-else class="file-node file-leaf" :style="filePadding">
<span class="file-icon">📄</span>
<span class="icon icon-sm file-icon"
:style="iconStyle('file')"
aria-hidden="true"></span>
<span class="file-name">{{ node.name }}</span>
<span v-if="node.annotation" class="annotation">{{ node.annotation }}</span>
</div>

13
static/icons/book.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
</svg>

After

Width:  |  Height:  |  Size: 310 B

18
static/icons/bot.svg Normal file
View File

@ -0,0 +1,18 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 8V4H8" />
<rect width="16" height="12" x="4" y="8" rx="2" />
<path d="M2 14h2" />
<path d="M20 14h2" />
<path d="M15 13v2" />
<path d="M9 13v2" />
</svg>

After

Width:  |  Height:  |  Size: 380 B

20
static/icons/brain.svg Normal file
View File

@ -0,0 +1,20 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 18V5" />
<path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4" />
<path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5" />
<path d="M17.997 5.125a4 4 0 0 1 2.526 5.77" />
<path d="M18 18a4 4 0 0 0 2-7.464" />
<path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517" />
<path d="M6 18a4 4 0 0 1-2-7.464" />
<path d="M6.003 5.125a4 4 0 0 0-2.526 5.77" />
</svg>

After

Width:  |  Height:  |  Size: 601 B

14
static/icons/camera.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z" />
<circle cx="12" cy="13" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 437 B

13
static/icons/check.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20 6 9 17l-5-5" />
</svg>

After

Width:  |  Height:  |  Size: 239 B

13
static/icons/checkbox.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="5" y="5" width="14" height="14" rx="2" />
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" x2="12" y1="8" y2="12" />
<line x1="12" x2="12.01" y1="16" y2="16" />
</svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
</svg>

After

Width:  |  Height:  |  Size: 354 B

14
static/icons/clock.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 7v5l3 3" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

14
static/icons/eye.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
<circle cx="12" cy="12" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 360 B

14
static/icons/file.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
</svg>

After

Width:  |  Height:  |  Size: 373 B

13
static/icons/flag.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528" />
</svg>

After

Width:  |  Height:  |  Size: 370 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="m6 14l1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg>

After

Width:  |  Height:  |  Size: 395 B

13
static/icons/folder.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
</svg>

After

Width:  |  Height:  |  Size: 342 B

15
static/icons/globe.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
<path d="M2 12h20" />
</svg>

After

Width:  |  Height:  |  Size: 331 B

15
static/icons/hammer.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" />
<path d="m18 15 4-4" />
<path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5" />
</svg>

After

Width:  |  Height:  |  Size: 483 B

15
static/icons/info.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>

After

Width:  |  Height:  |  Size: 293 B

14
static/icons/laptop.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.897l1.068 2.127a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45l1.068-2.127A2 2 0 0 0 4 15.526V7a2 2 0 0 1 2-2z" />
<path d="M20.054 15.987H3.946" />
</svg>

After

Width:  |  Height:  |  Size: 405 B

15
static/icons/menu.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 5h16" />
<path d="M4 12h16" />
<path d="M4 19h16" />
</svg>

After

Width:  |  Height:  |  Size: 279 B

15
static/icons/monitor.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="20" height="14" x="2" y="3" rx="2" />
<line x1="8" x2="16" y1="21" y2="21" />
<line x1="12" x2="12" y1="17" y2="21" />
</svg>

After

Width:  |  Height:  |  Size: 346 B

13
static/icons/octagon.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z" />
</svg>

After

Width:  |  Height:  |  Size: 458 B

14
static/icons/pencil.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
<path d="m15 5 4 4" />
</svg>

After

Width:  |  Height:  |  Size: 377 B

1
static/icons/python.svg Normal file
View File

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Python</title><path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

18
static/icons/recycle.svg Normal file
View File

@ -0,0 +1,18 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M7 19H4.815a1.83 1.83 0 0 1-1.57-.881 1.785 1.785 0 0 1-.004-1.784L7.196 9.5" />
<path d="M11 19h8.203a1.83 1.83 0 0 0 1.556-.89 1.784 1.784 0 0 0 0-1.775l-1.226-2.12" />
<path d="m14 16-3 3 3 3" />
<path d="M8.293 13.596 7.196 9.5 3.1 10.598" />
<path d="m9.344 5.811 1.093-1.892A1.83 1.83 0 0 1 11.985 3a1.784 1.784 0 0 1 1.546.888l3.943 6.843" />
<path d="m13.378 9.633 4.096 1.098 1.097-4.096" />
</svg>

After

Width:  |  Height:  |  Size: 630 B

15
static/icons/save.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" />
<path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" />
<path d="M7 3v4a1 1 0 0 0 1 1h7" />
</svg>

After

Width:  |  Height:  |  Size: 417 B

14
static/icons/search.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" />
</svg>

After

Width:  |  Height:  |  Size: 275 B

14
static/icons/settings.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
<circle cx="12" cy="12" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 586 B

16
static/icons/sparkles.svg Normal file
View File

@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" />
<path d="M20 2v4" />
<path d="M22 4h-4" />
<circle cx="4" cy="20" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 567 B

View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z" />
<path d="M15 3v5a1 1 0 0 0 1 1h5" />
</svg>

After

Width:  |  Height:  |  Size: 376 B

14
static/icons/terminal.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 19h8" />
<path d="m4 17 6-6-6-6" />
</svg>

After

Width:  |  Height:  |  Size: 261 B

15
static/icons/trash.svg Normal file
View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M3 6h18" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
</svg>

After

Width:  |  Height:  |  Size: 341 B

View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
<path d="M12 9v4" />
<path d="M12 17h.01" />
</svg>

After

Width:  |  Height:  |  Size: 345 B

14
static/icons/user.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>

After

Width:  |  Height:  |  Size: 299 B

13
static/icons/wrench.svg Normal file
View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" />
</svg>

After

Width:  |  Height:  |  Size: 417 B

14
static/icons/x.svg Normal file
View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@ -79,8 +79,11 @@
<span class="btn-text">新建对话</span>
</button>
<button @click="toggleSidebar" class="toggle-sidebar-btn">
<span v-if="sidebarCollapsed"></span>
<span v-else></span>
<span v-if="sidebarCollapsed"
class="icon icon-md"
:style="iconStyle('menu')"
aria-hidden="true"></span>
<span v-else class="toggle-arrow" aria-hidden="true"></span>
</button>
</template>
</div>
@ -166,7 +169,12 @@
<div class="sidebar-status">
<div class="compact-status-card">
<div class="status-top">
<span class="logo">🤖 AI Agent</span>
<span class="logo icon-label">
<span class="icon icon-md"
:style="iconStyle('bot')"
aria-hidden="true"></span>
<span>AI Agent</span>
</span>
<span class="agent-version" v-if="agentVersion">{{ agentVersion }}</span>
</div>
<div class="status-bottom">
@ -185,22 +193,36 @@
<button class="sidebar-view-toggle"
@click.stop="togglePanelMenu"
title="切换侧边栏">
<span class="icon icon-md"
:style="iconStyle('menu')"
aria-hidden="true"></span>
</button>
<transition name="fade">
<div class="panel-menu" v-if="panelMenuOpen">
<button type="button"
:class="{ active: panelMode === 'files' }"
@click.stop="selectPanelMode('files')"
title="项目文件">📁</button>
title="项目文件">
<span class="icon icon-md"
:style="iconStyle('folder')"
aria-hidden="true"></span>
</button>
<button type="button"
:class="{ active: panelMode === 'todo' }"
@click.stop="selectPanelMode('todo')"
title="待办列表">{{ todoEmoji }}</button>
title="待办列表">
<span class="icon icon-md"
:style="iconStyle('stickyNote')"
aria-hidden="true"></span>
</button>
<button type="button"
:class="{ active: panelMode === 'subAgents' }"
@click.stop="selectPanelMode('subAgents')"
title="子智能体">🤖</button>
title="子智能体">
<span class="icon icon-md"
:style="iconStyle('bot')"
aria-hidden="true"></span>
</button>
</div>
</transition>
</div>
@ -210,9 +232,24 @@
管理
</button>
<h3>
<span v-if="panelMode === 'files'">{{ fileEmoji }} 项目文件</span>
<span v-else-if="panelMode === 'todo'">{{ todoEmoji }} 待办列表</span>
<span v-else>🤖 子智能体</span>
<span v-if="panelMode === 'files'" class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('folder')"
aria-hidden="true"></span>
<span>项目文件</span>
</span>
<span v-else-if="panelMode === 'todo'" class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('stickyNote')"
aria-hidden="true"></span>
<span>待办列表</span>
</span>
<span v-else class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('bot')"
aria-hidden="true"></span>
<span>子智能体</span>
</span>
</h3>
</div>
<div class="sidebar-panel-content">
@ -226,7 +263,12 @@
:key="task.index"
:class="{ done: task.status === 'done' }">
<span class="todo-task-title">task{{ task.index }}{{ task.title }}</span>
<span class="todo-task-status">{{ formatTaskStatus(task) }}</span>
<span class="todo-task-status icon-label">
<span class="icon icon-sm"
:style="iconStyle(task.status === 'done' ? 'check' : 'checkbox')"
aria-hidden="true"></span>
<span>{{ task.status === 'done' ? '完成' : '未完成' }}</span>
</span>
</div>
<div class="todo-instruction">{{ todoList.instruction }}</div>
</div>
@ -298,13 +340,23 @@
<!-- 用户消息 -->
<div v-if="msg.role === 'user'" class="user-message">
<div class="message-header">👤 用户</div>
<div class="message-header icon-label">
<span class="icon icon-sm"
:style="iconStyle('user')"
aria-hidden="true"></span>
<span>用户</span>
</div>
<div class="message-text">{{ msg.content }}</div>
</div>
<!-- AI消息 -->
<div v-else-if="msg.role === 'assistant'" class="assistant-message">
<div class="message-header">🤖 AI Assistant</div>
<div class="message-header icon-label">
<span class="icon icon-sm"
:style="iconStyle('bot')"
aria-hidden="true"></span>
<span>AI Assistant</span>
</div>
<!-- 按顺序显示所有actions -->
<div v-for="(action, actionIndex) in msg.actions"
@ -323,7 +375,11 @@
<div class="collapsible-header" @click="toggleBlock(action.blockId || `${index}-thinking-${actionIndex}`)">
<div class="arrow"></div>
<div class="status-icon">
<span class="thinking-icon" :class="{ 'thinking-animation': action.streaming }">🧠</span>
<span class="thinking-icon" :class="{ 'thinking-animation': action.streaming }">
<span class="icon icon-sm"
:style="iconStyle('brain')"
aria-hidden="true"></span>
</span>
</div>
<span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span>
</div>
@ -362,10 +418,20 @@
:class="{ 'append-error': action.append?.success === false }">
<div class="append-placeholder-content">
<template v-if="action.append?.success !== false">
✏️ 已写入 {{ action.append?.path || '目标文件' }} 的追加内容(内容已保存至文件)
<div class="icon-label append-status">
<span class="icon icon-sm"
:style="iconStyle('pencil')"
aria-hidden="true"></span>
<span>已写入 {{ action.append?.path || '目标文件' }} 的追加内容(内容已保存至文件)</span>
</div>
</template>
<template v-else>
❌ 向 {{ action.append?.path || '目标文件' }} 写入失败,内容已截获供后续修复。
<div class="icon-label append-status append-error-text">
<span class="icon icon-sm"
:style="iconStyle('x')"
aria-hidden="true"></span>
<span>向 {{ action.append?.path || '目标文件' }} 写入失败,内容已截获供后续修复。</span>
</div>
</template>
<div class="append-meta" v-if="action.append">
<span v-if="action.append.lines !== null && action.append.lines !== undefined">
@ -375,8 +441,11 @@
· 字节 {{ action.append.bytes }}
</span>
</div>
<div class="append-warning" v-if="action.append?.forced">
⚠️ 未检测到结束标记,请根据提示继续补充。
<div class="append-warning icon-label" v-if="action.append?.forced">
<span class="icon icon-sm"
:style="iconStyle('triangleAlert')"
aria-hidden="true"></span>
<span>未检测到结束标记,请根据提示继续补充。</span>
</div>
</div>
</div>
@ -386,16 +455,22 @@
class="append-placeholder"
:class="{ 'append-error': action.append?.success === false }">
<div class="append-placeholder-content">
<div>
✏️ {{ action.append?.summary || '文件追加完成' }}
<div class="icon-label append-status">
<span class="icon icon-sm"
:style="iconStyle('pencil')"
aria-hidden="true"></span>
<span>{{ action.append?.summary || '文件追加完成' }}</span>
</div>
<div class="append-meta" v-if="action.append">
<span>{{ action.append.path || '目标文件' }}</span>
<span v-if="action.append.lines">· 行数 {{ action.append.lines }}</span>
<span v-if="action.append.bytes">· 字节 {{ action.append.bytes }}</span>
</div>
<div class="append-warning" v-if="action.append?.forced">
⚠️ 未检测到结束标记,请按提示继续补充。
<div class="append-warning icon-label" v-if="action.append?.forced">
<span class="icon icon-sm"
:style="iconStyle('triangleAlert')"
aria-hidden="true"></span>
<span>未检测到结束标记,请按提示继续补充。</span>
</div>
</div>
</div>
@ -403,7 +478,12 @@
<!-- 修改内容占位 -->
<div v-else-if="action.type === 'modify_payload'" class="modify-placeholder">
<div class="modify-placeholder-content">
🛠️ 已对 {{ action.modify?.path || '目标文件' }} 执行补丁
<span class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('hammer')"
aria-hidden="true"></span>
<span>已对 {{ action.modify?.path || '目标文件' }} 执行补丁</span>
</span>
<div class="modify-meta" v-if="action.modify">
<span v-if="action.modify.total !== null && action.modify.total !== undefined">
· 共 {{ action.modify.total }} 处
@ -415,11 +495,17 @@
· 未完成 {{ action.modify.failed.length }} 处
</span>
</div>
<div class="modify-warning" v-if="action.modify?.forced">
⚠️ 未检测到结束标记,系统已在流结束时执行补丁。
<div class="modify-warning icon-label" v-if="action.modify?.forced">
<span class="icon icon-sm"
:style="iconStyle('triangleAlert')"
aria-hidden="true"></span>
<span>未检测到结束标记,系统已在流结束时执行补丁。</span>
</div>
<div class="modify-warning" v-if="action.modify?.failed && action.modify.failed.length">
⚠️ 未完成的序号:{{ action.modify.failed.map(f => f.index || f).join('、') || action.modify.failed.join('、') }},请根据提示重新输出。
<div class="modify-warning icon-label" v-if="action.modify?.failed && action.modify.failed.length">
<span class="icon icon-sm"
:style="iconStyle('triangleAlert')"
aria-hidden="true"></span>
<span>未完成的序号:{{ action.modify.failed.map(f => f.index || f).join('、') || action.modify.failed.join('、') }},请根据提示重新输出。</span>
</div>
</div>
</div>
@ -427,14 +513,22 @@
<!-- 修改结果摘要 -->
<div v-else-if="action.type === 'modify'" class="modify-placeholder">
<div class="modify-placeholder-content">
🛠️ {{ action.modify?.summary || `已处理 ${action.modify?.path || '目标文件'}` }}
<div class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('hammer')"
aria-hidden="true"></span>
<span>{{ action.modify?.summary || `已处理 ${action.modify?.path || '目标文件'}` }}</span>
</div>
<div class="modify-meta" v-if="action.modify">
<span v-if="action.modify.total">· 共 {{ action.modify.total }} 处</span>
<span v-if="action.modify.completed">· 完成 {{ action.modify.completed.length || action.modify.completed }} 处</span>
<span v-if="action.modify.failed">· 未完成 {{ action.modify.failed.length || action.modify.failed }} 处</span>
</div>
<div class="modify-warning" v-if="action.modify?.forced">
⚠️ 未检测到结束标记,系统已自动处理。
<div class="modify-warning icon-label" v-if="action.modify?.forced">
<span class="icon icon-sm"
:style="iconStyle('triangleAlert')"
aria-hidden="true"></span>
<span>未检测到结束标记,系统已自动处理。</span>
</div>
</div>
</div>
@ -451,10 +545,10 @@
<div class="arrow"></div>
<div class="status-icon">
<!-- 修复传递完整的tool对象 -->
<span class="tool-icon"
:class="getToolAnimationClass(action.tool)">
{{ getToolIcon(action.tool) }}
</span>
<span class="tool-icon icon icon-md"
:class="getToolAnimationClass(action.tool)"
:style="iconStyle(getToolIcon(action.tool))"
aria-hidden="true"></span>
</div>
<span class="status-text">
{{ getToolStatusText(action.tool) }}
@ -509,7 +603,9 @@
<div class="collapsible-header" @click="toggleBlock(`system-${index}`)">
<div class="arrow"></div>
<div class="status-icon">
<span class="tool-icon"></span>
<span class="tool-icon icon icon-md"
:style="iconStyle('info')"
aria-hidden="true"></span>
</div>
<span class="status-text">系统消息</span>
</div>
@ -622,9 +718,11 @@
:class="{ disabled: !category.enabled }"
@click.stop="updateToolCategory(category.id, !category.enabled)"
:disabled="streamingMessage || !isConnected || toolSettingsLoading">
<span class="submenu-label">
<span class="submenu-icon">{{ toolCategoryEmoji(category.id) }}</span>
{{ category.label }}
<span class="submenu-label icon-label">
<span class="icon icon-sm"
:style="iconStyle(toolCategoryIcon(category.id))"
aria-hidden="true"></span>
<span>{{ category.label }}</span>
</span>
<span class="entry-arrow">{{ category.enabled ? '禁用' : '启用' }}</span>
</button>
@ -675,7 +773,12 @@
:class="{ collapsed: rightCollapsed }"
:style="{ width: rightCollapsed ? '0px' : rightWidth + 'px' }">
<div class="sidebar-header">
<h3>👁️ 聚焦文件 ({{ Object.keys(focusedFiles).length }}/3)</h3>
<h3 class="icon-label">
<span class="icon icon-sm"
:style="iconStyle('eye')"
aria-hidden="true"></span>
<span>聚焦文件 ({{ Object.keys(focusedFiles).length }}/3)</span>
</h3>
</div>
<div class="focused-files" v-if="!rightCollapsed">
<div v-if="Object.keys(focusedFiles).length === 0" class="no-files">
@ -740,25 +843,24 @@
// 使用保存的原始代码内容
const codeContent = codeElement.dataset.originalCode || codeElement.textContent;
// 首次点击时保存原始图标
if (!button.dataset.originalIcon) {
button.dataset.originalIcon = button.textContent;
// 保存原始提示文本
if (!button.dataset.originalLabel) {
button.dataset.originalLabel = button.getAttribute('aria-label') || '复制代码';
}
navigator.clipboard.writeText(codeContent).then(() => {
button.textContent = '✓';
button.classList.add('copied');
button.setAttribute('aria-label', '已复制');
setTimeout(() => {
// 使用保存的原始图标恢复
button.textContent = button.dataset.originalIcon || '📋';
button.classList.remove('copied');
button.setAttribute('aria-label', button.dataset.originalLabel);
}, 2000);
}).catch(err => {
console.error('复制失败:', err);
// 即使失败也要恢复状态
button.textContent = button.dataset.originalIcon || '📋';
button.classList.remove('copied');
button.setAttribute('aria-label', button.dataset.originalLabel || '复制代码');
});
}
// 使用事件委托处理复制按钮点击

View File

@ -27,6 +27,46 @@
--claude-warning: #d99845;
}
/* 全局图标工具类 */
.icon {
--icon-size: 1em;
width: var(--icon-size);
height: var(--icon-size);
display: inline-block;
vertical-align: middle;
background-color: currentColor;
mask-image: var(--icon-src);
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
-webkit-mask-image: var(--icon-src);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
}
.icon-sm {
--icon-size: 16px;
}
.icon-md {
--icon-size: 20px;
}
.icon-lg {
--icon-size: 24px;
}
.icon-xl {
--icon-size: 32px;
}
.icon-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
html, body {
height: var(--app-viewport, 100vh);
}
@ -1298,6 +1338,15 @@ o-conversations {
gap: 4px;
}
.append-status {
font-weight: 500;
color: var(--claude-text);
}
.append-error-text {
color: #b0432a;
}
.append-warning {
color: var(--claude-warning);
font-weight: 500;
@ -1416,12 +1465,30 @@ o-conversations {
background: transparent;
color: var(--claude-text-secondary);
border: 1px solid rgba(121, 109, 94, 0.35);
padding: 6px 10px;
padding: 0;
border-radius: 6px;
font-size: 16px;
width: 36px;
height: 32px;
cursor: pointer;
transition: all 0.2s ease;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
}
.copy-code-btn::before {
content: '';
width: 18px;
height: 18px;
background-color: currentColor;
mask-image: url('/static/icons/clipboard.svg');
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
-webkit-mask-image: url('/static/icons/clipboard.svg');
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
}
.copy-code-btn:hover {
@ -1435,6 +1502,11 @@ o-conversations {
color: #f6fff8;
}
.copy-code-btn.copied::before {
mask-image: url('/static/icons/check.svg');
-webkit-mask-image: url('/static/icons/check.svg');
}
/* 代码块内容区 */
.code-block-wrapper pre {
background: #ffffff !important;
@ -1529,6 +1601,7 @@ o-conversations {
align-items: center;
justify-content: center;
font-size: 18px;
color: var(--claude-text);
}
/* 内容区域 */
@ -1950,10 +2023,6 @@ o-conversations {
gap: 8px;
}
.submenu-icon {
font-size: 16px;
}
.quick-menu-enter-active,
.quick-menu-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;