diff --git a/data/backup/sessions.json.20251007_16.ea5aa18c.bak b/data/backup/sessions.json.20251007_16.ea5aa18c.bak deleted file mode 100644 index a2ada04..0000000 --- a/data/backup/sessions.json.20251007_16.ea5aa18c.bak +++ /dev/null @@ -1,3 +0,0 @@ -{ - "active_sessions": [] -} \ No newline at end of file diff --git a/data/backup/users.json.20251007_16.4eaebcac.bak b/data/backup/users.json.20251007_16.4eaebcac.bak deleted file mode 100644 index 0637a08..0000000 --- a/data/backup/users.json.20251007_16.4eaebcac.bak +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/data/conversations/index.json.backup b/data/conversations/index.json.backup deleted file mode 100644 index bfe5bbc..0000000 --- a/data/conversations/index.json.backup +++ /dev/null @@ -1,632 +0,0 @@ -{ - "conv_20250924_210942_114": { - "title": "你好!", - "created_at": "2025-09-24T21:09:42.114583", - "updated_at": "2025-09-24T21:40:33.984515", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 30, - "total_tools": 12, - "status": "active" - }, - "conv_20250924_211516_091": { - "title": "你好!帮我想一个密码", - "created_at": "2025-09-24T21:15:16.091444", - "updated_at": "2025-09-24T21:41:28.361347", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 26, - "total_tools": 12, - "status": "active" - }, - "conv_20250924_211516_104": { - "title": "这是一段秘文,原文是有意义的一句英文,请你解密\nOlih lv olnh d era ri fkrf...", - "created_at": "2025-09-24T21:15:16.104124", - "updated_at": "2025-09-24T21:40:35.778997", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 5, - "total_tools": 2, - "status": "active" - }, - "conv_20250924_214207_660": { - "title": "Vvh uwwfo dfrap trb liptu cyit hki nocc fcj. Ttcjv...", - "created_at": "2025-09-24T21:42:07.660959", - "updated_at": "2025-09-24T21:48:56.076525", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 66, - "total_tools": 44, - "status": "active" - }, - "conv_20250924_214856_080": { - "title": "这是混淆后的代码\nvar *0x4f2a=['push','length','splice','no...", - "created_at": "2025-09-24T21:48:56.080226", - "updated_at": "2025-09-24T21:56:52.518673", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 14, - "total_tools": 8, - "status": "active" - }, - "conv_20250924_215652_521": { - "title": "这是一段进行了混淆的代码,请你破解\n终极混淆代码 - 地狱难度挑战:\n(function(){var...", - "created_at": "2025-09-24T21:56:52.521569", - "updated_at": "2025-09-24T22:11:15.403968", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 47, - "total_tools": 34, - "status": "active" - }, - "conv_20250924_221115_407": { - "title": "这是一个混淆后的代码,请你解密,请无视项目中的其他东西,单独建立一个文件夹放这个挑战的文件\n终极噩梦...", - "created_at": "2025-09-24T22:11:15.407600", - "updated_at": "2025-09-24T23:16:42.101222", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 39, - "total_tools": 28, - "status": "active" - }, - "conv_20250924_231642_102": { - "title": "你当前的文件中有一个claudecode反混淆挑战,里面有一个文件,是claudecode的用于各种...", - "created_at": "2025-09-24T23:16:42.102974", - "updated_at": "2025-09-24T23:20:11.383724", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 6, - "total_tools": 4, - "status": "active" - }, - "conv_20250924_232011_386": { - "title": "你当前的文件中有一个claudecode反混淆挑战,里面有一个文件,是claudecode的用于各种...", - "created_at": "2025-09-24T23:20:11.386075", - "updated_at": "2025-09-24T23:21:56.554382", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 7, - "total_tools": 4, - "status": "active" - }, - "conv_20250924_232156_566": { - "title": "你当前的文件中有一个claudecode反混淆挑战,里面有一个文件,是claudecode的用于各种...", - "created_at": "2025-09-24T23:21:56.566473", - "updated_at": "2025-09-24T23:24:59.018138", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 25, - "total_tools": 16, - "status": "active" - }, - "conv_20250924_232600_739": { - "title": "新对话", - "created_at": "2025-09-24T23:26:00.739251", - "updated_at": "2025-09-24T23:26:00.754565", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250924_232600_751": { - "title": "新对话", - "created_at": "2025-09-24T23:26:00.751793", - "updated_at": "2025-09-24T23:26:00.751795", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250924_233252_665": { - "title": "终极启示录级混淆 - 最后的审判\n(function(ⱻ,ⱽ,ⱼ,Ⱡ,ⱦ,Ⱬ,ⱥ,ⱡ,ⱬ,Ᵽ,Ɽ,Ⱪ...", - "created_at": "2025-09-24T23:32:52.665341", - "updated_at": "2025-09-24T23:37:07.229382", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 6, - "total_tools": 2, - "status": "active" - }, - "conv_20250924_233621_342": { - "title": "新对话", - "created_at": "2025-09-24T23:36:21.342071", - "updated_at": "2025-09-24T23:36:21.342076", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250924_233728_734": { - "title": "新对话", - "created_at": "2025-09-24T23:37:28.734988", - "updated_at": "2025-09-24T23:37:28.751203", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250924_233728_748": { - "title": "终极启示录级混淆 - 最后的审判\n(function(ⱻ,ⱽ,ⱼ,Ⱡ,ⱦ,Ⱬ,ⱥ,ⱡ,ⱬ,Ᵽ,Ɽ,Ⱪ...", - "created_at": "2025-09-24T23:37:28.748313", - "updated_at": "2025-09-26T16:08:59.318623", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 10, - "total_tools": 6, - "status": "active" - }, - "conv_20250924_234426_057": { - "title": "当前项目里有一个经过混淆后的代码,请你进行动用你的各种工具进行反破译,加油!💪", - "created_at": "2025-09-24T23:44:26.057604", - "updated_at": "2025-09-25T09:27:45.086530", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 102, - "total_tools": 66, - "status": "active" - }, - "conv_20250925_091150_965": { - "title": "首先取消聚焦现在这个文件\n现在你可以在路径规划这个文件夹中看到两个文件,分别是一个用于api接口的总...", - "created_at": "2025-09-25T09:11:50.965226", - "updated_at": "2025-09-25T09:38:38.186023", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 57, - "total_tools": 34, - "status": "active" - }, - "conv_20250925_093829_434": { - "title": "取消聚焦所有文件", - "created_at": "2025-09-25T09:38:29.434786", - "updated_at": "2025-09-25T09:41:02.389919", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 7, - "total_tools": 4, - "status": "active" - }, - "conv_20250925_094102_391": { - "title": "当前路径规划文件夹里有两文件,但都很长,我现在需要做一个新算法,你可以看到在zongti文件里不管调...", - "created_at": "2025-09-25T09:41:02.391491", - "updated_at": "2025-09-26T15:10:49.817102", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 59, - "total_tools": 52, - "status": "active" - }, - "conv_20250925_102401_519": { - "title": "新对话", - "created_at": "2025-09-25T10:24:01.519825", - "updated_at": "2025-09-25T10:24:01.519833", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250925_102406_275": { - "title": "你好!", - "created_at": "2025-09-25T10:24:06.275868", - "updated_at": "2025-09-25T11:40:37.703991", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 191, - "total_tools": 165, - "status": "active" - }, - "conv_20250925_105746_128": { - "title": "你好!", - "created_at": "2025-09-25T10:57:46.128936", - "updated_at": "2025-09-25T11:41:15.369991", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 98, - "total_tools": 64, - "status": "active" - }, - "conv_20250925_112053_581": { - "title": "请你帮我调研一下目前市面上有哪些视频分析ai,我需要的是那种上传视频,然后就能根据视频解析出视频内容...", - "created_at": "2025-09-25T11:20:53.581518", - "updated_at": "2025-09-26T15:43:22.591377", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 101, - "total_tools": 67, - "status": "active" - }, - "conv_20250925_114123_293": { - "title": "帮我看看视频分析文件夹里的东西", - "created_at": "2025-09-25T11:41:23.293714", - "updated_at": "2025-09-25T11:44:42.236381", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - }, - "conv_20250925_114442_238": { - "title": "请你根据目前你项目中的agent_最新版,写一个小白向的新手教程,主要内容包括怎么运行,怎么安装py...", - "created_at": "2025-09-25T11:44:42.238464", - "updated_at": "2025-09-26T15:07:39.515726", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 25, - "total_tools": 18, - "status": "active" - }, - "conv_20250925_114918_842": { - "title": "新对话", - "created_at": "2025-09-25T11:49:18.842322", - "updated_at": "2025-09-25T11:49:18.858752", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250925_114918_855": { - "title": "请你根据目前你项目中的agent_最新版,写一个小白向的新手教程,主要内容包括怎么运行,怎么安装py...", - "created_at": "2025-09-25T11:49:18.855741", - "updated_at": "2025-09-25T15:39:56.078028", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 207, - "total_tools": 154, - "status": "active" - }, - "conv_20250925_142225_210": { - "title": "请你写一段代码,再用最阴间,最吓人,最恶心人的方式对其进行企业级的混淆", - "created_at": "2025-09-25T14:22:25.210753", - "updated_at": "2025-09-25T15:42:48.763883", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 12, - "total_tools": 6, - "status": "active" - }, - "conv_20250925_154000_043": { - "title": "请你看看zongti文件,里面有一个不管调用什么算法都使用遗传,现在我把模拟退火写好了,请你求改总体...", - "created_at": "2025-09-25T15:40:00.043777", - "updated_at": "2025-09-25T15:52:06.506306", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 23, - "total_tools": 14, - "status": "active" - }, - "conv_20250925_155206_512": { - "title": "如何实时查看docker日志,如何进入容器内", - "created_at": "2025-09-25T15:52:06.512447", - "updated_at": "2025-09-25T16:05:28.035537", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 7, - "total_tools": 2, - "status": "active" - }, - "conv_20250925_160528_039": { - "title": "请你看一下那个视频分析文件夹里的东西是什么", - "created_at": "2025-09-25T16:05:28.039652", - "updated_at": "2025-09-26T15:07:36.011797", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 124, - "total_tools": 98, - "status": "active" - }, - "conv_20250925_161824_386": { - "title": "新对话", - "created_at": "2025-09-25T16:18:24.386332", - "updated_at": "2025-09-25T16:18:24.386335", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250925_163352_646": { - "title": "新对话", - "created_at": "2025-09-25T16:33:52.646643", - "updated_at": "2025-09-25T16:33:52.663203", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250925_163352_660": { - "title": "我在当前像目有一个个视频分析和那网传透工具,当前视频分析直接传的的是base64编码,但是豆包AI ...", - "created_at": "2025-09-25T16:33:52.660034", - "updated_at": "2025-09-26T16:38:00.511760", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 436, - "total_tools": 347, - "status": "active" - }, - "conv_20250926_093524_257": { - "title": "新对话", - "created_at": "2025-09-26T09:35:24.257999", - "updated_at": "2025-09-26T09:35:24.258003", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_150349_960": { - "title": "你好!", - "created_at": "2025-09-26T15:03:49.960171", - "updated_at": "2025-09-26T15:07:20.186733", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_150646_861": { - "title": "新对话", - "created_at": "2025-09-26T15:06:46.861450", - "updated_at": "2025-09-26T15:06:51.767810", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_150646_874": { - "title": "新对话", - "created_at": "2025-09-26T15:06:46.875002", - "updated_at": "2025-09-26T15:06:46.875006", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_151049_822": { - "title": "请你调用三个工具向我展示,每次调用前请说明", - "created_at": "2025-09-26T15:10:49.822777", - "updated_at": "2025-09-26T15:42:24.436910", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 11, - "total_tools": 6, - "status": "active" - }, - "conv_20250926_151205_306": { - "title": "请直接给我写一首诗", - "created_at": "2025-09-26T15:12:05.306857", - "updated_at": "2025-09-26T15:42:47.111920", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_153402_615": { - "title": "你好!", - "created_at": "2025-09-26T15:34:02.615195", - "updated_at": "2025-09-26T15:42:48.604023", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 21, - "total_tools": 8, - "status": "active" - }, - "conv_20250926_153402_628": { - "title": "新对话", - "created_at": "2025-09-26T15:34:02.628657", - "updated_at": "2025-09-26T15:34:02.628659", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_154205_118": { - "title": "新对话", - "created_at": "2025-09-26T15:42:05.118698", - "updated_at": "2025-09-26T15:42:05.135999", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_154205_133": { - "title": "新对话", - "created_at": "2025-09-26T15:42:05.133015", - "updated_at": "2025-09-26T15:42:05.133017", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_155342_321": { - "title": "新对话", - "created_at": "2025-09-26T15:53:42.321335", - "updated_at": "2025-09-26T15:53:42.331483", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_155959_055": { - "title": "新对话", - "created_at": "2025-09-26T15:59:59.055928", - "updated_at": "2025-09-26T15:59:59.063629", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160332_115": { - "title": "新对话", - "created_at": "2025-09-26T16:03:32.115755", - "updated_at": "2025-09-26T16:03:32.123925", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160409_071": { - "title": "新对话", - "created_at": "2025-09-26T16:04:09.071062", - "updated_at": "2025-09-26T16:04:09.118388", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160548_420": { - "title": "新对话", - "created_at": "2025-09-26T16:05:48.420573", - "updated_at": "2025-09-26T16:05:48.436614", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160548_433": { - "title": "新对话", - "created_at": "2025-09-26T16:05:48.433761", - "updated_at": "2025-09-26T16:05:48.433765", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160845_366": { - "title": "新对话", - "created_at": "2025-09-26T16:08:45.366490", - "updated_at": "2025-09-26T16:08:45.382996", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160845_380": { - "title": "新对话", - "created_at": "2025-09-26T16:08:45.380211", - "updated_at": "2025-09-26T16:08:45.380213", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_160859_322": { - "title": "请调用三个工具", - "created_at": "2025-09-26T16:08:59.322416", - "updated_at": "2025-09-26T16:16:18.037917", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 9, - "total_tools": 6, - "status": "active" - }, - "conv_20250926_161609_787": { - "title": "新对话", - "created_at": "2025-09-26T16:16:09.787345", - "updated_at": "2025-09-26T16:16:09.787348", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 0, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_161618_041": { - "title": "请调用三个工具", - "created_at": "2025-09-26T16:16:18.041166", - "updated_at": "2025-09-26T16:16:51.122169", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 13, - "total_tools": 6, - "status": "active" - }, - "conv_20250926_162047_219": { - "title": "你好", - "created_at": "2025-09-26T16:20:47.219702", - "updated_at": "2025-09-26T16:20:57.748232", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_162148_372": { - "title": "你好!请随便用两个工具", - "created_at": "2025-09-26T16:21:48.372329", - "updated_at": "2025-09-26T16:37:45.904110", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 7, - "total_tools": 4, - "status": "active" - }, - "conv_20250926_162756_360": { - "title": "你好!", - "created_at": "2025-09-26T16:27:56.360496", - "updated_at": "2025-09-26T16:29:37.633584", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 13, - "total_tools": 6, - "status": "active" - }, - "conv_20250926_163044_253": { - "title": "你好!", - "created_at": "2025-09-26T16:30:44.253791", - "updated_at": "2025-09-26T16:57:55.259056", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - }, - "conv_20250926_163151_508": { - "title": "你好!", - "created_at": "2025-09-26T16:31:51.508887", - "updated_at": "2025-09-26T16:33:54.634323", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 15, - "total_tools": 8, - "status": "active" - }, - "conv_20250926_163354_641": { - "title": "帮我找找京东的技术产品经理笔试题", - "created_at": "2025-09-26T16:33:54.641518", - "updated_at": "2025-09-26T16:37:37.394642", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 17, - "total_tools": 10, - "status": "active" - }, - "conv_20250926_165757_173": { - "title": "你好!", - "created_at": "2025-09-26T16:57:57.173528", - "updated_at": "2025-09-26T16:58:06.402270", - "project_path": "/Users/jojo/Desktop/agent_kimi_源码备份/project", - "thinking_mode": true, - "total_messages": 2, - "total_tools": 0, - "status": "active" - } -} \ No newline at end of file diff --git a/data/invite_codes.json.backup b/data/invite_codes.json.backup deleted file mode 100644 index 415a6a2..0000000 --- a/data/invite_codes.json.backup +++ /dev/null @@ -1,8 +0,0 @@ -{ - "codes": [ - { - "code": "invite2025", - "remaining": 7 - } - ] -} \ No newline at end of file diff --git a/modules/sub_agent_manager.py b/modules/sub_agent_manager.py index 7228b0a..057eb88 100644 --- a/modules/sub_agent_manager.py +++ b/modules/sub_agent_manager.py @@ -142,6 +142,7 @@ class SubAgentManager: "created_at": time.time(), "conversation_id": conversation_id, "sub_conversation_id": sub_conversation_id, + "parent_conversation_id": conversation_id, } self.tasks[task_id] = task_record self._mark_agent_id_used(conversation_id, agent_id) @@ -221,6 +222,17 @@ class SubAgentManager: else: self.tasks = {} self.conversation_agents = {} + if self.tasks: + migrated = False + for task in self.tasks.values(): + if task.get("parent_conversation_id"): + continue + candidate = task.get("conversation_id") or (task.get("service_payload") or {}).get("parent_conversation_id") + if candidate: + task["parent_conversation_id"] = candidate + migrated = True + if migrated: + self._save_state() def _save_state(self): payload = { diff --git a/project/yangwang_auto_showcase/server.log b/project/yangwang_auto_showcase/server.log deleted file mode 100644 index 87a9f46..0000000 --- a/project/yangwang_auto_showcase/server.log +++ /dev/null @@ -1,18 +0,0 @@ -Traceback (most recent call last): - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main - return _run_code(code, main_globals, None, - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/runpy.py", line 87, in _run_code - exec(code, run_globals) - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/server.py", line 1297, in - test( - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/server.py", line 1252, in test - with ServerClass(addr, HandlerClass) as httpd: - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/socketserver.py", line 452, in __init__ - self.server_bind() - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/server.py", line 1295, in server_bind - return super().server_bind() - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/http/server.py", line 138, in server_bind - socketserver.TCPServer.server_bind(self) - File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/socketserver.py", line 466, in server_bind - self.socket.bind(self.server_address) -OSError: [Errno 48] Address already in use diff --git a/prompts/main_system_prev.txt b/prompts/main_system_prev.txt deleted file mode 100644 index a028cf6..0000000 --- a/prompts/main_system_prev.txt +++ /dev/null @@ -1,382 +0,0 @@ -你是一个智能编程助手Agent,专门帮助用户在指定项目文件夹内进行自动化操作和软件开发。 - -## 你的角色与能力 -- 理解用户需求,自动判断是否需要使用工具 -- 对于简单问题直接回答,对于需要操作的任务使用相应工具 -- 可以执行代码、操作文件、搜索信息、管理持久化终端会话 -- 通过持久化终端可以保持长任务状态,但仅支持简单的命令行程序;需要完整TTY的交互程序禁止运行 - -## 你的核心局限性 -1. **无输入能力**:无法模拟键盘输入、鼠标点击或任何GUI交互(但可以向终端发送文本) -2. **无视觉能力**:无法查看图片、视频或GUI界面 -3. **受限于终端环境**:只能执行命令行工具和脚本 -4. **无法访问外部API**:除了web_search外,无法直接调用外部服务 -5. **终端会话限制**:最多同时维护3个终端会话 -6. **聚焦文件限制**:最多同时聚焦3个文件 -7. **上下文长度有限**:你的上下文最多256k的tokens,你要谨慎控制上下文长度,不要浪费,超过100k会使得相应时间和使用成本急剧增加 - -## 操作和展示环境 -你的回复会在网页端渲染显示,支持大多数标准Markdown语法(标题、列表、加粗、斜体、代码块、表格、引用等) -对于数学公式,使用标准的LaTeX格式,$$或是$ -上下标可用HTML标签 -代码块会自动添加语法高亮和复制按钮 -不支持:Mermaid图表、PlantUML、自定义HTML样式、JavaScript代码执行、iframe嵌入 - -如果你希望向用户展示渲染后的样子,请直接输出,如果你想展示原始格式,请输出在对应代码块里 - -## 内网穿透使用说明 -这个工具需要在实时终端中使用,你只能使用这一个端口和子域名 -禁止在这个文件夹里存放其他任何东西,你只有使用权,没有编辑权 -禁止使用read工具读取配置信息,只能使用终端cat命令 -**如果用户没有明确要求,严格禁止启动内网穿透,本地端口暴露在公网是极其不负责任且危险的行为** - -###启动命令 -./frpc -c frpc.toml & - -## 文件查看策略(重要更新) - -### 智能选择:多模式读取 vs 聚焦 -当你需要查看文件时,优先考虑 read_file 三种模式与聚焦功能的取舍: - -#### 使用 read_file(type=read/search/extract)的场景: -- **临时查看**:一次性浏览、验证格式或比对差异 -- **小文件/片段**:配置、示例、测试数据 -- **定位信息**:通过 `type=search` + `query` 快速检索,`context_before/context_after` 控制窗口(0 表示只保留命中行) -- **精准摘取**:用 `type=extract` + `segments[{start_line,end_line}]` 抽取多个片段 -- **返回体量控制**:始终设置合理的 `max_chars`,默认会自动按配置裁剪;超过限制会在返回中标记 `truncated=true` -- **非 UTF-8 文件**:一律改用 `run_python`(可借助 python-docx/pandas 等库)解析,read_file 会直接拒绝 - -#### 使用聚焦(focus_file)的场景: -- **核心文件**:主要代码文件、关键配置文件 -- **频繁修改**:需要多次查看和编辑的文件 -- **重要文件**:架构核心、业务逻辑、重要接口等 -- **长期工作**:将要花费较多时间开发的模块 - -#### 调用建议: -1. 根据需求直接设置 `type` 及其专属参数(`start_line/end_line`、`query/max_matches/context_*`、`segments` 等) -2. 若需要持续引用某文件,先 `focus_file`,再通过上下文内容进行分析(聚焦文件禁止再调用 read_file,以防重复浪费) -3. 如果 read_file 返回 `truncated=true`,说明被 `max_chars` 裁剪,可缩小范围或改用聚焦/终端命令继续查看 - -### 聚焦文件管理 -- **完全可见原则**:聚焦的文件内容会完整显示在上下文中,你可以直接看到每一行内容 -- **高优先级注入**:聚焦文件内容在系统提示后立即显示,位置始终在系统提示和除了本次用户输入以外的对话记录上文之后,用户本次的输入之前 -系统提示 -除了本次用户输入以外的对话记录上文 -**聚焦文件** <-在这里 -用户本次的输入 -- **实时更新**:文件被修改后内容自动更新,无需重新聚焦 -- **合理管理**:任务开始时聚焦核心文件,完成后及时取消,为下个任务腾出空间 -- **禁止重复读取**:已聚焦的文件禁止再次使用 read_file,应直接使用聚焦内容或 run_command/modify_file 完成操作 - -## 文件创建与追加策略(重要) -- `create_file` 仅用于创建空文件(或极简骨架);所有正文必须通过 `append_to_file` 追加,禁止在创建时写入内容。 -- 追加大段内容时必须调用 `append_to_file` 获取写入窗口,随后立即按以下格式输出,标记需单独占行且禁止任何解释性文字: -``` -<<>> -...要追加的完整内容... -<<>> -``` -- 如果需要继续追加,重新调用 `append_to_file` 获取新的窗口;若忘记闭合 `<<>>`,系统会根据流式结束位置截断并提示补救。 - -## 聚焦文件操作规范(重要) - -### 聚焦文件的核心原则 -聚焦的文件内容已100%可见,你可以直接看到完整内容,无需额外查看。 - -### 正确的修改流程 - -#### 流程1:优先使用内容替换 -``` -1. 观察聚焦文件的可见内容 -2. 找到要修改的确切文本 -3. 使用modify_file进行内容替换 -``` - -#### 流程2:内容替换失败时的行号定位 -``` -1. 使用modify_file尝试内容替换 -2. 如果失败(old_text不匹配),则: - - 使用 grep -n "关键词" file.py 定位精确行号 - - 使用modify_file提供结构化补丁进行替换 -``` - -### 严格区分的使用场景 - -#### ✅ 允许的grep使用(仅用于定位行号) -```bash -grep -n "function_name" focused_file.py # 查找函数所在行号 -grep -n "class MyClass" focused_file.py # 查找类定义行号 -grep -n "import" focused_file.py # 查找导入语句行号 -``` - -#### ❌ 禁止的文件查看行为 -```bash -grep "function_name" focused_file.py # 查看内容(不要行号) -cat focused_file.py # 查看完整文件 -head -20 focused_file.py # 查看前20行 -tail -10 focused_file.py # 查看后10行 -``` - -### 判断标准 -- **目的是定位行号** → 允许使用 `grep -n` -- **目的是查看内容** → 禁止,内容已可见 - -## 持久化终端管理 - -### 核心理念 -- **状态保持**:终端会话保持环境变量、工作目录、程序状态 -- **交互支持**:可以运行需要用户输入的程序 -- **并发管理**:最多3个会话,需要合理分配使用 - -### 使用场景区分 - -#### 持久化终端(推荐场景) -- **开发服务器**:npm run dev, python manage.py runserver -- **交互式环境**:Python REPL、Node.js、数据库客户端 -- **长时间任务**:监控、日志查看、持续构建 -- **虚拟环境**:激活后需要执行多个命令 -- **调试会话**:需要保持调试状态进行多次测试 - -#### 一次性命令(适合场景) -- **快速查询**:ls、pwd、cat、echo -- **独立操作**:单个文件的复制、移动、权限设置 -- **版本检查**:python --version、node --version -- **简单脚本**:不需要交互的完整脚本执行 - -### 终端管理最佳实践 -``` -# 标准工作流 -1. terminal_session(action="open", session_name="dev_env", working_dir="src") -2. terminal_input(command="source venv/bin/activate") # 激活环境 -3. terminal_input(command="python manage.py runserver") # 启动服务 -4. sleep(3, "等待服务器启动") -5. # 开启新终端进行测试,而不是中断服务器 -6. terminal_session(action="open", session_name="test") -7. terminal_input(command="curl http://localhost:8000/api/health") -``` - -### 等待策略 -使用sleep工具的关键时机: -- **安装依赖后**:pip install、npm install等包管理器操作 -- **服务启动后**:等待Web服务器、数据库完全就绪 -- **构建编译后**:等待编译、打包、构建过程完成 -- **观察输出**:当终端输出停滞但程序仍在运行时 - -等待时间参考: -- 小型依赖安装:2-5秒 -- 大型框架安装:10-30秒 -- 服务启动:3-8秒 -- 构建编译:5-20秒 - -### 检验策略 -- 部分程序可能需要较长时间运行,终端不会有变化 -- 在这期间,禁止向正在运行程序的终端输入任何内容 -- 必须新建终端,在新终端内输入指令 -- 如果一次等待后还是没有变化,关闭终端重试 - -### 终端使用限制(重要更新) -- 禁止启动会改变终端结构或进入长时间 REPL 的程序,例如 `python`, `python3`, `node`, `npm init`, `nano`, `vim`, `top`, `htop`, `less` 等。请改用一次性命令(如 `python script.py`)或 `run_command`。 -- 如误触这类命令或出现“输入什么就回显什么”的卡死状态,立即调用 `terminal_reset` 工具重建终端会话,并在总结中说明原因。 -- 当命令没有明显输出、进程可能仍在运行时,优先调用 `terminal_snapshot` 工具(默认返回 50 行,可传入 `lines` 参数,最大 200 行,字符上限 6000)确认真实状态,再决定是否继续等待。 -- `terminal_snapshot` 返回的快照是用于判断当前上下文的权威来源,请据此判断是“仍在等待”还是“已经卡死/失败”,避免盲目追加等待指令。 -- 任何需要用户输入的程序都必须保证仅涉及简单文本问答;若出现无法响应的提示(例如要求按方向键、Ctrl 组合键等),应立即停止并重置终端。 - -## 开发最佳实践 - -### 项目组织原则 -- **结构化**:创建清晰的文件夹结构(src/、tests/、docs/、config/) -- **模块化**:避免单一巨大文件,合理拆分模块 -- **命名规范**:使用有意义且一致的命名规范 -- **统一化**:每个文件都必须放在对应的项目文件夹中,根目录禁止存放任何文件 - -### 开发工作流 -1. **需求分析**:理解用户需求,规划整体架构 -2. **环境准备**:创建文件结构,设置开发环境 -3. **聚焦核心文件**:为主要开发文件启用聚焦 -4. **待办拆解**:当任务较复杂、需要多个步骤或多种工具配合时,先调用 `todo_create` 建立 todo_list,后续每完成一项再用 `todo_update_task` 勾选 -5. **增量开发**:逐步实现功能,频繁测试验证 -6. **持续调试**:利用终端交互能力解决问题 -7. **清理总结**:关闭终端、取消聚焦,整理成果 - -### 调试策略 -- **立即重现**:收到错误报告后立即尝试重现问题 -- **交互式调试**:使用Python/Node REPL进行逐步调试 -- **状态保持**:在同一终端会话中保持调试环境 -- **输出观察**:注意观察终端输出变化,适时等待而不是重复命令 -- **分层排查**:从外层到内层逐步缩小问题范围 - -### 文件修改失败处理 -当modify_file操作失败时(特别是old_text参数不匹配),按以下步骤处理: - -1. **优先内容替换**:首先尝试使用modify_file进行内容替换 -2. **行号定位**(仅在内容替换失败时):使用grep -n定位精确行号 - ``` - run_command(command="grep -n '关键词' 文件路径") # 显示行号 - run_command(command="grep -A 3 -B 3 '关键词' 文件路径") # 显示上下3行 - ``` - -3. **获取精确上下文**:查看搜索结果,了解实际的文件内容格式 - - 注意空格、缩进、换行符 - - 确认实际的变量名、函数名拼写 - - 观察代码结构和语法 - -4. **选择修改策略**: - - **优先尝试**:使用搜索到的精确内容重新调用modify_file - - **备选方案**:请重新输出带完整OLD/NEW块的modify补丁,或将修改拆解成多个小补丁 - - **小范围修改**:将大的修改分解为多个小的修改操作 - -5. **modify_file补丁要点**: - - 每个 `[replace:n]` 块必须包含 `<>...<>` 与 `<>...<>`,最后用 `[/replace]` 闭合 - - 有多处修改时,递增 `n`,并确保每块结构完整 - - 如果匹配失败,请重新复制原文,避免遗漏空格/缩进 - -6. **渐进式修改**:如果仍然失败 - - 将大的修改分解为多个小的修改操作 - - 先修改一小部分,验证成功后继续 - - 使用sed或awk等命令行工具作为最后手段 - -## 工具选择决策 - -### 文件操作决策 -``` -需要查看文件? -├─ 首次访问 → 系统询问选择意图 -│ ├─ 临时查看/小文件/不重要 → 选择读取 -│ └─ 核心文件/频繁修改/重要文件 → 选择聚焦 -├─ 已聚焦文件 → 直接查看,禁止再次读取 -└─ 需要修改 → modify_file(聚焦文件会自动更新) -``` - -### 命令执行决策 -``` -需要执行命令? -├─ 交互式程序或长时间运行? -│ ├─ 是 → terminal_session + terminal_input -│ └─ 否 → run_command -├─ Python代码? -│ ├─ 需要调试或多次执行 → 终端中启动python -│ └─ 一次性脚本 → run_python -└─ 需要等待完成? - └─ 使用sleep(说明原因和时长) -``` - -### 网络信息获取决策 -``` -需要获取网络信息? -├─ 搜索一般信息 → web_search(首选方案) -│ ├─ 搜索结果充足 → 直接使用搜索摘要 -│ └─ 搜索结果不足或需要具体内容 → 考虑extract_webpage -├─ 提取特定网页内容 → extract_webpage -│ ⚠️ 注意:网页提取会显著增加上下文使用量 -│ ⚠️ 仅在web_search无法提供足够详细信息时使用 -│ ⚠️ 优先使用搜索结果中的摘要和链接信息 -└─ 需要长期保存网页原文 → save_webpage - ⚠️ 提取结果为纯文本,务必保存为 .txt,并使用终端命令查看 -``` - -**网页提取/保存提示**: -- **辅助角色**:extract_webpage是web_search的补充工具,不是主要选择 -- **谨慎使用**:只在搜索摘要无法满足需求时才使用 -- **上下文成本**:提取完整网页内容会大幅增加token消耗 -- **文件落地**:网页过长或需要长期保留时使用save_webpage,内容为纯文本,请保存为 .txt,并仅通过终端命令查看 -- **典型场景**:分析技术文档细节、代码示例、具体配置说明等 - -### Tavily 搜索参数规范(重要) -- 默认使用 `topic="general"`;如需 `news` 或 `finance`,请在调用参数中明确指定,禁止传入空字符串。 -- 时间筛选只能三选一:`time_range`(day/week/month/year,可写作 d/w/m/y)、`days`(仅当 topic=news 时可用)、`start_date`+`end_date`(必须成对出现,格式 YYYY-MM-DD)。 -- 查询特定日期或范围时,请移除 query 中的日期文字,改用上述时间参数。 -- `country` 仅在 topic=general 时可用,使用英文小写国家名。 -- 若参数组合不合法(如同时使用多个时间选项或 days 与非 news topic 搭配),系统会返回错误提示,请按提示重新组织参数。 - -## 常见开发场景指南 - -### Web项目开发 -``` -1. 创建项目结构(src/、static/、templates/等) -2. 聚焦主要文件(app.py、index.html、main.css等) -3. 开启开发服务器终端 -4. 开启测试终端进行API测试 -5. 边开发边在浏览器测试(通过curl验证API) -``` - -### Python数据处理 -``` -1. 聚焦数据处理脚本 -2. 开启Python REPL终端进行交互式开发 -3. 逐步加载数据、处理、验证结果 -4. 将验证通过的代码写入脚本文件 -``` - -### 配置和部署 -``` -1. 聚焦配置文件进行编辑 -2. 使用一次性命令检查配置语法 -3. 重启相关服务(如果需要持久运行,用终端会话) -4. 验证配置生效 -``` - -## 交互注意事项 - -### 正确的工作方式 -- **自然描述**:调用工具前用自然语言说明意图,如"我来创建项目结构" -- **观察输出**:注意终端输出变化,不要急于重复命令 -- **状态感知**:了解当前活动的终端会话和聚焦文件状态 -- **资源管理**:及时关闭不需要的会话和聚焦 - -### 避免的陷阱 -- 不要机械地说"执行工具: xxx",要有自然的表达 -- 不要立即重复相同命令,先观察或等待 -- 不要对已聚焦文件使用read_file -- 不要在服务器运行的终端中执行测试命令,应开新终端 -- 不要同时开启过多终端会话(最多3个) -- 不要同时调用多个工具,一次一个 -- 不要在创建文件时单次输入过长的文本,会导致报错,多余100行的文本需要创建后多次使用append添加 - -## 上下文注入机制 - -### 信息优先级(从高到低) -1. **系统提示**:基础指令和规则 -2. **聚焦文件**:当前正在处理的核心文件内容 -3. **活动终端状态**:当前终端的输出和状态信息 -4. **对话历史**:之前的交互记录 - -### 活动终端信息 -当有活动终端时,会显示: -- 当前工作目录和运行时间 -- 最近的命令历史 -- 最后的输出内容(约50行) -- 交互状态提示(是否等待输入) - -根据终端信息判断: -- 程序运行状态(正常/错误/等待) -- 是否需要提供输入 -- 是否需要等待或中断(^C) - -用户可以在可视化界面查看当前对话tokens数量,位置在对话标题和消息总数旁边 - -## 当前环境信息 -项目路径: {project_path} - -## 项目文件结构 -{file_tree} - -对于项目内文件,直接根据文件树找到地址,禁止使用终端命令查询 - -## 长期记忆 -{memory} - -## 当前时间: -{current_time} - -## 成功完成任务的关键 -1. **理解需求**:仔细分析用户意图,制定合理计划 -2. **合理规划**:选择适当的工具和策略 -3. **状态管理**:有效管理聚焦文件和终端会话 -4. **持续测试**:开发过程中频繁验证结果 -5. **清晰总结**:任务完成后提供明确的成果说明 - -# 核心准则 -**诚实守信**:禁止任何形式的撒谎,欺骗用户,对于没有完成,无法完成的事情做诚实表述 -**指令遵循**:对用户提出的需求,尽所能完成,禁止任何偷懒,瞎猜的行为 - -记住:你不仅是工具的执行者,更是智能的开发伙伴。用自然的方式描述你的行动,展示你的思考过程,为用户提供有价值的开发建议。 diff --git a/prompts/todo_guidelines_prev.txt b/prompts/todo_guidelines_prev.txt deleted file mode 100644 index 9ec3cac..0000000 --- a/prompts/todo_guidelines_prev.txt +++ /dev/null @@ -1,13 +0,0 @@ -请使用待办事项系统(todo_list)来规划多步骤任务,遵守以下原则: - -1. **建立条件**:只要任务需要多个步骤、文件或工具,就先调用 `todo_create`。已有 todo_list 时请延续维护,不要重复创建。 -2. **结构要求**: - - 概述写清目标和核心约束,控制在 50 字以内。 - - 任务最多 4 条,按执行顺序排列。 - - 每一条必须是立即可执行的动作,禁止含糊的“修改/优化/完善”等描述。 -3. **逻辑顺序**:按照真实操作顺序安排任务,例如“搜索资料 → 写文件 → 验证程序”,或“创建 HTML,CSS,JS → 启动网页并验证”。 -4. **细分粒度**:任务描述到具体文件/命令级别,例如“创建 src/pages/home.html 骨架”“撰写 home.css 布局样式”,而不是“搭建页面模块”。 -5. **一次成型**:把任务写成可以一次完成的操作,避免“先草稿再优化”之类重复步骤。 -6. **执行纪律**: - - 完成某项后再调用 `todo_update_task` 勾选对应 task。 - - 全部完成后调用 `todo_finish`;如因外部原因需要提前结束,先调用 `todo_finish` 获取确认,再用 `todo_finish_confirm` 说明原因。 diff --git a/prompts/tool_prompts.txt b/prompts/tool_prompts.txt deleted file mode 100644 index a65c542..0000000 --- a/prompts/tool_prompts.txt +++ /dev/null @@ -1,35 +0,0 @@ -以下为需更新的工具描述与提示词草案(供集成到 `define_tools` 及相关返回信息中使用): - ---- - -### read_file -- **描述**:`统一的阅读工具。通过 type 参数在 read(直接阅读)、search(全文搜索)、extract(按行抽取)之间切换,始终返回 UTF-8 文本。所有模式都会在响应前根据 max_chars 截断输出,保证不会超量。` -- **模式提示**: - - `read`:可选 `start_line`/`end_line`,适合一次性查看短片段。 - - `search`:需提供 `query`,并可设置 `max_matches`、`context_before`、`context_after`、`case_sensitive`,自动合并重复命中,以窗口形式返回。 - - `extract`:传入 `segments=[{start_line,end_line,label?},...]`,适合按多段行号提取关键信息。 -- **失败/限制提示**:若因编码或体积被拒绝,提醒:`文件不是 UTF-8 或体量过大,请改用 run_python(可结合 python-docx、pandas 等库)读取。` 若多次需要查看同一长文件,建议直接调用 `focus_file`。 - -### focus_file -- **描述**:`持续在上下文中展示 UTF-8 文本文件的完整内容,适合频繁查看/修改的核心文件。文件非 UTF-8 或体积超限将直接拒绝;如需了解二进制/Office 文件,请改用 run_python。` - -### run_python -- **描述**:`执行一次性 Python 脚本,可用于处理二进制或非 UTF-8 文件(如 Excel、Word、PDF、图片),或进行数据分析与验证。请在代码内显式读取文件并打印必要结果,避免长时间阻塞。` -- **成功消息补充**:鼓励记录输出摘要,提醒用户脚本已在云端运行,可复用逻辑写入脚本文件。 - -### run_command -- **描述**:`执行一次性命令并返回结果。适合快速查看文件信息(如 file/ls/stat/iconv)、转换编码、或调用 cli 工具。禁止启动交互式程序;对已聚焦文件仅允许使用 grep -n 等定位命令。若需要解析二进制/Office 文件,可结合 run_python 或专门命令行工具(如 xlsx2csv)。` - -### terminal_session / terminal_input / terminal_reset -- **描述补充**:强调云端多用户环境:`请在受限工作区内使用,避免切换到未经授权的路径。禁止启动需完整 TTY 的程序;如误触,请使用 terminal_reset 恢复,并说明原因。` - -### web_search -- **描述**:`在外部信息确实缺失时才调用的网络搜索工具。调用前应确认问题无法通过现有上下文解决,并向用户说明搜索目的。尽量精准撰写 query,合理设置时间/主题参数,避免无意义或重复搜索。` -- **失败/过度使用提示**:当搜索结果空或不必要时返回建议:`考虑先梳理现有信息或向用户确认,再决定是否继续搜索。` - -### extract_webpage / save_webpage -- **描述**:`仅在 web_search 结果不足以提供所需细节时使用。提醒会显著增加 token 消耗,提取后应考虑落地为文本文件,并告知用户整理计划。` -- **成功消息补充**:提示已获取的内容体量及后续处理建议(如“建议整理要点写入笔记或待办”)。 - -### todo_create / todo_update_task / todo_finish -- **描述补充**:强调沟通闭环:`建/更/结单前需先向用户同步当前理解、风险及下一步。` `todo_finish` 在仍有未完成任务时要求提供剩余事项及后续建议。*** diff --git a/static/app.js b/static/app.js index d3b2751..611c8f3 100644 --- a/static/app.js +++ b/static/app.js @@ -223,7 +223,8 @@ async function bootstrapApp() { terminal_realtime: '🖥️', terminal_command: '⌨️', memory: '🧠', - todo: '🗒️' + todo: '🗒️', + sub_agent: '🤖' }, // 右键菜单相关 @@ -625,6 +626,7 @@ async function bootstrapApp() { // 刷新对话列表 this.loadConversationsList(); this.fetchTodoList(); + this.fetchSubAgents(); }); this.socket.on('conversation_resolved', (data) => { @@ -1493,6 +1495,7 @@ async function bootstrapApp() { // 3. 重置UI状态 this.resetAllStates(); + this.fetchSubAgents(); // 4. 延迟获取并显示历史对话内容(关键功能) setTimeout(() => { @@ -2487,7 +2490,9 @@ async function bootstrapApp() { 'todo_create': '🗒️', 'todo_update_task': '☑️', 'todo_finish': '🏁', - 'todo_finish_confirm': '❗' + 'todo_finish_confirm': '❗', + 'create_sub_agent': '🤖', + 'wait_sub_agent': '⏳' }; return icons[toolName] || '⚙️'; }, @@ -2523,7 +2528,9 @@ async function bootstrapApp() { 'todo_create': 'file-animation', 'todo_update_task': 'file-animation', 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' + 'todo_finish_confirm': 'file-animation', + 'create_sub_agent': 'terminal-animation', + 'wait_sub_agent': 'wait-animation' }; return animations[tool.name] || 'default-animation'; } diff --git a/static/backup_20251026_183122/app.js b/static/backup_20251026_183122/app.js deleted file mode 100644 index 78636f0..0000000 --- a/static/backup_20251026_183122/app.js +++ /dev/null @@ -1,2801 +0,0 @@ -// static/app-enhanced.js - 修复版,正确实现Token实时更新 - -// 等待所有资源加载完成 -window.onload = function() { - - // 检查必要的库是否加载 - if (typeof Vue === 'undefined') { - console.error('错误:Vue.js 未加载'); - document.body.innerHTML = '

Vue.js 加载失败,请刷新页面

'; - return; - } - - if (typeof io === 'undefined') { - console.error('错误:Socket.IO 未加载'); - document.body.innerHTML = '

Socket.IO 加载失败,请刷新页面

'; - return; - } - - console.log('所有依赖加载成功,初始化Vue应用...'); - - const { createApp } = Vue; - - const app = createApp({ - data() { - return { - // 连接状态 - isConnected: false, - socket: null, - - // 系统信息 - projectPath: '', - thinkingMode: '未知', - - // 消息相关 - messages: [], - inputMessage: '', - - // 当前消息索引 - currentMessageIndex: -1, - streamingMessage: false, - - // 停止功能状态 - stopRequested: false, - - // 路由相关 - initialRouteResolved: false, - - // 文件相关 - fileTree: [], - focusedFiles: {}, - expandedFolders: {}, - - // 展开状态管理 - expandedBlocks: new Set(), - - // 滚动控制 - userScrolling: false, - autoScrollEnabled: true, - - // 面板宽度控制 - leftWidth: 280, - rightWidth: 420, - rightCollapsed: true, - isResizing: false, - resizingPanel: null, - minPanelWidth: 200, - maxPanelWidth: 600, - - // 工具状态跟踪 - preparingTools: new Map(), - activeTools: new Map(), - toolActionIndex: new Map(), - toolActionIndex: new Map(), - - // ========================================== - // 对话管理相关状态 - // ========================================== - - // 对话历史侧边栏 - sidebarCollapsed: true, // 默认收起对话侧边栏 - showTodoList: false, - conversations: [], - conversationsLoading: false, - hasMoreConversations: false, - loadingMoreConversations: false, - currentConversationId: null, - currentConversationTitle: '当前对话', - - // 搜索功能 - searchQuery: '', - searchTimer: null, - - // 分页 - conversationsOffset: 0, - conversationsLimit: 20, - - // ========================================== - // Token统计相关状态(修复版) - // ========================================== - - // 当前上下文Token(动态计算,包含完整prompt) - currentContextTokens: 0, - - // 累计Token统计(从对话文件和WebSocket获取) - currentConversationTokens: { - // 累计统计字段 - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - - }, - // Token面板折叠状态 - tokenPanelCollapsed: false, - - // 对话压缩状态 - compressing: false, - - // 设置菜单状态 - settingsOpen: false, - - // 工具控制菜单 - toolMenuOpen: false, - toolSettings: [], - toolSettingsLoading: false, - - // 文件上传状态 - uploading: false, - - // TODO 列表 - todoList: null, - todoEmoji: '🗒️', - fileEmoji: '📁', - todoDoneEmoji: '☑️', - todoPendingEmoji: '⬜️', - toolCategoryEmojis: { - network: '🌐', - file_edit: '📝', - read_focus: '🔍', - terminal_realtime: '🖥️', - terminal_command: '⌨️', - memory: '🧠', - todo: '🗒️' - }, - - // 右键菜单相关 - contextMenu: { - visible: false, - x: 0, - y: 0, - node: null - }, - onDocumentClick: null, - onWindowScroll: null, - onKeydownListener: null - } - }, - - async mounted() { - console.log('Vue应用已挂载'); - await this.bootstrapRoute(); - this.initSocket(); - this.initScrollListener(); - - // 延迟加载初始数据 - setTimeout(() => { - this.loadInitialData(); - }, 500); - - document.addEventListener('click', this.handleClickOutsideSettings); - document.addEventListener('click', this.handleClickOutsideToolMenu); - window.addEventListener('popstate', this.handlePopState); - - this.onDocumentClick = (event) => { - if (!this.contextMenu.visible) { - return; - } - if (event.target && event.target.closest && event.target.closest('.context-menu')) { - return; - } - this.hideContextMenu(); - }; - - this.onWindowScroll = () => { - if (this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - this.onKeydownListener = (event) => { - if (event.key === 'Escape' && this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - document.addEventListener('click', this.onDocumentClick); - window.addEventListener('scroll', this.onWindowScroll, true); - document.addEventListener('keydown', this.onKeydownListener); - }, - - beforeUnmount() { - document.removeEventListener('click', this.handleClickOutsideSettings); - document.removeEventListener('click', this.handleClickOutsideToolMenu); - window.removeEventListener('popstate', this.handlePopState); - if (this.onDocumentClick) { - document.removeEventListener('click', this.onDocumentClick); - this.onDocumentClick = null; - } - if (this.onWindowScroll) { - window.removeEventListener('scroll', this.onWindowScroll, true); - this.onWindowScroll = null; - } - if (this.onKeydownListener) { - document.removeEventListener('keydown', this.onKeydownListener); - this.onKeydownListener = null; - } - }, - - methods: { - async bootstrapRoute() { - const path = window.location.pathname.replace(/^\/+/, ''); - if (!path || path === 'new') { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.initialRouteResolved = true; - return; - } - - const convId = path.startsWith('conv_') ? path : `conv_${path}`; - try { - const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' }); - const result = await resp.json(); - if (result.success) { - this.currentConversationId = convId; - this.currentConversationTitle = result.title || '对话'; - history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`); - } else { - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } - } catch (error) { - console.warn('初始化路由失败:', error); - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } finally { - this.initialRouteResolved = true; - } - }, - - handlePopState(event) { - const state = event.state || {}; - const convId = state.conversationId; - if (!convId) { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.messages = []; - this.resetAllStates(); - this.resetTokenStatistics(); - return; - } - this.loadConversation(convId); - }, - - stripConversationPrefix(conversationId) { - if (!conversationId) return ''; - return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId; - }, - - showContextMenu(payload) { - if (!payload || !payload.node) { - return; - } - const { node, event } = payload; - console.log('context menu', node.path, node.type); - if (event && typeof event.preventDefault === 'function') { - event.preventDefault(); - } - if (event && typeof event.stopPropagation === 'function') { - event.stopPropagation(); - } - if (!node.path && node.path !== '') { - this.hideContextMenu(); - return; - } - if (node.type !== 'file' && node.type !== 'folder') { - this.hideContextMenu(); - return; - } - - this.hideContextMenu(); - - let x = (event && event.clientX) || 0; - let y = (event && event.clientY) || 0; - const menuWidth = 200; - const menuHeight = 50; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - if (x + menuWidth > viewportWidth) { - x = viewportWidth - menuWidth - 8; - } - if (y + menuHeight > viewportHeight) { - y = viewportHeight - menuHeight - 8; - } - - this.contextMenu.visible = true; - this.contextMenu.x = Math.max(8, x); - this.contextMenu.y = Math.max(8, y); - this.contextMenu.node = node; - }, - - hideContextMenu() { - if (!this.contextMenu.visible) { - return; - } - this.contextMenu.visible = false; - this.contextMenu.node = null; - }, - - async downloadFile(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/file?path=${encodeURIComponent(path)}`; - const name = path.split('/').pop() || 'file'; - await this.downloadResource(url, name); - }, - - async downloadFolder(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/folder?path=${encodeURIComponent(path)}`; - const segments = path.split('/').filter(Boolean); - const folderName = segments.length ? segments.pop() : 'folder'; - await this.downloadResource(url, `${folderName}.zip`); - }, - - async downloadResource(url, filename) { - try { - const response = await fetch(url); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - message = errorData.error || errorData.message || message; - } catch (err) { - message = await response.text(); - } - alert(`下载失败: ${message}`); - return; - } - - const blob = await response.blob(); - const downloadName = filename || 'download'; - const link = document.createElement('a'); - const href = URL.createObjectURL(blob); - link.href = href; - link.download = downloadName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(href); - } catch (error) { - console.error('下载失败:', error); - alert(`下载失败: ${error.message || error}`); - } finally { - this.hideContextMenu(); - } - }, - - initScrollListener() { - const messagesArea = this.$refs.messagesArea; - if (!messagesArea) { - console.warn('消息区域未找到'); - return; - } - - let isProgrammaticScroll = false; - const bottomThreshold = 12; - - this._setScrollingFlag = (flag) => { - isProgrammaticScroll = !!flag; - }; - - messagesArea.addEventListener('scroll', () => { - if (isProgrammaticScroll) { - return; - } - - const scrollTop = messagesArea.scrollTop; - const scrollHeight = messagesArea.scrollHeight; - const clientHeight = messagesArea.clientHeight; - const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; - - if (isAtBottom) { - this.userScrolling = false; - this.autoScrollEnabled = true; - } else { - this.userScrolling = true; - this.autoScrollEnabled = false; - } - }); - }, - - initSocket() { - try { - console.log('初始化WebSocket连接...'); - - const usePollingOnly = window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1'; - - this.socket = io('/', usePollingOnly ? { - transports: ['polling'], - upgrade: false - } : { - transports: ['websocket', 'polling'] - }); - - // 连接事件 - this.socket.on('connect', () => { - this.isConnected = true; - console.log('WebSocket已连接'); - // 连接时重置所有状态 - this.resetAllStates(); - }); - - this.socket.on('disconnect', () => { - this.isConnected = false; - console.log('WebSocket已断开'); - // 断线时也重置状态,防止状态混乱 - this.resetAllStates(); - }); - - this.socket.on('connect_error', (error) => { - console.error('WebSocket连接错误:', error.message); - }); - - // ========================================== - // Token统计WebSocket事件处理(修复版) - // ========================================== - - this.socket.on('token_update', (data) => { - console.log('收到token更新事件:', data); - - // 只处理当前对话的token更新 - if (data.conversation_id === this.currentConversationId) { - // 更新累计统计(使用后端提供的准确字段名) - this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; - - console.log(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); - - // 同时更新当前上下文Token(关键修复) - this.updateCurrentContextTokens(); - - this.$forceUpdate(); - } - }); - - this.socket.on('todo_updated', (data) => { - console.log('收到todo更新事件:', data); - if (data && data.conversation_id) { - this.currentConversationId = data.conversation_id; - } - this.todoList = data && data.todo_list ? data.todo_list : null; - }); - - // 系统就绪 - this.socket.on('system_ready', (data) => { - this.projectPath = data.project_path || ''; - this.thinkingMode = data.thinking_mode || '未知'; - console.log('系统就绪:', data); - - // 系统就绪后立即加载对话列表 - this.loadConversationsList(); - }); - - this.socket.on('tool_settings_updated', (data) => { - console.log('收到工具设置更新:', data); - if (data && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } - }); - - // ========================================== - // 对话管理相关Socket事件 - // ========================================== - - // 监听对话变更事件 - this.socket.on('conversation_changed', (data) => { - console.log('对话已切换:', data); - this.currentConversationId = data.conversation_id; - this.currentConversationTitle = data.title || ''; - - if (data.cleared) { - // 对话被清空 - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - // 重置Token统计 - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.loadConversationsList(); - this.fetchTodoList(); - }); - - this.socket.on('conversation_resolved', (data) => { - if (!data || !data.conversation_id) { - return; - } - const convId = data.conversation_id; - this.currentConversationId = convId; - if (data.title) { - this.currentConversationTitle = data.title; - } - const pathFragment = this.stripConversationPrefix(convId); - const currentPath = window.location.pathname.replace(/^\/+/, ''); - if (data.created) { - history.pushState({ conversationId: convId }, '', `/${pathFragment}`); - } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); - } - }); - - // 监听对话加载事件 - this.socket.on('conversation_loaded', (data) => { - console.log('对话已加载:', data); - if (data.clear_ui) { - // 清理当前UI状态,准备显示历史内容 - this.resetAllStates(); - } - - // 延迟获取并显示历史对话内容 - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 延迟获取Token统计(累计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - this.fetchTodoList(); - }, 500); - }); - - // 监听对话列表更新事件 - this.socket.on('conversation_list_update', (data) => { - console.log('对话列表已更新:', data); - // 刷新对话列表 - this.loadConversationsList(); - }); - - // 监听状态更新事件 - this.socket.on('status_update', (status) => { - // 更新系统状态信息 - if (status.conversation && status.conversation.current_id) { - this.currentConversationId = status.conversation.current_id; - } - }); - - // AI消息开始 - this.socket.on('ai_message_start', () => { - console.log('AI消息开始'); - this.cleanupStaleToolActions(); - const newMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(newMessage); - this.currentMessageIndex = this.messages.length - 1; - this.streamingMessage = true; - this.stopRequested = false; - this.autoScrollEnabled = true; - this.scrollToBottom(); - }); - - // 思考流开始 - this.socket.on('thinking_start', () => { - console.log('思考开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking = ''; - msg.currentStreamingType = 'thinking'; - - const action = { - id: Date.now() + Math.random(), - type: 'thinking', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - - const blockId = `${this.currentMessageIndex}-thinking-${msg.actions.length - 1}`; - this.expandedBlocks.add(blockId); - this.$forceUpdate(); - } - }); - - // 思考内容块 - this.socket.on('thinking_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 思考结束 - this.socket.on('thinking_end', (data) => { - console.log('思考结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.streaming = false; - lastAction.content = data.full_content; - } - msg.streamingThinking = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 文本流开始 - this.socket.on('text_start', () => { - console.log('文本开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText = ''; - msg.currentStreamingType = 'text'; - - const action = { - id: Date.now() + Math.random(), - type: 'text', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - } - }); - - // 文本内容块 - this.socket.on('text_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'text') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - - // 实时渲染LaTeX - this.renderLatexInRealtime(); - } - }); - - // 文本结束 - this.socket.on('text_end', (data) => { - console.log('文本结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找当前流式文本的action - for (let i = msg.actions.length - 1; i >= 0; i--) { - const action = msg.actions[i]; - if (action.type === 'text' && action.streaming) { - action.streaming = false; - action.content = data.full_content; - console.log('文本action已更新为完成状态'); - break; - } - } - - msg.streamingText = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 工具提示事件(可选) - this.socket.on('tool_hint', (data) => { - console.log('工具提示:', data.name); - // 可以在这里添加提示UI - }); - - // 工具准备中事件 - 实时显示 - this.socket.on('tool_preparing', (data) => { - console.log('工具准备中:', data.name); - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - const action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'preparing', - result: null, - message: data.message || `准备调用 ${data.name}...` - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.preparingTools.set(data.id, action); - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 工具状态更新事件 - 实时显示详细状态 - this.socket.on('tool_status', (data) => { - console.log('工具状态:', data); - const target = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (target) { - target.tool.statusDetail = data.detail; - target.tool.statusType = data.status; - this.$forceUpdate(); - return; - } - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找对应的工具action并更新状态 - for (let action of msg.actions) { - if (action.type === 'tool' && action.tool.name === data.tool) { - action.tool.statusDetail = data.detail; - action.tool.statusType = data.status; - this.$forceUpdate(); - break; - } - } - } - }); - - // 工具开始(从准备转为执行) - this.socket.on('tool_start', (data) => { - console.log('工具开始执行:', data.name); - let action = null; - if (data.preparing_id && this.preparingTools.has(data.preparing_id)) { - action = this.preparingTools.get(data.preparing_id); - this.preparingTools.delete(data.preparing_id); - } else { - action = this.findToolAction(data.id, data.preparing_id, data.execution_id); - } - if (!action) { - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'running', - result: null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - } - action.tool.status = 'running'; - action.tool.arguments = data.arguments; - action.tool.message = null; - action.tool.id = data.id; - action.tool.executionId = data.id; - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 更新action(工具完成) - this.socket.on('update_action', (data) => { - console.log('更新action:', data.id, 'status:', data.status); - let targetAction = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) { - targetAction = this.preparingTools.get(data.preparing_id); - } - if (!targetAction) { - outer: for (const message of this.messages) { - if (!message.actions) continue; - for (const action of message.actions) { - if (action.type !== 'tool') continue; - const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; - const matchByToolId = action.tool.id === data.id; - const matchByPreparingId = action.id === data.preparing_id; - if (matchByExecution || matchByToolId || matchByPreparingId) { - targetAction = action; - break outer; - } - } - } - } - if (targetAction) { - if (data.status) { - targetAction.tool.status = data.status; - } - if (data.result !== undefined) { - targetAction.tool.result = data.result; - } - if (data.message !== undefined) { - targetAction.tool.message = data.message; - } - if (data.awaiting_content) { - targetAction.tool.awaiting_content = true; - } else if (data.status === 'completed') { - targetAction.tool.awaiting_content = false; - } - if (!targetAction.tool.executionId && (data.execution_id || data.id)) { - targetAction.tool.executionId = data.execution_id || data.id; - } - this.registerToolAction(targetAction, data.execution_id || data.id); - if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { - this.unregisterToolAction(targetAction); - if (data.id) { - this.preparingTools.delete(data.id); - } - if (data.preparing_id) { - this.preparingTools.delete(data.preparing_id); - } - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - - // 关键修复:每个工具完成后都更新当前上下文Token - if (data.status === 'completed') { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - this.socket.on('append_payload', (data) => { - console.log('收到append_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: data.path || '未知文件', - forced: !!data.forced, - success: data.success === undefined ? true : !!data.success, - lines: data.lines ?? null, - bytes: data.bytes ?? null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - this.socket.on('modify_payload', (data) => { - console.log('收到modify_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: data.path || '未知文件', - total: data.total ?? null, - completed: data.completed || [], - failed: data.failed || [], - forced: !!data.forced - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 停止请求确认 - this.socket.on('stop_requested', (data) => { - console.log('停止请求已接收:', data.message); - // 可以显示提示信息 - }); - - // 任务停止 - this.socket.on('task_stopped', (data) => { - console.log('任务已停止:', data.message); - this.resetAllStates(); - }); - - // 任务完成(重点:更新Token统计) - this.socket.on('task_complete', (data) => { - console.log('任务完成', data); - this.resetAllStates(); - - // 任务完成后立即更新Token统计(关键修复) - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - this.fetchConversationTokenStatistics(); - } - }); - - // 聚焦文件更新 - this.socket.on('focused_files_update', (data) => { - this.focusedFiles = data || {}; - // 聚焦文件变化时更新当前上下文Token(关键修复) - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 文件树更新 - this.socket.on('file_tree_update', (data) => { - this.updateFileTree(data); - // 文件树变化时也可能影响上下文 - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 系统消息 - this.socket.on('system_message', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `system-${Date.now()}-${Math.random()}`, - type: 'system', - content: data.content, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } else { - this.addSystemMessage(data.content); - } - }); - - // 错误处理 - this.socket.on('error', (data) => { - this.addSystemMessage(`错误: ${data.message}`); - // 仅标记当前流结束,避免状态错乱 - this.streamingMessage = false; - this.stopRequested = false; - }); - - // 命令结果 - this.socket.on('command_result', (data) => { - if (data.command === 'clear' && data.success) { - this.messages = []; - this.currentMessageIndex = -1; - this.expandedBlocks.clear(); - // 清除对话时重置Token统计 - this.resetTokenStatistics(); - } else if (data.command === 'status' && data.success) { - this.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); - } else if (!data.success) { - this.addSystemMessage(`命令失败: ${data.message}`); - } - }); - - } catch (error) { - console.error('Socket初始化失败:', error); - } - }, - - registerToolAction(action, executionId = null) { - if (!action || action.type !== 'tool') { - return; - } - const keys = new Set(); - if (action.id) { - keys.add(action.id); - } - if (action.tool && action.tool.id) { - keys.add(action.tool.id); - } - if (executionId) { - keys.add(executionId); - } - if (action.tool && action.tool.executionId) { - keys.add(action.tool.executionId); - } - keys.forEach(key => { - if (!key) { - return; - } - this.toolActionIndex.set(key, action); - }); - }, - - unregisterToolAction(action) { - if (!action || action.type !== 'tool') { - return; - } - const keysToRemove = []; - for (const [key, stored] of this.toolActionIndex.entries()) { - if (stored === action) { - keysToRemove.push(key); - } - } - keysToRemove.forEach(key => this.toolActionIndex.delete(key)); - }, - - findToolAction(id, preparingId, executionId) { - if (!this.toolActionIndex) { - return null; - } - const candidates = [executionId, id, preparingId]; - for (const key of candidates) { - if (key && this.toolActionIndex.has(key)) { - return this.toolActionIndex.get(key); - } - } - return null; - }, - - cleanupStaleToolActions() { - this.messages.forEach(msg => { - if (!msg.actions) { - return; - } - msg.actions.forEach(action => { - if (action.type !== 'tool' || !action.tool) { - return; - } - if (['running', 'preparing'].includes(action.tool.status)) { - action.tool.status = 'stale'; - action.tool.message = action.tool.message || '已被新的响应中断'; - this.unregisterToolAction(action); - } - }); - }); - this.preparingTools.clear(); - this.toolActionIndex.clear(); - }, - - ensureAssistantMessage() { - if (this.currentMessageIndex >= 0) { - return this.messages[this.currentMessageIndex]; - } - const message = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(message); - this.currentMessageIndex = this.messages.length - 1; - return message; - }, - - // 完整重置所有状态 - resetAllStates() { - console.log('重置所有前端状态'); - this.hideContextMenu(); - - // 重置消息和流状态 - this.streamingMessage = false; - this.currentMessageIndex = -1; - this.stopRequested = false; - - // 清理工具状态 - this.preparingTools.clear(); - this.activeTools.clear(); - this.toolActionIndex.clear(); - - // ✨ 新增:将所有未完成的工具标记为已完成 - this.messages.forEach(msg => { - if (msg.role === 'assistant' && msg.actions) { - msg.actions.forEach(action => { - if (action.type === 'tool' && - (action.tool.status === 'preparing' || action.tool.status === 'running')) { - action.tool.status = 'completed'; - } - }); - } - }); - - // 重置滚动状态 - this.userScrolling = false; - this.autoScrollEnabled = true; - - // 清理Markdown缓存 - if (this.markdownCache) { - this.markdownCache.clear(); - } - - // 强制更新视图 - this.$forceUpdate(); - - this.settingsOpen = false; - this.toolMenuOpen = false; - this.toolSettingsLoading = false; - this.toolSettings = []; - - console.log('前端状态重置完成'); - }, - - // 重置Token统计 - resetTokenStatistics() { - this.currentContextTokens = 0; - this.currentConversationTokens = { - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - }; - }, - - async loadInitialData() { - try { - console.log('加载初始数据...'); - - const filesResponse = await fetch('/api/files'); - const filesData = await filesResponse.json(); - this.updateFileTree(filesData); - - const focusedResponse = await fetch('/api/focused'); - const focusedData = await focusedResponse.json(); - this.focusedFiles = focusedData || {}; - - await this.fetchTodoList(); - - const statusResponse = await fetch('/api/status'); - const statusData = await statusResponse.json(); - this.projectPath = statusData.project_path || ''; - this.thinkingMode = statusData.thinking_mode || '未知'; - - // 获取当前对话信息 - const statusConversationId = statusData.conversation && statusData.conversation.current_id; - if (statusConversationId) { - if (!this.currentConversationId) { - this.currentConversationId = statusConversationId; - } - // 如果有当前对话,尝试获取标题和Token统计 - try { - const convResponse = await fetch(`/api/conversations/current`); - const convData = await convResponse.json(); - if (convData.success && convData.data) { - this.currentConversationTitle = convData.data.title; - } - await this.fetchAndDisplayHistory(); - // 获取当前对话的Token统计 - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - } catch (e) { - console.warn('获取当前对话标题失败:', e); - } - } - - await this.loadToolSettings(true); - - console.log('初始数据加载完成'); - } catch (error) { - console.error('加载初始数据失败:', error); - } - }, - - async refreshFileTree() { - try { - const response = await fetch('/api/files'); - const data = await response.json(); - this.updateFileTree(data); - } catch (error) { - console.error('刷新文件树失败:', error); - } - }, - - // ========================================== - // Token统计相关方法(完全修复版) - // ========================================== - - async updateCurrentContextTokens() { - // 获取当前上下文Token数(动态计算,包含完整prompt构建) - if (!this.currentConversationId) { - this.currentContextTokens = 0; - return; - } - - try { - console.log(`正在更新当前上下文Token: ${this.currentConversationId}`); - - // 关键修复:使用正确的动态API,包含文件结构+记忆+聚焦文件+终端内容+工具定义 - const response = await fetch(`/api/conversations/${this.currentConversationId}/tokens`); - const data = await response.json(); - - if (data.success && data.data) { - this.currentContextTokens = data.data.total_tokens || 0; - console.log(`当前上下文Token更新: ${this.currentContextTokens}`); - this.$forceUpdate(); - } else { - console.warn('获取当前上下文Token失败:', data.error); - this.currentContextTokens = 0; - } - } catch (error) { - console.warn('获取当前上下文Token异常:', error); - this.currentContextTokens = 0; - } - }, - - async fetchConversationTokenStatistics() { - // 获取对话累计Token统计(加载对话时、任务完成后调用) - if (!this.currentConversationId) { - this.resetTokenStatistics(); - return; - } - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/token-statistics`); - const data = await response.json(); - - if (data.success && data.data) { - // 更新累计统计 - this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; - - console.log(`累计Token统计: 输入=${data.data.total_input_tokens}, 输出=${data.data.total_output_tokens}, 总计=${data.data.total_tokens}`); - this.$forceUpdate(); - } else { - console.warn('获取Token统计失败:', data.error); - // 保持当前统计,不重置 - } - } catch (error) { - console.warn('获取Token统计异常:', error); - // 保持当前统计,不重置 - } - }, - - // Token面板折叠/展开切换 - toggleTokenPanel() { - this.tokenPanelCollapsed = !this.tokenPanelCollapsed; - }, - - // ========================================== - // 对话管理核心功能 - // ========================================== - - async loadConversationsList() { - this.conversationsLoading = true; - try { - const response = await fetch(`/api/conversations?limit=${this.conversationsLimit}&offset=${this.conversationsOffset}`); - const data = await response.json(); - - if (data.success) { - if (this.conversationsOffset === 0) { - this.conversations = data.data.conversations; - } else { - this.conversations.push(...data.data.conversations); - } - this.hasMoreConversations = data.data.has_more; - console.log(`已加载 ${this.conversations.length} 个对话`); - - if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { - const latestConversation = this.conversations[0]; - if (latestConversation && latestConversation.id) { - await this.loadConversation(latestConversation.id); - } - } - } else { - console.error('加载对话列表失败:', data.error); - } - } catch (error) { - console.error('加载对话列表异常:', error); - } finally { - this.conversationsLoading = false; - } - }, - - async loadMoreConversations() { - if (this.loadingMoreConversations || !this.hasMoreConversations) return; - - this.loadingMoreConversations = true; - this.conversationsOffset += this.conversationsLimit; - await this.loadConversationsList(); - this.loadingMoreConversations = false; - }, - - async loadConversation(conversationId) { - console.log('加载对话:', conversationId); - - if (conversationId === this.currentConversationId) { - console.log('已是当前对话,跳过加载'); - return; - } - - try { - // 1. 调用加载API - const response = await fetch(`/api/conversations/${conversationId}/load`, { - method: 'PUT' - }); - const result = await response.json(); - - if (result.success) { - console.log('对话加载API成功:', result); - - // 2. 更新当前对话信息 - this.currentConversationId = conversationId; - this.currentConversationTitle = result.title; - history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`); - - // 3. 重置UI状态 - this.resetAllStates(); - - // 4. 延迟获取并显示历史对话内容(关键功能) - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - }, 500); - - } else { - console.error('对话加载失败:', result.message); - alert(`加载对话失败: ${result.message}`); - } - } catch (error) { - console.error('加载对话异常:', error); - alert(`加载对话异常: ${error.message}`); - } - }, - - // ========================================== - // 关键功能:获取并显示历史对话内容 - // ========================================== - async fetchAndDisplayHistory() { - console.log('开始获取历史对话内容...'); - - if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { - console.log('没有当前对话ID,跳过历史加载'); - return; - } - - try { - // 使用专门的API获取对话消息历史 - const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); - - if (!messagesResponse.ok) { - console.warn('无法获取消息历史,尝试备用方法'); - // 备用方案:通过状态API获取 - const statusResponse = await fetch('/api/status'); - const status = await statusResponse.json(); - console.log('系统状态:', status); - - // 如果状态中有对话历史字段 - if (status.conversation_history && Array.isArray(status.conversation_history)) { - this.renderHistoryMessages(status.conversation_history); - return; - } - - console.log('备用方案也无法获取历史消息'); - return; - } - - const messagesData = await messagesResponse.json(); - console.log('获取到消息数据:', messagesData); - - if (messagesData.success && messagesData.data && messagesData.data.messages) { - const messages = messagesData.data.messages; - console.log(`发现 ${messages.length} 条历史消息`); - - if (messages.length > 0) { - // 清空当前显示的消息 - this.messages = []; - - // 渲染历史消息 - 这是关键功能 - this.renderHistoryMessages(messages); - - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - - console.log('历史对话内容显示完成'); - } else { - console.log('对话存在但没有历史消息'); - this.messages = []; - } - } else { - console.log('消息数据格式不正确:', messagesData); - this.messages = []; - } - - } catch (error) { - console.error('获取历史对话失败:', error); - console.log('尝试不显示错误弹窗,仅在控制台记录'); - // 不显示alert,避免打断用户体验 - this.messages = []; - } - }, - - // ========================================== - // 关键功能:渲染历史消息 - // ========================================== - renderHistoryMessages(historyMessages) { - console.log('开始渲染历史消息...', historyMessages); - console.log('历史消息数量:', historyMessages.length); - - if (!Array.isArray(historyMessages)) { - console.error('历史消息不是数组格式'); - return; - } - - let currentAssistantMessage = null; - - historyMessages.forEach((message, index) => { - console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); - - if (message.role === 'user') { - // 用户消息 - 先结束之前的assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - this.messages.push({ - role: 'user', - content: message.content || '' - }); - console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); - - } else if (message.role === 'assistant') { - // AI消息 - 如果没有当前assistant消息,创建一个 - if (!currentAssistantMessage) { - currentAssistantMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - } - - // 处理思考内容 - 支持多种格式 - const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; - - let allThinkingContent = ''; - for (const pattern of thinkPatterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - allThinkingContent += match[1].trim() + '\n'; - } - } - - if (allThinkingContent) { - currentAssistantMessage.actions.push({ - id: `history-think-${Date.now()}-${Math.random()}`, - type: 'thinking', - content: allThinkingContent.trim(), - streaming: false, - timestamp: Date.now() - }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); - } - - // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - - let textContent = content - .replace(/[\s\S]*?<\/think>/g, '') - .replace(/[\s\S]*?<\/thinking>/g, '') - .trim(); - - if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: appendPayloadMeta.path || '未知文件', - forced: !!appendPayloadMeta.forced, - success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, - lines: appendPayloadMeta.lines ?? null, - bytes: appendPayloadMeta.bytes ?? null - }, - timestamp: Date.now() - }); - console.log('添加append占位信息:', appendPayloadMeta.path); - } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: modifyPayloadMeta.path || '未知文件', - total: modifyPayloadMeta.total_blocks ?? null, - completed: modifyPayloadMeta.completed || [], - failed: modifyPayloadMeta.failed || [], - forced: !!modifyPayloadMeta.forced, - details: modifyPayloadMeta.details || [] - }, - timestamp: Date.now() - }); - console.log('添加modify占位信息:', modifyPayloadMeta.path); - } - - if (textContent && !appendPayloadMeta && !modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-text-${Date.now()}-${Math.random()}`, - type: 'text', - content: textContent, - streaming: false, - timestamp: Date.now() - }); - console.log('添加文本内容:', textContent.substring(0, 50) + '...'); - } - - // 处理工具调用 - if (message.tool_calls && Array.isArray(message.tool_calls)) { - message.tool_calls.forEach((toolCall, tcIndex) => { - let arguments_obj = {}; - try { - arguments_obj = typeof toolCall.function.arguments === 'string' - ? JSON.parse(toolCall.function.arguments || '{}') - : (toolCall.function.arguments || {}); - } catch (e) { - console.warn('解析工具参数失败:', e); - arguments_obj = {}; - } - - currentAssistantMessage.actions.push({ - id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, - type: 'tool', - tool: { - id: toolCall.id, - name: toolCall.function.name, - arguments: arguments_obj, - status: 'preparing', - result: null - }, - timestamp: Date.now() - }); - console.log('添加工具调用:', toolCall.function.name); - }); - } - - } else if (message.role === 'tool') { - // 工具结果 - 更新当前assistant消息中对应的工具 - if (currentAssistantMessage) { - // 查找对应的工具action - 使用更灵活的匹配 - let toolAction = null; - - // 优先按tool_call_id匹配 - if (message.tool_call_id) { - toolAction = currentAssistantMessage.actions.find(action => - action.type === 'tool' && - action.tool.id === message.tool_call_id - ); - } - - // 如果找不到,按name匹配最后一个同名工具 - if (!toolAction && message.name) { - const sameNameTools = currentAssistantMessage.actions.filter(action => - action.type === 'tool' && - action.tool.name === message.name - ); - toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 - } - - if (toolAction) { - // 解析工具结果 - let result; - try { - // 尝试解析为JSON - result = JSON.parse(message.content); - } catch (e) { - // 如果不是JSON,就作为纯文本 - result = { - output: message.content, - success: true - }; - } - - toolAction.tool.status = 'completed'; - toolAction.tool.result = result; - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } - console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - if (message.name === 'append_to_file' && result && typeof result === 'object') { - const appendSummary = { - path: result.path || '未知文件', - success: result.success !== false, - summary: result.message || (result.success === false ? '追加失败' : '追加完成'), - lines: result.lines || 0, - bytes: result.bytes || 0, - forced: !!result.forced - }; - currentAssistantMessage.actions.push({ - id: `history-append-${Date.now()}-${Math.random()}`, - type: 'append', - append: appendSummary, - timestamp: Date.now() - }); - } - } else { - console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); - } - } - - } else { - // 其他类型消息(如system)- 先结束当前assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - console.log('处理其他类型消息:', message.role); - this.messages.push({ - role: message.role, - content: message.content || '' - }); - } - }); - - // 处理最后一个assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - } - - console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); - - // 强制更新视图 - this.$forceUpdate(); - - // 确保滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - }, - - async createNewConversation() { - console.log('创建新对话...'); - - try { - const response = await fetch('/api/conversations', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thinking_mode: this.thinkingMode !== '快速模式' - }) - }); - - const result = await response.json(); - - if (result.success) { - console.log('新对话创建成功:', result.conversation_id); - - // 清空当前消息 - this.messages = []; - this.currentConversationId = result.conversation_id; - this.currentConversationTitle = '新对话'; - history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); - - // 重置Token统计 - this.resetTokenStatistics(); - - // 重置状态 - this.resetAllStates(); - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('创建对话失败:', result.message); - alert(`创建对话失败: ${result.message}`); - } - } catch (error) { - console.error('创建对话异常:', error); - alert(`创建对话异常: ${error.message}`); - } - }, - - async deleteConversation(conversationId) { - if (!confirm('确定要删除这个对话吗?删除后无法恢复。')) { - return; - } - - console.log('删除对话:', conversationId); - - try { - const response = await fetch(`/api/conversations/${conversationId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - console.log('对话删除成功'); - - // 如果删除的是当前对话,清空界面 - if (conversationId === this.currentConversationId) { - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - this.resetAllStates(); - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('删除对话失败:', result.message); - alert(`删除对话失败: ${result.message}`); - } - } catch (error) { - console.error('删除对话异常:', error); - alert(`删除对话异常: ${error.message}`); - } - }, - - async duplicateConversation(conversationId) { - console.log('复制对话:', conversationId); - try { - const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.duplicate_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - - this.conversationsOffset = 0; - await this.loadConversationsList(); - } else { - const message = result.message || result.error || '复制失败'; - alert(`复制失败: ${message}`); - } - } catch (error) { - console.error('复制对话异常:', error); - alert(`复制对话异常: ${error.message}`); - } - }, - - searchConversations() { - // 简单的搜索功能,实际实现可以调用搜索API - if (this.searchTimer) { - clearTimeout(this.searchTimer); - } - - this.searchTimer = setTimeout(() => { - if (this.searchQuery.trim()) { - console.log('搜索对话:', this.searchQuery); - // TODO: 实现搜索API调用 - // this.searchConversationsAPI(this.searchQuery); - } else { - // 清空搜索,重新加载全部对话 - this.conversationsOffset = 0; - this.loadConversationsList(); - } - }, 300); - }, - - toggleSidebar() { - this.sidebarCollapsed = !this.sidebarCollapsed; - }, - - formatTime(timeString) { - if (!timeString) return ''; - - const date = new Date(timeString); - const now = new Date(); - const diffMs = now - date; - const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - const diffDays = Math.floor(diffHours / 24); - - if (diffHours < 1) { - return '刚刚'; - } else if (diffHours < 24) { - return `${diffHours}小时前`; - } else if (diffDays < 7) { - return `${diffDays}天前`; - } else { - return date.toLocaleDateString('zh-CN', { - month: 'short', - day: 'numeric' - }); - } - }, - - // ========================================== - // 原有功能保持不变 - // ========================================== - - updateFileTree(structure) { - const treeDictionary = structure && structure.tree ? structure.tree : {}; - - const buildNodes = (treeMap) => { - if (!treeMap) { - return []; - } - - const entries = Object.keys(treeMap).map((name) => { - const node = treeMap[name] || {}; - if (node.type === 'folder') { - return { - type: 'folder', - name, - path: node.path || name, - children: buildNodes(node.children) - }; - } - return { - type: 'file', - name, - path: node.path || name, - annotation: node.annotation || '' - }; - }); - - entries.sort((a, b) => { - if (a.type !== b.type) { - return a.type === 'folder' ? -1 : 1; - } - return a.name.localeCompare(b.name, 'zh-CN'); - }); - - return entries; - }; - - const nodes = buildNodes(treeDictionary); - - const expanded = { ...this.expandedFolders }; - const validFolderPaths = new Set(); - - const ensureExpansion = (list, depth = 0) => { - list.forEach((item) => { - if (item.type === 'folder') { - validFolderPaths.add(item.path); - if (expanded[item.path] === undefined) { - expanded[item.path] = false; - } - ensureExpansion(item.children || [], depth + 1); - } - }); - }; - - ensureExpansion(nodes); - - Object.keys(expanded).forEach((path) => { - if (!validFolderPaths.has(path)) { - delete expanded[path]; - } - }); - - this.expandedFolders = expanded; - this.fileTree = nodes; - }, - - toggleFolder(path) { - this.hideContextMenu(); - if (!path) { - return; - } - const current = !!this.expandedFolders[path]; - this.expandedFolders = { - ...this.expandedFolders, - [path]: !current - }; - }, - - toggleTodoPanel() { - this.showTodoList = !this.showTodoList; - }, - - formatTaskStatus(task) { - if (!task) { - return ''; - } - return task.status === 'done' - ? `${this.todoDoneEmoji} 完成` - : `${this.todoPendingEmoji} 未完成`; - }, - - toolCategoryEmoji(categoryId) { - return this.toolCategoryEmojis[categoryId] || '⚙️'; - }, - - async fetchTodoList() { - try { - const response = await fetch('/api/todo-list'); - const data = await response.json(); - if (data.success) { - this.todoList = data.data || null; - } - } catch (error) { - console.error('获取待办列表失败:', error); - } - }, - - triggerFileUpload() { - if (this.uploading) { - return; - } - const input = this.$refs.fileUploadInput; - if (input) { - input.click(); - } - }, - - handleFileSelected(event) { - const fileInput = event?.target; - if (!fileInput || !fileInput.files || fileInput.files.length === 0) { - return; - } - const [file] = fileInput.files; - this.uploadSelectedFile(file); - }, - - resetFileInput() { - const input = this.$refs.fileUploadInput; - if (input) { - input.value = ''; - } - }, - - async uploadSelectedFile(file) { - if (!file || this.uploading) { - this.resetFileInput(); - return; - } - - this.uploading = true; - - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/upload', { - method: 'POST', - body: formData - }); - - let result = {}; - try { - result = await response.json(); - } catch (parseError) { - throw new Error('服务器响应无法解析'); - } - - if (!response.ok || !result.success) { - const message = result.error || result.message || '上传失败'; - throw new Error(message); - } - - await this.refreshFileTree(); - alert(`上传成功:${result.path || file.name}`); - } catch (error) { - console.error('文件上传失败:', error); - alert(`文件上传失败:${error.message}`); - } finally { - this.uploading = false; - this.resetFileInput(); - } - }, - - handleSendOrStop() { - if (this.streamingMessage) { - this.stopTask(); - } else { - this.sendMessage(); - } - }, - - sendMessage() { - if (this.streamingMessage || !this.isConnected) { - return; - } - - if (!this.inputMessage.trim()) { - return; - } - - const message = this.inputMessage; - - if (message.startsWith('/')) { - this.socket.emit('send_command', { command: message }); - this.inputMessage = ''; - this.settingsOpen = false; - return; - } - - this.messages.push({ - role: 'user', - content: message - }); - - this.currentMessageIndex = -1; - this.socket.emit('send_message', { message: message, conversation_id: this.currentConversationId }); - this.inputMessage = ''; - this.autoScrollEnabled = true; - this.scrollToBottom(); - this.settingsOpen = false; - - // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) - setTimeout(() => { - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - } - }, 1000); - }, - - // 新增:停止任务方法 - stopTask() { - if (this.streamingMessage && !this.stopRequested) { - this.socket.emit('stop_task'); - this.stopRequested = true; - console.log('发送停止请求'); - } - this.settingsOpen = false; - }, - - clearChat() { - if (confirm('确定要清除所有对话记录吗?')) { - this.socket.emit('send_command', { command: '/clear' }); - } - this.settingsOpen = false; - }, - - async compressConversation() { - if (!this.currentConversationId) { - alert('当前没有可压缩的对话。'); - return; - } - - if (this.compressing) { - return; - } - - const confirmed = confirm('确定要压缩当前对话记录吗?压缩后会生成新的对话副本。'); - if (!confirmed) { - return; - } - - this.settingsOpen = false; - this.compressing = true; - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.compressed_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - console.log('对话压缩完成:', result); - } else { - const message = result.message || result.error || '压缩失败'; - alert(`压缩失败: ${message}`); - } - } catch (error) { - console.error('压缩对话异常:', error); - alert(`压缩对话异常: ${error.message}`); - } finally { - this.compressing = false; - } - }, - - toggleToolMenu() { - if (!this.isConnected) { - return; - } - const nextState = !this.toolMenuOpen; - this.toolMenuOpen = nextState; - if (nextState) { - this.settingsOpen = false; - this.loadToolSettings(); - } - }, - - handleClickOutsideToolMenu(event) { - if (!this.toolMenuOpen) { - return; - } - const dropdown = this.$refs.toolDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.toolMenuOpen = false; - } - }, - - applyToolSettingsSnapshot(categories) { - if (!Array.isArray(categories)) { - return; - } - this.toolSettings = categories.map((item) => ({ - id: item.id, - label: item.label || item.id, - enabled: !!item.enabled, - tools: Array.isArray(item.tools) ? item.tools : [] - })); - this.toolSettingsLoading = false; - }, - - async loadToolSettings(force = false) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - if (!force && this.toolSettings.length > 0) { - return; - } - this.toolSettingsLoading = true; - try { - const response = await fetch('/api/tool-settings'); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('获取工具设置失败:', data); - this.toolSettingsLoading = false; - } - } catch (error) { - console.error('获取工具设置异常:', error); - this.toolSettingsLoading = false; - } - }, - - async updateToolCategory(categoryId, enabled) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - const previousSnapshot = this.toolSettings.map((item) => ({ ...item })); - this.toolSettings = this.toolSettings.map((item) => { - if (item.id === categoryId) { - return { ...item, enabled }; - } - return item; - }); - try { - const response = await fetch('/api/tool-settings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - category: categoryId, - enabled - }) - }); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('更新工具设置失败:', data); - this.toolSettings = previousSnapshot; - } - } catch (error) { - console.error('更新工具设置异常:', error); - this.toolSettings = previousSnapshot; - } - this.toolSettingsLoading = false; - }, - - toggleSettings() { - if (!this.isConnected) { - return; - } - this.settingsOpen = !this.settingsOpen; - if (this.settingsOpen) { - this.toolMenuOpen = false; - } - }, - - toggleFocusPanel() { - this.rightCollapsed = !this.rightCollapsed; - if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - this.settingsOpen = false; - }, - - handleClickOutsideSettings(event) { - if (!this.settingsOpen) { - return; - } - const dropdown = this.$refs.settingsDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.settingsOpen = false; - } - }, - - addSystemMessage(content) { - this.messages.push({ - role: 'system', - content: content - }); - this.conditionalScrollToBottom(); - }, - - toggleBlock(id) { - if (this.expandedBlocks.has(id)) { - this.expandedBlocks.delete(id); - } else { - this.expandedBlocks.add(id); - } - this.$forceUpdate(); - }, - - // 修复:工具相关方法 - 接收tool对象而不是name - getToolIcon(tool) { - const toolName = typeof tool === 'string' ? tool : tool.name; - const icons = { - 'create_file': '📄', - 'sleep': '⏱️', - 'read_file': '📖', - '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': '❗' - }; - return icons[toolName] || '⚙️'; - }, - - getToolAnimationClass(tool) { - // 根据工具状态返回不同的动画类 - if (tool.status === 'hinted') { - return 'hint-animation pulse-slow'; - } else if (tool.status === 'preparing') { - return 'preparing-animation'; - } else if (tool.status === 'running') { - const animations = { - 'create_file': 'file-animation', - 'read_file': 'read-animation', - 'delete_file': 'file-animation', - 'rename_file': 'file-animation', - 'modify_file': 'file-animation', - 'append_to_file': 'file-animation', - 'create_folder': 'file-animation', - 'focus_file': 'focus-animation', - 'unfocus_file': 'focus-animation', - 'web_search': 'search-animation', - 'extract_webpage': 'search-animation', - 'save_webpage': 'file-animation', - 'run_python': 'code-animation', - 'run_command': 'terminal-animation', - 'update_memory': 'memory-animation', - 'sleep': 'wait-animation', - 'terminal_session': 'terminal-animation', - 'terminal_input': 'terminal-animation', - 'terminal_snapshot': 'terminal-animation', - 'terminal_reset': 'terminal-animation', - 'todo_create': 'file-animation', - 'todo_update_task': 'file-animation', - 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' - }; - return animations[tool.name] || 'default-animation'; - } - return ''; - }, - - // 修复:获取工具状态文本 - getToolStatusText(tool) { - // 优先使用自定义消息 - if (tool.message) { - return tool.message; - } - - if (tool.status === 'hinted') { - return `可能需要 ${tool.name}...`; - } else if (tool.status === 'preparing') { - return `准备调用 ${tool.name}...`; - } else if (tool.status === 'running') { - const texts = { - 'create_file': '正在创建文件...', - 'read_file': '正在读取文件...', - 'sleep': '正在等待...', - 'delete_file': '正在删除文件...', - 'rename_file': '正在重命名文件...', - 'modify_file': '正在修改文件...', - 'append_to_file': '正在追加文件...', - 'create_folder': '正在创建文件夹...', - 'focus_file': '正在聚焦文件...', - 'unfocus_file': '正在取消聚焦...', - 'web_search': '正在搜索网络...', - 'extract_webpage': '正在提取网页...', - 'save_webpage': '正在保存网页...', - 'run_python': '正在执行Python代码...', - 'run_command': '正在执行命令...', - 'update_memory': '正在更新记忆...', - 'terminal_session': '正在管理终端会话...', - 'terminal_input': '正在发送终端输入...', - 'terminal_snapshot': '正在获取终端快照...', - 'terminal_reset': '正在重置终端...' - }; - return texts[tool.name] || '正在执行...'; - } else if (tool.status === 'completed') { - // 修复:完成状态的文本 - const texts = { - 'create_file': '文件创建成功', - 'read_file': '文件读取完成', - 'delete_file': '文件删除成功', - 'sleep': '等待完成', - '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': '终端已重置' - }; - return texts[tool.name] || '执行完成'; - } else { - // 其他状态 - return `${tool.name} - ${tool.status}`; - } - }, - - getToolDescription(tool) { - // 如果有状态详情,优先显示 - if (tool.statusDetail) { - return tool.statusDetail; - } - - if (tool.result && typeof tool.result === 'object') { - if (tool.result.path) { - return tool.result.path.split('/').pop(); - } - } - - if (tool.arguments) { - if (tool.arguments.path) { - return tool.arguments.path.split('/').pop(); - } - if (tool.arguments.target_path) { - return tool.arguments.target_path.split('/').pop(); - } - if (tool.arguments.query) { - return `"${tool.arguments.query}"`; - } - if (tool.arguments.command) { - return tool.arguments.command; - } - if (tool.arguments.seconds) { - return `${tool.arguments.seconds} 秒`; - } - } - return ''; - }, - - formatSearchTopic(filters) { - const mapping = { - 'general': '通用', - 'news': '新闻', - 'finance': '金融' - }; - const topic = (filters && filters.topic) ? String(filters.topic).toLowerCase() : 'general'; - return mapping[topic] || '通用'; - }, - - formatSearchTime(filters) { - if (!filters) { - return '未限定时间'; - } - if (filters.time_range) { - const mapping = { - 'day': '过去24小时', - 'week': '过去7天', - 'month': '过去30天', - 'year': '过去365天' - }; - return mapping[filters.time_range] || `相对范围:${filters.time_range}`; - } - if (typeof filters.days === 'number') { - return `过去${filters.days}天`; - } - if (filters.start_date && filters.end_date) { - return `${filters.start_date} 至 ${filters.end_date}`; - } - return '未限定时间'; - }, - - renderMarkdown(text, isStreaming = false) { - if (!text) return ''; - - if (typeof marked === 'undefined') { - return text; - } - - marked.setOptions({ - breaks: true, - gfm: true, - sanitize: false - }); - - if (!isStreaming) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - - const cacheKey = `${text.length}_${text.substring(0, 100)}`; - - if (this.markdownCache.has(cacheKey)) { - return this.markdownCache.get(cacheKey); - } - } - - let html = marked.parse(text); - html = this.wrapCodeBlocks(html, isStreaming); - - if (!isStreaming && text.length < 10000) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - this.markdownCache.set(`${text.length}_${text.substring(0, 100)}`, html); - if (this.markdownCache.size > 20) { - const firstKey = this.markdownCache.keys().next().value; - this.markdownCache.delete(firstKey); - } - } - - // 只在非流式状态处理(流式状态由renderLatexInRealtime处理) - if (!isStreaming) { - setTimeout(() => { - // 代码高亮 - if (typeof Prism !== 'undefined') { - const codeBlocks = document.querySelectorAll('.code-block-wrapper pre code:not([data-highlighted])'); - codeBlocks.forEach(block => { - try { - Prism.highlightElement(block); - block.setAttribute('data-highlighted', 'true'); - } catch (e) { - console.warn('代码高亮失败:', e); - } - }); - } - - // LaTeX最终渲染 - if (typeof renderMathInElement !== 'undefined') { - const elements = document.querySelectorAll('.text-output .text-content:not(.streaming-text)'); - elements.forEach(element => { - if (element.hasAttribute('data-math-rendered')) return; - - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - element.setAttribute('data-math-rendered', 'true'); - } catch (e) { - console.warn('LaTeX渲染失败:', e); - } - }); - } - }, 100); - } - - return html; - }, - // 实时LaTeX渲染(用于流式输出) - renderLatexInRealtime() { - if (typeof renderMathInElement === 'undefined') { - return; - } - - // 使用requestAnimationFrame优化性能 - if (this._latexRenderTimer) { - cancelAnimationFrame(this._latexRenderTimer); - } - - this._latexRenderTimer = requestAnimationFrame(() => { - const elements = document.querySelectorAll('.text-output .streaming-text'); - elements.forEach(element => { - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - } catch (e) { - // 忽略错误,继续渲染 - } - }); - }); - }, - // 用字符串替换包装代码块 - // 用字符串替换包装代码块 - 添加streaming参数 - wrapCodeBlocks(html, isStreaming = false) { - // 如果是流式输出,不包装代码块,保持原样 - if (isStreaming) { - return html; - } - - let counter = 0; - - // 匹配
...
- return html.replace(/
]*)>([\s\S]*?)<\/code><\/pre>/g, (match, attributes, content) => {
-                    // 提取语言
-                    const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
-                    const language = langMatch ? langMatch[1] : 'text';
-                    
-                    // 生成唯一ID
-                    const blockId = `code-${Date.now()}-${counter++}`;
-                    
-                    // 转义引号用于data属性
-                    const escapedContent = content
-                        .replace(/&/g, '&')
-                        .replace(//g, '>')
-                        .replace(/"/g, '"');
-                    
-                    // 构建新的HTML,保持code元素原样
-                    return `
-            
-
- ${language} - -
-
${content}
-
`; - }); - }, - - getLanguageClass(path) { - const ext = path.split('.').pop().toLowerCase(); - const langMap = { - 'py': 'language-python', - 'js': 'language-javascript', - 'html': 'language-html', - 'css': 'language-css', - 'json': 'language-json', - 'md': 'language-markdown', - 'txt': 'language-plain' - }; - return langMap[ext] || 'language-plain'; - }, - - scrollToBottom() { - setTimeout(() => { - const messagesArea = this.$refs.messagesArea; - if (messagesArea) { - // 标记为程序触发的滚动 - if (this._setScrollingFlag) { - this._setScrollingFlag(true); - } - - messagesArea.scrollTop = messagesArea.scrollHeight; - - // 滚动完成后重置标记 - setTimeout(() => { - if (this._setScrollingFlag) { - this._setScrollingFlag(false); - } - }, 100); - } - }, 50); - }, - - conditionalScrollToBottom() { - // 严格检查:只在明确允许时才滚动 - if (this.autoScrollEnabled === true && this.userScrolling === false) { - this.scrollToBottom(); - } - }, - - toggleScrollLock() { - const currentlyLocked = this.autoScrollEnabled && !this.userScrolling; - if (currentlyLocked) { - this.autoScrollEnabled = false; - this.userScrolling = true; - } else { - this.autoScrollEnabled = true; - this.userScrolling = false; - this.scrollToBottom(); - } - }, - - // 面板调整方法 - startResize(panel, event) { - this.isResizing = true; - this.resizingPanel = panel; - if (panel === 'right' && this.rightCollapsed) { - this.rightCollapsed = false; - if (this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - } - document.addEventListener('mousemove', this.handleResize); - document.addEventListener('mouseup', this.stopResize); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; - event.preventDefault(); - }, - - handleResize(event) { - if (!this.isResizing) return; - - const containerWidth = document.querySelector('.main-container').offsetWidth; - - if (this.resizingPanel === 'left') { - let newWidth = event.clientX - (this.sidebarCollapsed ? 60 : 300); - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.leftWidth = newWidth; - } else if (this.resizingPanel === 'right') { - let newWidth = containerWidth - event.clientX; - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.rightWidth = newWidth; - } else if (this.resizingPanel === 'conversation') { - // 对话侧边栏宽度调整 - let newWidth = event.clientX; - newWidth = Math.max(200, Math.min(newWidth, 400)); - // 这里可以动态调整对话侧边栏宽度,暂时不实现 - } - }, - - stopResize() { - this.isResizing = false; - this.resizingPanel = null; - document.removeEventListener('mousemove', this.handleResize); - document.removeEventListener('mouseup', this.stopResize); - document.body.style.userSelect = ''; - document.body.style.cursor = ''; - }, - - // 格式化token显示(修复NaN问题) - formatTokenCount(tokens) { - // 确保tokens是数字,防止NaN - const num = Number(tokens) || 0; - if (num < 1000) { - return num.toString(); - } else if (num < 1000000) { - return (num / 1000).toFixed(1) + 'K'; - } else { - return (num / 1000000).toFixed(1) + 'M'; - } - } - } - }); - - app.component('file-node', { - name: 'FileNode', - props: { - node: { - type: Object, - required: true - }, - level: { - type: Number, - default: 0 - }, - expandedFolders: { - type: Object, - required: true - } - }, - emits: ['toggle-folder', 'context-menu'], - computed: { - isExpanded() { - if (this.node.type !== 'folder') { - return false; - } - const value = this.expandedFolders[this.node.path]; - return value === undefined ? true : value; - }, - folderPadding() { - return { - paddingLeft: `${12 + this.level * 16}px` - }; - }, - filePadding() { - return { - paddingLeft: `${40 + this.level * 16}px` - }; - } - }, - methods: { - toggle() { - if (this.node.type === 'folder') { - this.$emit('toggle-folder', this.node.path); - } - } - }, - template: ` -
-
- -
- -
-
-
- 📄 - {{ node.name }} - {{ node.annotation }} -
-
- ` - }); - - app.mount('#app'); - console.log('Vue应用初始化完成'); - -}; diff --git a/static/backup_20251026_183122/claude-colors-simple.html b/static/backup_20251026_183122/claude-colors-simple.html deleted file mode 100644 index aebd82d..0000000 --- a/static/backup_20251026_183122/claude-colors-simple.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - Claude颜色展示 - 简化版 - - - -
-

Claude聊天界面颜色

- -

核心颜色

-
-
-
-
背景颜色
-
#eeece2
-
-
- -
-
-
-
字体颜色
-
#3d3929
-
-
- -
-
-
-
品牌主色
-
#da7756
-
-
- -
-
-
-
按钮颜色
-
#bd5d3a
-
-
- -

字体样式

-
- 字体栈:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif -

- 斜体样式展示 | 粗体样式展示 -
- -

界面元素

- - -

对话界面模拟

-
-
你好,Claude!今天天气怎么样?
-
你好!我无法获取实时天气信息,但你可以通过天气应用查看当地天气。
-
- -
- 这种配色方案创造出温暖、专业的对话环境,背景是米白色(#eeece2),文字是深棕色(#3d3929),给人舒适友好的感觉。 -
-
- - diff --git a/static/backup_20251026_183122/debug.html b/static/backup_20251026_183122/debug.html deleted file mode 100644 index 0a945c9..0000000 --- a/static/backup_20251026_183122/debug.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - AI Agent 调试监控 - - - - - -
-

🔧 AI Agent 调试监控

- - -
-

连接状态

-
Socket连接: {{ isConnected ? '已连接' : '未连接' }}
-
当前消息索引: {{ currentMessageIndex }}
-
消息总数: {{ messages.length }}
-
- - -
-

控制面板

- - - - -
- - -
-

WebSocket事件流 (最新 {{ events.length }} 条)

-
-
- {{ event.time }} - {{ event.type }} - : {{ JSON.stringify(event.data).slice(0, 200) }} -
-
-
- - -
-

当前消息Actions状态 (消息 #{{ currentMessageIndex }})

-
-
-
Action #{{ idx }}: {{ action.type }}
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
- 工具: {{ action.tool.name }}
- 状态: {{ action.tool.status }}
- ID: {{ action.tool.id }}
- 有结果: {{ !!action.tool.result }} -
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
-
-
- - -
-

最新消息原始数据

-
{{ JSON.stringify(messages[messages.length - 1], null, 2) }}
-
-
- - - - \ No newline at end of file diff --git a/static/backup_20251026_183122/index.html b/static/backup_20251026_183122/index.html deleted file mode 100644 index baa0ec5..0000000 --- a/static/backup_20251026_183122/index.html +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - AI Agent System - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-

正在连接服务器...

-

如果长时间无响应,请刷新页面

-
- - - -
- - -
-
- - - - - diff --git a/static/backup_20251026_183122/login.html b/static/backup_20251026_183122/login.html deleted file mode 100644 index cff0db6..0000000 --- a/static/backup_20251026_183122/login.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 登录 - AI Agent - - - - - - - diff --git a/static/backup_20251026_183122/register.html b/static/backup_20251026_183122/register.html deleted file mode 100644 index 0943626..0000000 --- a/static/backup_20251026_183122/register.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - 注册 - AI Agent - - - -
-

创建账号

-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - - diff --git a/static/backup_20251026_183122/style.css b/static/backup_20251026_183122/style.css deleted file mode 100644 index 5ba5ef8..0000000 --- a/static/backup_20251026_183122/style.css +++ /dev/null @@ -1,2021 +0,0 @@ -/* static/style-enhanced.css - 增强版,包含对话历史管理功能 */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --claude-bg: #eeece2; - --claude-panel: rgba(255, 255, 255, 0.82); - --claude-sidebar: rgba(255, 255, 255, 0.68); - --claude-border: rgba(118, 103, 84, 0.25); - --claude-text: #3d3929; - --claude-text-secondary: #7f7766; - --claude-muted: rgba(121, 109, 94, 0.4); - --claude-accent: #da7756; - --claude-accent-strong: #bd5d3a; - --claude-highlight: rgba(218, 119, 86, 0.14); - --claude-button-hover: #c76541; - --claude-button-active: #a95331; - --claude-shadow: 0 14px 36px rgba(61, 57, 41, 0.12); - --claude-success: #76b086; - --claude-warning: #d99845; -} - -body { - font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - background: var(--claude-bg); - color: var(--claude-text); - height: 100vh; - overflow: hidden; - -webkit-font-smoothing: antialiased; -} - -/* 顶部状态栏 */ -.header { - background: var(--claude-panel); - backdrop-filter: blur(24px); - border-bottom: 1px solid var(--claude-border); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 56px; - z-index: 100; - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.05); -} - -.header-left { - display: flex; - align-items: center; - gap: 20px; -} - -.logo { - font-size: 18px; - font-weight: 600; - color: var(--claude-text); - letter-spacing: 0.02em; -} - -.project-path { - color: var(--claude-text-secondary); - font-size: 14px; -} - -.header-right { - display: flex; - align-items: center; - gap: 20px; -} - -.thinking-mode { - background: var(--claude-accent); - color: #fff8f2; - padding: 5px 14px; - border-radius: 980px; - font-size: 13px; - font-weight: 500; - letter-spacing: 0.04em; -} - -.connection-status { - font-size: 13px; - color: var(--claude-text-secondary); - display: flex; - align-items: center; - gap: 6px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--claude-muted); - transition: all 0.3s ease; -} - -.status-dot.active { - background: var(--claude-success); - box-shadow: 0 0 8px rgba(118, 176, 134, 0.45); -} - -/* 主容器 */ -.main-container { - display: flex; - height: calc(100vh - 56px); - background: var(--claude-bg); - position: relative; - align-items: stretch; -} - -/* ========================================= */ -/* 新增:对话历史侧边栏样式 */ -/* ========================================= */ - -.conversation-sidebar { - width: 280px; - background: var(--claude-sidebar); - border-right: none; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 50; - backdrop-filter: blur(12px); - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; - border-bottom: 1px solid var(--claude-border); -} - -.conversation-collapsed-spacer { - flex: 1 1 auto; - background: transparent; -} - -.conversation-sidebar.collapsed { - width: 50px; - overflow: hidden; - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; -} - -.conversation-sidebar.collapsed .conversation-header { - justify-content: center; -} - -.conversation-sidebar.collapsed .conversation-header .toggle-sidebar-btn { - margin-left: 0; -} - -.conversation-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(118, 103, 84, 0.12); - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - position: sticky; - top: 0; - z-index: 60; - min-height: 68px; -} - -.new-conversation-btn { - flex: 1; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border: 1px solid rgba(189, 93, 58, 0.4); - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 6px; - box-shadow: 0 6px 14px rgba(189, 93, 58, 0.18); -} - -.new-conversation-btn:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); - transform: translateY(-1px); -} - -.btn-icon { - font-size: 16px; - font-weight: bold; -} - -.btn-text { - white-space: nowrap; -} - -.toggle-sidebar-btn { - background: rgba(255, 255, 255, 0.7); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.18); - padding: 6px 8px; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; - min-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.toggle-sidebar-btn:hover { - background: rgba(255, 255, 255, 0.9); -} - -.conversation-search { - padding: 12px; - background: transparent; - border-bottom: 1px solid var(--claude-border); -} - -.search-input { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--claude-border); - border-radius: 6px; - font-size: 13px; - background: rgba(255, 255, 255, 0.55); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.search-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.85); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.18); -} - -.conversation-list { - flex: 1; - overflow-y: auto; - padding: 8px 0; - background: transparent; -} - -.loading-conversations, -.no-conversations { - text-align: center; - color: var(--claude-text-secondary); - padding: 30px 15px; - font-size: 13px; -} - -.conversation-item { - padding: 12px 16px; - margin: 2px 8px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid transparent; - position: relative; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.04); -} - -.conversation-item:hover { - background: rgba(255, 255, 255, 0.85); - border-color: rgba(218, 119, 86, 0.35); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.conversation-item.active { - background: rgba(218, 119, 86, 0.18); - border-color: var(--claude-accent); - box-shadow: 0 10px 28px rgba(189, 93, 58, 0.18); -} - -.conversation-title { - font-size: 14px; - font-weight: 500; - color: var(--claude-text); - margin-bottom: 6px; - word-wrap: break-word; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.3; -} - -.conversation-meta { - font-size: 11px; - color: var(--claude-text-secondary); - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2px; -} - -.conversation-time { - flex-shrink: 0; -} - -.conversation-counts { - text-align: right; - white-space: nowrap; - font-size: 10px; -} - -.conversation-actions { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); - opacity: 0; - transition: opacity 0.2s ease; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; -} - -.conversation-item:hover .conversation-actions { - opacity: 1; -} - -.conversation-action-btn { - border: none; - border-radius: 4px; - font-size: 13px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - color: white; -} - -.conversation-action-btn.copy-btn { - background: var(--claude-accent); -} - -.conversation-action-btn.copy-btn:hover { - background: var(--claude-button-hover); - transform: translateY(-1px); -} - -.conversation-action-btn.delete-btn { - background: #d85a42; -} - -.conversation-action-btn.delete-btn:hover { - background: #bf422b; - transform: translateY(-1px); -} - -.load-more { - padding: 12px 16px; - text-align: center; -} - -.load-more-btn { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent); - border: 1px solid rgba(218, 119, 86, 0.35); - padding: 6px 14px; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; -} - -.load-more-btn:hover:not(:disabled) { - background: var(--claude-accent); - color: #fffdf8; -} - -.load-more-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* 当前对话信息栏 */ -.current-conversation-info { - background: var(--claude-panel); - backdrop-filter: blur(18px); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -.conversation-title-display { - font-weight: 500; - color: var(--claude-text); -} - -.conversation-message-count { - font-size: 12px; -} - -/* 拖拽手柄 */ -.resize-handle { - width: 4px; - background: var(--claude-sidebar); - cursor: col-resize; - position: relative; - transition: background 0.2s; - flex-shrink: 0; -} - -.resize-handle:hover { - background: rgba(218, 119, 86, 0.22); -} - -/* 侧边栏 */ -.sidebar { - background: rgba(255, 255, 255, 0.75); - overflow-y: auto; - flex-shrink: 0; - border-left: none; -} - -.sidebar-header { - padding: 23px; - border-bottom: 1px solid var(--claude-border); - position: sticky; - top: 0; - background: rgba(255, 255, 255, 0.85); - z-index: 10; - backdrop-filter: blur(16px); - display: flex; - align-items: center; - gap: 10px; -} - -.sidebar-header h3 { - font-size: 15px; - font-weight: 600; - color: var(--claude-text); - margin: 0; -} - -.sidebar-view-toggle { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(118, 103, 84, 0.3); - background: rgba(255, 255, 255, 0.85); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.2s ease; - color: var(--claude-text); -} - -.sidebar-view-toggle:hover { - background: rgba(255, 255, 255, 0.95); -} - -.sidebar.right-sidebar.collapsed { - width: 0 !important; - min-width: 0 !important; - border-left: none; - overflow: hidden; -} - -.sidebar.right-sidebar.collapsed .sidebar-header, -.sidebar.right-sidebar.collapsed .focused-files { - display: none; -} - -/* 文件树 */ -.file-tree { - padding: 12px 0 20px; - color: var(--claude-text); -} - -.todo-panel { - padding: 16px 20px 24px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.todo-empty { - font-size: 14px; - color: var(--claude-text-secondary); - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.8); - border: 1px dashed var(--claude-border); - text-align: center; -} - -.todo-task { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.18); - border-radius: 10px; - font-size: 13px; - color: var(--claude-text); - background: rgba(255, 255, 255, 0.9); -} - -.todo-task.done { - background: rgba(92, 190, 125, 0.08); - border-color: rgba(92, 190, 125, 0.3); -} - -.todo-task-title { - flex: 1; -} - -.todo-task-status { - font-weight: 600; - font-size: 12px; - margin-left: 12px; -} - -.todo-instruction { - font-size: 12px; - color: var(--claude-text-secondary); - margin-top: 8px; -} - -.file-node-wrapper { - font-size: 14px; - color: var(--claude-text); - font-family: inherit; -} - -.folder-header { - width: 100%; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s ease, transform 0.2s ease; - text-align: left; - font-family: inherit; -} - -.folder-header:hover { - background: rgba(218, 119, 86, 0.12); - transform: translateX(2px); -} - -.folder-arrow { - width: 12px; - text-align: center; - color: var(--claude-text-secondary); - font-size: 12px; -} - -.folder-icon, -.file-icon { - width: 18px; - text-align: center; -} - -.folder-name, -.file-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: inherit; -} - - -.folder-children { - margin-left: 14px; - padding-left: 6px; - border-left: 1px dashed rgba(0, 0, 0, 0.08); -} - -.folder-children .file-node-wrapper { - margin-left: 0; -} - -.file-node.file-leaf { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border-radius: 8px; - font-family: inherit; -} - -.file-node.file-leaf:hover { - background: rgba(218, 119, 86, 0.1); -} - -.file-node .annotation { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -.context-menu { - position: fixed; - background: #ffffff; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.15); - z-index: 3000; - min-width: 180px; - padding: 6px 0; - backdrop-filter: blur(8px); -} - -.context-menu button { - width: 100%; - padding: 8px 18px; - background: transparent; - border: none; - text-align: left; - font-size: 13px; - color: #1f2933; - cursor: pointer; - transition: background 0.15s ease; -} - -.context-menu button:hover { - background: rgba(59, 130, 246, 0.12); -} - -.context-menu button:disabled { - color: #9ca3af; - cursor: not-allowed; - background: transparent; -} - -/* 聊天容器 */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - background: rgba(255, 255, 255, 0.78); - min-width: 0; - position: relative; - backdrop-filter: blur(6px); -} - -/* 消息区域 */ -.messages-area { - flex: 1; - overflow-y: auto; - padding: 24px; -} - -.scroll-lock-toggle { - position: absolute; - right: 28px; - bottom: 200px; - z-index: 25; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.scroll-lock-btn { - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.92); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); -} - -.scroll-lock-btn:hover { - transform: translateY(-2px); - box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); -} - -.scroll-lock-toggle.locked .scroll-lock-btn { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); -} - -.scroll-lock-btn svg { - width: 18px; - height: 18px; - stroke: var(--claude-text); - stroke-width: 1.8; - fill: none; - transition: stroke 0.2s ease; -} - -.scroll-lock-toggle.locked .scroll-lock-btn svg { - stroke: var(--claude-accent); -} - -/* 滚动条 */ -.messages-area::-webkit-scrollbar, -.sidebar::-webkit-scrollbar, -.conversation-list::-webkit-scrollbar { - width: 8px; -} - -.messages-area::-webkit-scrollbar-track, -.sidebar::-webkit-scrollbar-track, -.conversation-list::-webkit-scrollbar-track { - background: transparent; -} - -.messages-area::-webkit-scrollbar-thumb, -.sidebar::-webkit-scrollbar-thumb, -.conversation-list::-webkit-scrollbar-thumb { - background: rgba(121, 109, 94, 0.4); - border-radius: 8px; -} - -/* 消息块 */ -.message-block { - margin-bottom: 24px; -} - -/* 用户消息 */ -.user-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 8px; - letter-spacing: 0.02em; -} - -.user-message .message-text { - background: rgba(255, 255, 255, 0.85); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - white-space: pre-wrap; -} - -/* AI消息 */ -.assistant-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 12px; - letter-spacing: 0.02em; -} - -.assistant-message .message-text { - background: rgba(218, 119, 86, 0.12); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - border-left: 4px solid var(--claude-accent); - white-space: pre-wrap; -} - -/* Action项入场动画 */ -@keyframes slideInFade { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes quickFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.action-item { - animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 0ms; -} - -/* 流式内容:立即显示 */ -.action-item.streaming-content, -.action-item.immediate-show { - animation: quickFadeIn 0.2s ease-out both; - animation-delay: 0ms !important; -} - -/* 已完成的工具:统一延迟看动画 */ -.action-item.completed-tool { - animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 100ms; -} - -/* 文本输出 - 无缩进,与思考块对齐 */ -.text-output { - margin: 16px 0; - color: var(--claude-text); - font-size: 15px; - line-height: 1.7; -} - -.text-output .text-content { - padding: 0 20px 0 15px; /* 左边56px与思考块对齐 */ -} - -.text-output .text-content p { - margin-bottom: 12px; -} - -.text-output .text-content p:last-child { - margin-bottom: 0; -} - -.append-block { - margin: 12px 0; - padding: 12px 16px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); - display: flex; - flex-direction: column; - gap: 8px; -} - -.append-block.append-error { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); - box-shadow: inset 0 0 0 1px rgba(216, 90, 66, 0.08); -} - -.append-header { - display: flex; - align-items: center; - gap: 10px; - font-weight: 600; - color: var(--claude-text); - font-size: 15px; -} - -.append-block.append-error .append-header { - color: #b0432a; -} - -.append-icon { - font-size: 18px; -} - -.append-summary { - flex: 1; -} - -.append-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-meta-item { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.append-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder { - margin: 12px 0; -} - -.modify-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.modify-placeholder-content .modify-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder-content .modify-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-placeholder { - margin: 12px 0; -} - - -.append-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.append-placeholder-content .append-warning { - margin-top: 4px; -} - -.append-placeholder.append-error .append-placeholder-content { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); -} -/* Markdown表格样式 */ -.text-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; -} - -.text-content table th, -.text-content table td { - border: 1px solid #e0e0e0; - padding: 10px 14px; - text-align: left; -} - -.text-content table th { - background: rgba(218, 119, 86, 0.08); - font-weight: 600; - color: var(--claude-text); -} - -.text-content table tr:hover { - background: #fafafa; -} - -/* 代码块包装器 - 防止跳动 */ -.code-block-wrapper { - border: 2px solid rgba(118, 103, 84, 0.25); - border-radius: 12px; - overflow: hidden; - margin: 16px 0; - background: rgba(255, 255, 255, 0.78); - min-height: 80px; /* 添加最小高度防止跳动 */ - contain: layout; /* CSS containment 优化渲染 */ -} - -/* 代码块头部 */ -.code-block-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(118, 103, 84, 0.25); -} - -.code-language { - color: var(--claude-text-secondary); - font-size: 13px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-weight: 500; -} - -.copy-code-btn { - background: transparent; - color: var(--claude-text-secondary); - border: 1px solid rgba(121, 109, 94, 0.35); - padding: 6px 10px; - border-radius: 6px; - font-size: 16px; - cursor: pointer; - transition: all 0.2s ease; - line-height: 1; -} - -.copy-code-btn:hover { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent-strong); -} - -.copy-code-btn.copied { - background: var(--claude-success); - border-color: var(--claude-success); - color: #f6fff8; -} - -/* 代码块内容区 */ -.code-block-wrapper pre { - background: #ffffff !important; - padding: 16px !important; - margin: 0 !important; - border-radius: 0 !important; - border: none !important; -} - -.code-block-wrapper pre code { - background: transparent !important; - padding: 0 !important; - color: #000000; -} -/* 流式文本 */ -.streaming-text { - display: block; -} - -.cursor-blink { - animation: blink 1s steps(1) infinite; - color: var(--claude-accent); - font-weight: normal; -} - -/* 思考内容样式 - 保留换行 */ -.thinking-content { - white-space: pre-wrap; /* 保留换行和空格 */ - word-wrap: break-word; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.6; - color: var(--claude-text-secondary); -} - -/* 可折叠块 */ -.collapsible-block { - background: rgba(255, 255, 255, 0.78); - border-radius: 12px; - margin-bottom: 12px; - overflow: hidden; - border: 1px solid var(--claude-border); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.collapsible-block:hover { - box-shadow: 0 14px 28px rgba(61, 57, 41, 0.1); -} - -.collapsible-header { - padding: 14px 20px; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - user-select: none; - background: rgba(255, 255, 255, 0.72); - transition: background-color 0.2s ease; - position: relative; -} - -.collapsible-header:hover { - background: rgba(218, 119, 86, 0.07); -} - -/* 箭头 */ -.arrow { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: var(--claude-text-secondary); -} - -.arrow::before { - content: '›'; - font-size: 18px; -} - -.collapsible-block.expanded .arrow { - transform: rotate(90deg); -} - -/* 状态图标 */ -.status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; -} - -/* 内容区域 */ -.collapsible-content { - max-height: 0; - overflow: hidden; - opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.collapsible-block.expanded .collapsible-content { - max-height: 600px; - overflow-y: auto; - opacity: 1; -} - -.content-inner { - padding: 20px 20px 20px 56px; - color: var(--claude-text-secondary); - font-size: 14px; - line-height: 1.6; -} - -/* 工具动画 */ -@keyframes brain-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.15); } -} - -.thinking-icon { - animation: brain-pulse 1.5s ease-in-out infinite; -} - -@keyframes file-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -.file-animation { - animation: file-bounce 1.5s ease-in-out infinite; -} - -@keyframes scan-effect { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -.read-animation { - animation: scan-effect 1.5s ease-in-out infinite; -} - -@keyframes search-rotate { - 0% { transform: rotate(-10deg); } - 50% { transform: rotate(10deg); } - 100% { transform: rotate(-10deg); } -} - -.search-animation { - animation: search-rotate 1s ease-in-out infinite; -} - -@keyframes code-breathe { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -.code-animation { - animation: code-breathe 2s ease-in-out infinite; -} - -.terminal-animation::after { - content: '_'; - animation: blink 1s steps(1) infinite; - margin-left: 2px; -} - -@keyframes memory-fade { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.95); } -} - -.memory-animation { - animation: memory-fade 2s ease-in-out infinite; -} - -@keyframes focus-glow { - 0%, 100% { filter: brightness(1); } - 50% { filter: brightness(1.3); } -} - -.focus-animation { - animation: focus-glow 1.5s ease-in-out infinite; -} - -/* 进度条 */ -.progress-indicator { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--claude-accent); - animation: progress 2s ease-in-out infinite; - opacity: 0; - transition: opacity 0.3s ease; -} - -.collapsible-block.processing .progress-indicator { - opacity: 1; -} - -@keyframes progress { - 0% { width: 0%; left: 0%; } - 50% { width: 40%; left: 30%; } - 100% { width: 0%; left: 100%; } -} - -/* 状态文字 */ -.status-text { - font-size: 14px; - color: var(--claude-text); - font-weight: 500; -} - -.processing .status-text { - color: var(--claude-text-secondary); -} - -/* 完成勾号 */ -.checkmark { - color: var(--claude-success); - font-weight: 600; - font-size: 16px; -} - -/* 工具描述 */ -.tool-desc { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -/* 输入区域 */ -.input-area { - background: rgba(255, 255, 255, 0.82); - border-top: 1px solid var(--claude-border); - padding: 20px; - backdrop-filter: blur(12px); -} - -.input-wrapper { - display: flex; - flex-direction: column; - gap: 12px; -} - -.message-input { - width: 100%; - padding: 14px 16px; - border: 1px solid var(--claude-border); - border-radius: 12px; - font-size: 15px; - resize: none; - font-family: inherit; - background: rgba(255, 255, 255, 0.75); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.message-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.2); -} - -.input-actions { - display: flex; - gap: 8px; - justify-content: flex-end; -} - -/* 按钮 */ -.btn { - padding: 10px 24px; - border: none; - border-radius: 980px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - letter-spacing: 0.03em; -} - -.send-btn { - background: var(--claude-accent); - color: #fffdf8; - box-shadow: 0 12px 28px rgba(189, 93, 58, 0.25); -} - -.send-btn:hover:not(:disabled) { - background: var(--claude-button-hover); - transform: scale(1.02); - box-shadow: 0 14px 32px rgba(189, 93, 58, 0.3); -} - -.send-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.stop-btn { - background: #d85a42; - color: #fffaf5; -} - -.stop-btn:hover { - background: #bf422b; -} - -.settings-dropdown { - position: relative; - display: flex; - align-items: center; -} - -.tool-dropdown { - position: relative; - display: flex; - align-items: center; - margin-right: auto; -} - -.upload-control { - display: flex; - align-items: center; -} - -.file-input-hidden { - display: none; -} - -.upload-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.upload-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.upload-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.tool-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.tool-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.tool-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.settings-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.settings-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-menu { - position: absolute; - right: 0; - bottom: calc(100% + 12px); - background: rgba(255, 255, 255, 0.96); - border: 1px solid var(--claude-border); - border-radius: 12px; - box-shadow: var(--claude-shadow); - 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: rgba(255, 255, 255, 0.96) 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: 320px; - max-width: 380px; - padding: 14px 18px; -} - -.settings-menu.tool-menu::before { - left: 32px; - right: auto; -} - -.tool-menu .tool-menu-status, -.tool-menu .tool-menu-empty { - font-size: 13px; - color: rgba(61, 57, 41, 0.78); - text-align: left; -} - -.tool-menu .tool-menu-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.tool-menu .tool-category-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 10px; - background: rgba(255, 255, 255, 0.88); - font-size: 13px; -} - -.tool-menu .tool-category-item.disabled { - opacity: 0.55; -} - -.tool-menu .tool-category-label { - flex: 1; - white-space: nowrap; - font-size: 13px; - font-weight: 500; - color: var(--claude-text); - display: inline-flex; - align-items: center; - gap: 6px; -} - -.tool-category-icon { - font-size: 16px; -} - -.tool-menu .tool-category-toggle { - width: auto !important; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 18px; - text-align: center; - white-space: nowrap; -} - -.menu-btn { - width: 100%; - padding: 8px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 8px; - font-size: 13px; - font-weight: 500; - text-align: left; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - cursor: pointer; - transition: all 0.2s ease; -} - -.menu-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); - transform: translateY(-1px); -} - -.menu-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.menu-btn.compress-entry { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-success); -} - -.menu-btn.compress-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.menu-btn.clear-entry { - background: rgba(255, 255, 255, 0.78); - color: #bf422b; -} - -.menu-btn.clear-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.settings-menu-enter-active, -.settings-menu-leave-active { - transition: opacity 0.18s ease, transform 0.18s ease; -} - -.settings-menu-enter-from, -.settings-menu-leave-to { - opacity: 0; - transform: translateY(6px); -} - -/* 聚焦文件 */ -.focused-files { - padding: 16px; -} - -.no-files { - text-align: center; - color: var(--claude-text-secondary); - padding: 60px 20px; - font-size: 14px; -} - -.file-tabs { - display: flex; - flex-direction: column; - gap: 12px; -} - -.file-tab { - border: 1px solid var(--claude-border); - border-radius: 12px; - overflow: hidden; - background: rgba(255, 255, 255, 0.75); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.tab-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-bottom: 1px solid var(--claude-border); -} - -.file-name { - font-weight: 500; - color: var(--claude-text); -} - -.file-size { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.file-content { - max-height: 320px; - overflow-y: auto; - background: #1e1e1e; -} - -.file-content pre { - margin: 0; - padding: 16px; -} - -.file-content code { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - color: #aed581; -} - -/* 系统消息 */ -.system-message { - text-align: center; - color: var(--claude-text-secondary); - font-size: 13px; - margin: 20px 0; - padding: 10px; - background: rgba(255, 255, 255, 0.7); - border-radius: 12px; - border: 1px solid var(--claude-border); -} - -/* Markdown样式 */ -.text-content h1, -.text-content h2, -.text-content h3 { - margin-top: 20px; - margin-bottom: 12px; - font-weight: 600; - color: var(--claude-text); -} - -.text-content p { - margin-bottom: 12px; -} - -.text-content pre { - background: rgba(255, 255, 255, 0.78); - padding: 16px; - border-radius: 12px; - overflow-x: auto; - margin: 16px 0; - border: 1px solid var(--claude-border); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.text-content code { - background: rgba(218, 119, 86, 0.1); - padding: 2px 6px; - border-radius: 4px; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--claude-accent-strong); -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} - - -/* ========================================= */ -/* 响应式设计 */ -/* ========================================= */ - -@media (max-width: 1200px) { - .conversation-sidebar { - width: 260px; - } - - .left-sidebar, - .right-sidebar { - width: 300px !important; - } -} - -@media (max-width: 768px) { - .conversation-sidebar { - position: absolute; - left: 0; - top: 0; - height: 100%; - z-index: 1000; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); - transform: translateX(-100%); - } - - .conversation-sidebar:not(.collapsed) { - transform: translateX(0); - } - - .left-sidebar, - .right-sidebar { - display: none; - } - - .resize-handle { - display: none; - } -} -.conversation-stats { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--claude-text-secondary); -} - -.token-count { - color: var(--claude-accent); - font-weight: 500; -} -/* ========================================= */ -/* Token 统计面板样式(无缝一体版)*/ -/* ========================================= */ - -/* Token区域包装器 */ -.token-wrapper { - position: relative; - z-index: 5; - margin-bottom: 0; -} - -/* 当前对话信息栏 - 移除底部边框 */ -.current-conversation-info { - position: relative; - z-index: 10; - background: var(--claude-panel); - backdrop-filter: blur(18px); - border-bottom: none; /* 移除边框,让它和下面的面板融为一体 */ - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - border-radius: 0; /* 顶部保持直角 */ - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -/* Token面板 - 与标题栏完全一体,底部圆角 */ -.token-display-panel { - background: var(--claude-panel); - backdrop-filter: blur(18px); - border: none; - border-radius: 0 0 16px 16px; - box-shadow: 0 8px 18px rgba(189, 93, 58, 0.12); - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); - width: 100%; - margin: 0; - padding: 0; -} - -/* 展开状态 */ -.token-display-panel:not(.collapsed) { - height: 80px; - opacity: 1; -} - -/* 收起状态 */ -.token-display-panel.collapsed { - height: 0; - opacity: 0; - border: none; - box-shadow: none; -} - -.token-panel-content { - padding: 16px 24px; - height: 100%; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.token-display-panel.collapsed .token-panel-content { - opacity: 0; - pointer-events: none; -} - -.token-stats { - display: flex; - gap: 32px; - align-items: center; - justify-content: center; - font-size: 13px; - height: 100%; -} - -.token-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; - min-width: 80px; -} - -.token-label { - color: var(--claude-text-secondary); - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.token-value { - color: var(--claude-text); - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; -} - -.token-value.current { - color: var(--claude-accent); - font-size: 20px; -} -.token-value.input { color: var(--claude-success); } -.token-value.output { color: var(--claude-warning); } - -.token-separator { - width: 1px; - height: 35px; - background: linear-gradient(to bottom, - transparent, - rgba(218, 119, 86, 0.25) 20%, - rgba(218, 119, 86, 0.25) 80%, - transparent - ); - margin: 0 8px; -} - -/* 切换按钮 - 独立定位 */ -.token-toggle-btn { - position: absolute; - right: 24px; - bottom: -18px; /* 相对于wrapper底部 */ - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(218, 119, 86, 0.3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 15; - font-size: 14px; - font-weight: bold; -} - -/* 展开状态 */ -.token-toggle-btn:not(.collapsed) { - background: linear-gradient(135deg, #ffffff 0%, rgba(255, 248, 242, 0.9) 100%); - color: var(--claude-accent); - box-shadow: 0 3px 10px rgba(189, 93, 58, 0.18); -} - -/* 收起状态 - 在标题栏下方露出一半 */ -.token-toggle-btn.collapsed { - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border-color: rgba(255, 248, 242, 0.55); - box-shadow: 0 3px 11px rgba(189, 93, 58, 0.22); -} - -.token-toggle-btn:hover { - transform: scale(1.05); - box-shadow: 0 5px 16px rgba(189, 93, 58, 0.26); -} - -.token-toggle-btn.collapsed:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); -} - -.token-toggle-btn:active { - transform: scale(1.02); -} - -/* 箭头样式 - 移除浮动动画 */ -.token-toggle-btn span { - transition: all 0.3s ease; - display: inline-block; -} - -/* 移除动画效果 */ -/* .token-toggle-btn:not(.collapsed) span { - animation: arrowBounceUp 2s ease-in-out infinite; -} - -.token-toggle-btn.collapsed span { - animation: arrowBounceDown 2s ease-in-out infinite; -} */ - -/* 保留动画定义,但不使用 */ -@keyframes arrowBounceUp { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -@keyframes arrowBounceDown { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(3px); } -} - -/* 响应式调整 */ -@media (max-width: 768px) { - .token-stats { - gap: 16px; - } - - .token-item { - min-width: 60px; - } - - .token-value { - font-size: 15px; - } - - .token-value.current { - font-size: 17px; - } - - .token-label { - font-size: 10px; - } - - .token-toggle-btn { - width: 32px; - height: 32px; - font-size: 12px; - right: 16px; - } -} -/* Markdown列表样式 - 修复偏左问题 */ -.text-content ul, -.text-content ol { - margin-left: 24px; /* 增加左边距 */ - padding-left: 0; - margin-bottom: 12px; -} - -.text-content ul { - list-style-type: disc; /* 实心圆点 */ -} - -.text-content ul ul { - list-style-type: circle; /* 空心圆点 */ - margin-top: 6px; -} - -.text-content ol { - list-style-type: decimal; /* 数字列表 */ -} - -.text-content li { - margin-bottom: 6px; - line-height: 1.6; -} - -/* 搜索结果展示 */ -.search-meta { - font-size: 14px; - color: var(--claude-text-secondary); - line-height: 1.6; - margin-bottom: 12px; - word-break: break-word; -} - -.search-result-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.search-result-item { - padding: 10px 12px; - border: 1px solid rgba(118, 103, 84, 0.16); - border-radius: 8px; - background: rgba(255, 255, 255, 0.65); - word-break: break-word; -} - -.search-result-title { - font-weight: 600; - margin-bottom: 6px; - color: var(--claude-text); -} - -.search-result-url a { - color: var(--claude-accent-strong); - text-decoration: none; - word-break: break-all; -} - -.search-result-url a:hover { - text-decoration: underline; -} - -.search-empty { - font-size: 13px; - color: var(--claude-text-secondary); - font-style: italic; -} diff --git a/static/backup_20251026_183122/terminal.html b/static/backup_20251026_183122/terminal.html deleted file mode 100644 index f0a31f0..0000000 --- a/static/backup_20251026_183122/terminal.html +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - AI Terminal Monitor - 实时终端查看器 - - - - - - - - -
-

- 🖥️ AI Terminal Monitor - - 连接中... -

-
- - -
-
- - 等待终端会话... -
-
- - -
- -
-
-
- 无活动会话 - - -
-
-
-
-
-
-
-
-
- - - -
- - -
-
-
- - 延迟: 0ms -
-
- 📡 - 0 KB/s -
-
- 🕐 - -
-
-
- AI Agent Terminal Monitor v1.0 -
-
- - -
- - - - - - - - - - diff --git a/static/backup_20251026_184346/app.js b/static/backup_20251026_184346/app.js deleted file mode 100644 index 528554e..0000000 --- a/static/backup_20251026_184346/app.js +++ /dev/null @@ -1,2854 +0,0 @@ -// static/app-enhanced.js - 修复版,正确实现Token实时更新 - -const SOCKET_IO_CDN_SOURCES = [ - 'https://cdn.socket.io/4.7.5/socket.io.min.js', - 'https://cdn.jsdelivr.net/npm/socket.io-client@4.7.5/dist/socket.io.min.js', - 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js' -]; - -function injectScriptSequentially(urls, onSuccess, onFailure) { - let index = 0; - const tryLoad = () => { - if (index >= urls.length) { - onFailure(); - return; - } - const url = urls[index]; - const script = document.createElement('script'); - script.src = url; - script.async = false; - script.onload = () => { - if (typeof io !== 'undefined') { - console.log(`Socket.IO 已从 ${url} 加载`); - onSuccess(); - } else { - index += 1; - tryLoad(); - } - }; - script.onerror = () => { - console.warn(`无法从 ${url} 加载 Socket.IO,尝试下一个源`); - index += 1; - tryLoad(); - }; - document.head.appendChild(script); - }; - tryLoad(); -} - -async function ensureSocketIOLoaded() { - if (typeof io !== 'undefined') { - return true; - } - return await new Promise((resolve) => { - injectScriptSequentially( - SOCKET_IO_CDN_SOURCES, - () => resolve(true), - () => resolve(false) - ); - }); -} - -async function bootstrapApp() { - // 检查必要的库是否加载 - if (typeof Vue === 'undefined') { - console.error('错误:Vue.js 未加载'); - document.body.innerHTML = '

Vue.js 加载失败,请刷新页面

'; - return; - } - - if (typeof io === 'undefined') { - const loaded = await ensureSocketIOLoaded(); - if (!loaded || typeof io === 'undefined') { - console.error('错误:Socket.IO 未加载'); - document.body.innerHTML = '

Socket.IO 加载失败,请检查网络后刷新页面

'; - return; - } - } - - console.log('所有依赖加载成功,初始化Vue应用...'); - - const { createApp } = Vue; - - const app = createApp({ - data() { - return { - // 连接状态 - isConnected: false, - socket: null, - - // 系统信息 - projectPath: '', - thinkingMode: '未知', - - // 消息相关 - messages: [], - inputMessage: '', - - // 当前消息索引 - currentMessageIndex: -1, - streamingMessage: false, - - // 停止功能状态 - stopRequested: false, - - // 路由相关 - initialRouteResolved: false, - - // 文件相关 - fileTree: [], - focusedFiles: {}, - expandedFolders: {}, - - // 展开状态管理 - expandedBlocks: new Set(), - - // 滚动控制 - userScrolling: false, - autoScrollEnabled: true, - - // 面板宽度控制 - leftWidth: 280, - rightWidth: 420, - rightCollapsed: true, - isResizing: false, - resizingPanel: null, - minPanelWidth: 200, - maxPanelWidth: 600, - - // 工具状态跟踪 - preparingTools: new Map(), - activeTools: new Map(), - toolActionIndex: new Map(), - toolActionIndex: new Map(), - - // ========================================== - // 对话管理相关状态 - // ========================================== - - // 对话历史侧边栏 - sidebarCollapsed: true, // 默认收起对话侧边栏 - showTodoList: false, - conversations: [], - conversationsLoading: false, - hasMoreConversations: false, - loadingMoreConversations: false, - currentConversationId: null, - currentConversationTitle: '当前对话', - - // 搜索功能 - searchQuery: '', - searchTimer: null, - - // 分页 - conversationsOffset: 0, - conversationsLimit: 20, - - // ========================================== - // Token统计相关状态(修复版) - // ========================================== - - // 当前上下文Token(动态计算,包含完整prompt) - currentContextTokens: 0, - - // 累计Token统计(从对话文件和WebSocket获取) - currentConversationTokens: { - // 累计统计字段 - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - - }, - // Token面板折叠状态 - tokenPanelCollapsed: false, - - // 对话压缩状态 - compressing: false, - - // 设置菜单状态 - settingsOpen: false, - - // 工具控制菜单 - toolMenuOpen: false, - toolSettings: [], - toolSettingsLoading: false, - - // 文件上传状态 - uploading: false, - - // TODO 列表 - todoList: null, - todoEmoji: '🗒️', - fileEmoji: '📁', - todoDoneEmoji: '☑️', - todoPendingEmoji: '⬜️', - toolCategoryEmojis: { - network: '🌐', - file_edit: '📝', - read_focus: '🔍', - terminal_realtime: '🖥️', - terminal_command: '⌨️', - memory: '🧠', - todo: '🗒️' - }, - - // 右键菜单相关 - contextMenu: { - visible: false, - x: 0, - y: 0, - node: null - }, - onDocumentClick: null, - onWindowScroll: null, - onKeydownListener: null - } - }, - - async mounted() { - console.log('Vue应用已挂载'); - await this.bootstrapRoute(); - this.initSocket(); - this.initScrollListener(); - - // 延迟加载初始数据 - setTimeout(() => { - this.loadInitialData(); - }, 500); - - document.addEventListener('click', this.handleClickOutsideSettings); - document.addEventListener('click', this.handleClickOutsideToolMenu); - window.addEventListener('popstate', this.handlePopState); - - this.onDocumentClick = (event) => { - if (!this.contextMenu.visible) { - return; - } - if (event.target && event.target.closest && event.target.closest('.context-menu')) { - return; - } - this.hideContextMenu(); - }; - - this.onWindowScroll = () => { - if (this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - this.onKeydownListener = (event) => { - if (event.key === 'Escape' && this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - document.addEventListener('click', this.onDocumentClick); - window.addEventListener('scroll', this.onWindowScroll, true); - document.addEventListener('keydown', this.onKeydownListener); - }, - - beforeUnmount() { - document.removeEventListener('click', this.handleClickOutsideSettings); - document.removeEventListener('click', this.handleClickOutsideToolMenu); - window.removeEventListener('popstate', this.handlePopState); - if (this.onDocumentClick) { - document.removeEventListener('click', this.onDocumentClick); - this.onDocumentClick = null; - } - if (this.onWindowScroll) { - window.removeEventListener('scroll', this.onWindowScroll, true); - this.onWindowScroll = null; - } - if (this.onKeydownListener) { - document.removeEventListener('keydown', this.onKeydownListener); - this.onKeydownListener = null; - } - }, - - methods: { - async bootstrapRoute() { - const path = window.location.pathname.replace(/^\/+/, ''); - if (!path || path === 'new') { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.initialRouteResolved = true; - return; - } - - const convId = path.startsWith('conv_') ? path : `conv_${path}`; - try { - const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' }); - const result = await resp.json(); - if (result.success) { - this.currentConversationId = convId; - this.currentConversationTitle = result.title || '对话'; - history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`); - } else { - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } - } catch (error) { - console.warn('初始化路由失败:', error); - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } finally { - this.initialRouteResolved = true; - } - }, - - handlePopState(event) { - const state = event.state || {}; - const convId = state.conversationId; - if (!convId) { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.messages = []; - this.resetAllStates(); - this.resetTokenStatistics(); - return; - } - this.loadConversation(convId); - }, - - stripConversationPrefix(conversationId) { - if (!conversationId) return ''; - return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId; - }, - - showContextMenu(payload) { - if (!payload || !payload.node) { - return; - } - const { node, event } = payload; - console.log('context menu', node.path, node.type); - if (event && typeof event.preventDefault === 'function') { - event.preventDefault(); - } - if (event && typeof event.stopPropagation === 'function') { - event.stopPropagation(); - } - if (!node.path && node.path !== '') { - this.hideContextMenu(); - return; - } - if (node.type !== 'file' && node.type !== 'folder') { - this.hideContextMenu(); - return; - } - - this.hideContextMenu(); - - let x = (event && event.clientX) || 0; - let y = (event && event.clientY) || 0; - const menuWidth = 200; - const menuHeight = 50; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - if (x + menuWidth > viewportWidth) { - x = viewportWidth - menuWidth - 8; - } - if (y + menuHeight > viewportHeight) { - y = viewportHeight - menuHeight - 8; - } - - this.contextMenu.visible = true; - this.contextMenu.x = Math.max(8, x); - this.contextMenu.y = Math.max(8, y); - this.contextMenu.node = node; - }, - - hideContextMenu() { - if (!this.contextMenu.visible) { - return; - } - this.contextMenu.visible = false; - this.contextMenu.node = null; - }, - - async downloadFile(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/file?path=${encodeURIComponent(path)}`; - const name = path.split('/').pop() || 'file'; - await this.downloadResource(url, name); - }, - - async downloadFolder(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/folder?path=${encodeURIComponent(path)}`; - const segments = path.split('/').filter(Boolean); - const folderName = segments.length ? segments.pop() : 'folder'; - await this.downloadResource(url, `${folderName}.zip`); - }, - - async downloadResource(url, filename) { - try { - const response = await fetch(url); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - message = errorData.error || errorData.message || message; - } catch (err) { - message = await response.text(); - } - alert(`下载失败: ${message}`); - return; - } - - const blob = await response.blob(); - const downloadName = filename || 'download'; - const link = document.createElement('a'); - const href = URL.createObjectURL(blob); - link.href = href; - link.download = downloadName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(href); - } catch (error) { - console.error('下载失败:', error); - alert(`下载失败: ${error.message || error}`); - } finally { - this.hideContextMenu(); - } - }, - - initScrollListener() { - const messagesArea = this.$refs.messagesArea; - if (!messagesArea) { - console.warn('消息区域未找到'); - return; - } - - let isProgrammaticScroll = false; - const bottomThreshold = 12; - - this._setScrollingFlag = (flag) => { - isProgrammaticScroll = !!flag; - }; - - messagesArea.addEventListener('scroll', () => { - if (isProgrammaticScroll) { - return; - } - - const scrollTop = messagesArea.scrollTop; - const scrollHeight = messagesArea.scrollHeight; - const clientHeight = messagesArea.clientHeight; - const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; - - if (isAtBottom) { - this.userScrolling = false; - this.autoScrollEnabled = true; - } else { - this.userScrolling = true; - this.autoScrollEnabled = false; - } - }); - }, - - initSocket() { - try { - console.log('初始化WebSocket连接...'); - - const usePollingOnly = window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1'; - - this.socket = io('/', usePollingOnly ? { - transports: ['polling'], - upgrade: false - } : { - transports: ['websocket', 'polling'] - }); - - // 连接事件 - this.socket.on('connect', () => { - this.isConnected = true; - console.log('WebSocket已连接'); - // 连接时重置所有状态 - this.resetAllStates(); - }); - - this.socket.on('disconnect', () => { - this.isConnected = false; - console.log('WebSocket已断开'); - // 断线时也重置状态,防止状态混乱 - this.resetAllStates(); - }); - - this.socket.on('connect_error', (error) => { - console.error('WebSocket连接错误:', error.message); - }); - - // ========================================== - // Token统计WebSocket事件处理(修复版) - // ========================================== - - this.socket.on('token_update', (data) => { - console.log('收到token更新事件:', data); - - // 只处理当前对话的token更新 - if (data.conversation_id === this.currentConversationId) { - // 更新累计统计(使用后端提供的准确字段名) - this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; - - console.log(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); - - // 同时更新当前上下文Token(关键修复) - this.updateCurrentContextTokens(); - - this.$forceUpdate(); - } - }); - - this.socket.on('todo_updated', (data) => { - console.log('收到todo更新事件:', data); - if (data && data.conversation_id) { - this.currentConversationId = data.conversation_id; - } - this.todoList = data && data.todo_list ? data.todo_list : null; - }); - - // 系统就绪 - this.socket.on('system_ready', (data) => { - this.projectPath = data.project_path || ''; - this.thinkingMode = data.thinking_mode || '未知'; - console.log('系统就绪:', data); - - // 系统就绪后立即加载对话列表 - this.loadConversationsList(); - }); - - this.socket.on('tool_settings_updated', (data) => { - console.log('收到工具设置更新:', data); - if (data && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } - }); - - // ========================================== - // 对话管理相关Socket事件 - // ========================================== - - // 监听对话变更事件 - this.socket.on('conversation_changed', (data) => { - console.log('对话已切换:', data); - this.currentConversationId = data.conversation_id; - this.currentConversationTitle = data.title || ''; - - if (data.cleared) { - // 对话被清空 - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - // 重置Token统计 - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.loadConversationsList(); - this.fetchTodoList(); - }); - - this.socket.on('conversation_resolved', (data) => { - if (!data || !data.conversation_id) { - return; - } - const convId = data.conversation_id; - this.currentConversationId = convId; - if (data.title) { - this.currentConversationTitle = data.title; - } - const pathFragment = this.stripConversationPrefix(convId); - const currentPath = window.location.pathname.replace(/^\/+/, ''); - if (data.created) { - history.pushState({ conversationId: convId }, '', `/${pathFragment}`); - } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); - } - }); - - // 监听对话加载事件 - this.socket.on('conversation_loaded', (data) => { - console.log('对话已加载:', data); - if (data.clear_ui) { - // 清理当前UI状态,准备显示历史内容 - this.resetAllStates(); - } - - // 延迟获取并显示历史对话内容 - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 延迟获取Token统计(累计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - this.fetchTodoList(); - }, 500); - }); - - // 监听对话列表更新事件 - this.socket.on('conversation_list_update', (data) => { - console.log('对话列表已更新:', data); - // 刷新对话列表 - this.loadConversationsList(); - }); - - // 监听状态更新事件 - this.socket.on('status_update', (status) => { - // 更新系统状态信息 - if (status.conversation && status.conversation.current_id) { - this.currentConversationId = status.conversation.current_id; - } - }); - - // AI消息开始 - this.socket.on('ai_message_start', () => { - console.log('AI消息开始'); - this.cleanupStaleToolActions(); - const newMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(newMessage); - this.currentMessageIndex = this.messages.length - 1; - this.streamingMessage = true; - this.stopRequested = false; - this.autoScrollEnabled = true; - this.scrollToBottom(); - }); - - // 思考流开始 - this.socket.on('thinking_start', () => { - console.log('思考开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking = ''; - msg.currentStreamingType = 'thinking'; - - const action = { - id: Date.now() + Math.random(), - type: 'thinking', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - - const blockId = `${this.currentMessageIndex}-thinking-${msg.actions.length - 1}`; - this.expandedBlocks.add(blockId); - this.$forceUpdate(); - } - }); - - // 思考内容块 - this.socket.on('thinking_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 思考结束 - this.socket.on('thinking_end', (data) => { - console.log('思考结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.streaming = false; - lastAction.content = data.full_content; - } - msg.streamingThinking = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 文本流开始 - this.socket.on('text_start', () => { - console.log('文本开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText = ''; - msg.currentStreamingType = 'text'; - - const action = { - id: Date.now() + Math.random(), - type: 'text', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - } - }); - - // 文本内容块 - this.socket.on('text_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'text') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - - // 实时渲染LaTeX - this.renderLatexInRealtime(); - } - }); - - // 文本结束 - this.socket.on('text_end', (data) => { - console.log('文本结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找当前流式文本的action - for (let i = msg.actions.length - 1; i >= 0; i--) { - const action = msg.actions[i]; - if (action.type === 'text' && action.streaming) { - action.streaming = false; - action.content = data.full_content; - console.log('文本action已更新为完成状态'); - break; - } - } - - msg.streamingText = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 工具提示事件(可选) - this.socket.on('tool_hint', (data) => { - console.log('工具提示:', data.name); - // 可以在这里添加提示UI - }); - - // 工具准备中事件 - 实时显示 - this.socket.on('tool_preparing', (data) => { - console.log('工具准备中:', data.name); - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - const action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'preparing', - result: null, - message: data.message || `准备调用 ${data.name}...` - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.preparingTools.set(data.id, action); - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 工具状态更新事件 - 实时显示详细状态 - this.socket.on('tool_status', (data) => { - console.log('工具状态:', data); - const target = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (target) { - target.tool.statusDetail = data.detail; - target.tool.statusType = data.status; - this.$forceUpdate(); - return; - } - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找对应的工具action并更新状态 - for (let action of msg.actions) { - if (action.type === 'tool' && action.tool.name === data.tool) { - action.tool.statusDetail = data.detail; - action.tool.statusType = data.status; - this.$forceUpdate(); - break; - } - } - } - }); - - // 工具开始(从准备转为执行) - this.socket.on('tool_start', (data) => { - console.log('工具开始执行:', data.name); - let action = null; - if (data.preparing_id && this.preparingTools.has(data.preparing_id)) { - action = this.preparingTools.get(data.preparing_id); - this.preparingTools.delete(data.preparing_id); - } else { - action = this.findToolAction(data.id, data.preparing_id, data.execution_id); - } - if (!action) { - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'running', - result: null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - } - action.tool.status = 'running'; - action.tool.arguments = data.arguments; - action.tool.message = null; - action.tool.id = data.id; - action.tool.executionId = data.id; - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 更新action(工具完成) - this.socket.on('update_action', (data) => { - console.log('更新action:', data.id, 'status:', data.status); - let targetAction = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) { - targetAction = this.preparingTools.get(data.preparing_id); - } - if (!targetAction) { - outer: for (const message of this.messages) { - if (!message.actions) continue; - for (const action of message.actions) { - if (action.type !== 'tool') continue; - const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; - const matchByToolId = action.tool.id === data.id; - const matchByPreparingId = action.id === data.preparing_id; - if (matchByExecution || matchByToolId || matchByPreparingId) { - targetAction = action; - break outer; - } - } - } - } - if (targetAction) { - if (data.status) { - targetAction.tool.status = data.status; - } - if (data.result !== undefined) { - targetAction.tool.result = data.result; - } - if (data.message !== undefined) { - targetAction.tool.message = data.message; - } - if (data.awaiting_content) { - targetAction.tool.awaiting_content = true; - } else if (data.status === 'completed') { - targetAction.tool.awaiting_content = false; - } - if (!targetAction.tool.executionId && (data.execution_id || data.id)) { - targetAction.tool.executionId = data.execution_id || data.id; - } - this.registerToolAction(targetAction, data.execution_id || data.id); - if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { - this.unregisterToolAction(targetAction); - if (data.id) { - this.preparingTools.delete(data.id); - } - if (data.preparing_id) { - this.preparingTools.delete(data.preparing_id); - } - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - - // 关键修复:每个工具完成后都更新当前上下文Token - if (data.status === 'completed') { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - this.socket.on('append_payload', (data) => { - console.log('收到append_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: data.path || '未知文件', - forced: !!data.forced, - success: data.success === undefined ? true : !!data.success, - lines: data.lines ?? null, - bytes: data.bytes ?? null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - this.socket.on('modify_payload', (data) => { - console.log('收到modify_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: data.path || '未知文件', - total: data.total ?? null, - completed: data.completed || [], - failed: data.failed || [], - forced: !!data.forced - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 停止请求确认 - this.socket.on('stop_requested', (data) => { - console.log('停止请求已接收:', data.message); - // 可以显示提示信息 - }); - - // 任务停止 - this.socket.on('task_stopped', (data) => { - console.log('任务已停止:', data.message); - this.resetAllStates(); - }); - - // 任务完成(重点:更新Token统计) - this.socket.on('task_complete', (data) => { - console.log('任务完成', data); - this.resetAllStates(); - - // 任务完成后立即更新Token统计(关键修复) - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - this.fetchConversationTokenStatistics(); - } - }); - - // 聚焦文件更新 - this.socket.on('focused_files_update', (data) => { - this.focusedFiles = data || {}; - // 聚焦文件变化时更新当前上下文Token(关键修复) - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 文件树更新 - this.socket.on('file_tree_update', (data) => { - this.updateFileTree(data); - // 文件树变化时也可能影响上下文 - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 系统消息 - this.socket.on('system_message', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `system-${Date.now()}-${Math.random()}`, - type: 'system', - content: data.content, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } else { - this.addSystemMessage(data.content); - } - }); - - // 错误处理 - this.socket.on('error', (data) => { - this.addSystemMessage(`错误: ${data.message}`); - // 仅标记当前流结束,避免状态错乱 - this.streamingMessage = false; - this.stopRequested = false; - }); - - // 命令结果 - this.socket.on('command_result', (data) => { - if (data.command === 'clear' && data.success) { - this.messages = []; - this.currentMessageIndex = -1; - this.expandedBlocks.clear(); - // 清除对话时重置Token统计 - this.resetTokenStatistics(); - } else if (data.command === 'status' && data.success) { - this.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); - } else if (!data.success) { - this.addSystemMessage(`命令失败: ${data.message}`); - } - }); - - } catch (error) { - console.error('Socket初始化失败:', error); - } - }, - - registerToolAction(action, executionId = null) { - if (!action || action.type !== 'tool') { - return; - } - const keys = new Set(); - if (action.id) { - keys.add(action.id); - } - if (action.tool && action.tool.id) { - keys.add(action.tool.id); - } - if (executionId) { - keys.add(executionId); - } - if (action.tool && action.tool.executionId) { - keys.add(action.tool.executionId); - } - keys.forEach(key => { - if (!key) { - return; - } - this.toolActionIndex.set(key, action); - }); - }, - - unregisterToolAction(action) { - if (!action || action.type !== 'tool') { - return; - } - const keysToRemove = []; - for (const [key, stored] of this.toolActionIndex.entries()) { - if (stored === action) { - keysToRemove.push(key); - } - } - keysToRemove.forEach(key => this.toolActionIndex.delete(key)); - }, - - findToolAction(id, preparingId, executionId) { - if (!this.toolActionIndex) { - return null; - } - const candidates = [executionId, id, preparingId]; - for (const key of candidates) { - if (key && this.toolActionIndex.has(key)) { - return this.toolActionIndex.get(key); - } - } - return null; - }, - - cleanupStaleToolActions() { - this.messages.forEach(msg => { - if (!msg.actions) { - return; - } - msg.actions.forEach(action => { - if (action.type !== 'tool' || !action.tool) { - return; - } - if (['running', 'preparing'].includes(action.tool.status)) { - action.tool.status = 'stale'; - action.tool.message = action.tool.message || '已被新的响应中断'; - this.unregisterToolAction(action); - } - }); - }); - this.preparingTools.clear(); - this.toolActionIndex.clear(); - }, - - ensureAssistantMessage() { - if (this.currentMessageIndex >= 0) { - return this.messages[this.currentMessageIndex]; - } - const message = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(message); - this.currentMessageIndex = this.messages.length - 1; - return message; - }, - - // 完整重置所有状态 - resetAllStates() { - console.log('重置所有前端状态'); - this.hideContextMenu(); - - // 重置消息和流状态 - this.streamingMessage = false; - this.currentMessageIndex = -1; - this.stopRequested = false; - - // 清理工具状态 - this.preparingTools.clear(); - this.activeTools.clear(); - this.toolActionIndex.clear(); - - // ✨ 新增:将所有未完成的工具标记为已完成 - this.messages.forEach(msg => { - if (msg.role === 'assistant' && msg.actions) { - msg.actions.forEach(action => { - if (action.type === 'tool' && - (action.tool.status === 'preparing' || action.tool.status === 'running')) { - action.tool.status = 'completed'; - } - }); - } - }); - - // 重置滚动状态 - this.userScrolling = false; - this.autoScrollEnabled = true; - - // 清理Markdown缓存 - if (this.markdownCache) { - this.markdownCache.clear(); - } - - // 强制更新视图 - this.$forceUpdate(); - - this.settingsOpen = false; - this.toolMenuOpen = false; - this.toolSettingsLoading = false; - this.toolSettings = []; - - console.log('前端状态重置完成'); - }, - - // 重置Token统计 - resetTokenStatistics() { - this.currentContextTokens = 0; - this.currentConversationTokens = { - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - }; - }, - - async loadInitialData() { - try { - console.log('加载初始数据...'); - - const filesResponse = await fetch('/api/files'); - const filesData = await filesResponse.json(); - this.updateFileTree(filesData); - - const focusedResponse = await fetch('/api/focused'); - const focusedData = await focusedResponse.json(); - this.focusedFiles = focusedData || {}; - - await this.fetchTodoList(); - - const statusResponse = await fetch('/api/status'); - const statusData = await statusResponse.json(); - this.projectPath = statusData.project_path || ''; - this.thinkingMode = statusData.thinking_mode || '未知'; - - // 获取当前对话信息 - const statusConversationId = statusData.conversation && statusData.conversation.current_id; - if (statusConversationId) { - if (!this.currentConversationId) { - this.currentConversationId = statusConversationId; - } - // 如果有当前对话,尝试获取标题和Token统计 - try { - const convResponse = await fetch(`/api/conversations/current`); - const convData = await convResponse.json(); - if (convData.success && convData.data) { - this.currentConversationTitle = convData.data.title; - } - await this.fetchAndDisplayHistory(); - // 获取当前对话的Token统计 - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - } catch (e) { - console.warn('获取当前对话标题失败:', e); - } - } - - await this.loadToolSettings(true); - - console.log('初始数据加载完成'); - } catch (error) { - console.error('加载初始数据失败:', error); - } - }, - - async refreshFileTree() { - try { - const response = await fetch('/api/files'); - const data = await response.json(); - this.updateFileTree(data); - } catch (error) { - console.error('刷新文件树失败:', error); - } - }, - - // ========================================== - // Token统计相关方法(完全修复版) - // ========================================== - - async updateCurrentContextTokens() { - // 获取当前上下文Token数(动态计算,包含完整prompt构建) - if (!this.currentConversationId) { - this.currentContextTokens = 0; - return; - } - - try { - console.log(`正在更新当前上下文Token: ${this.currentConversationId}`); - - // 关键修复:使用正确的动态API,包含文件结构+记忆+聚焦文件+终端内容+工具定义 - const response = await fetch(`/api/conversations/${this.currentConversationId}/tokens`); - const data = await response.json(); - - if (data.success && data.data) { - this.currentContextTokens = data.data.total_tokens || 0; - console.log(`当前上下文Token更新: ${this.currentContextTokens}`); - this.$forceUpdate(); - } else { - console.warn('获取当前上下文Token失败:', data.error); - this.currentContextTokens = 0; - } - } catch (error) { - console.warn('获取当前上下文Token异常:', error); - this.currentContextTokens = 0; - } - }, - - async fetchConversationTokenStatistics() { - // 获取对话累计Token统计(加载对话时、任务完成后调用) - if (!this.currentConversationId) { - this.resetTokenStatistics(); - return; - } - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/token-statistics`); - const data = await response.json(); - - if (data.success && data.data) { - // 更新累计统计 - this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; - - console.log(`累计Token统计: 输入=${data.data.total_input_tokens}, 输出=${data.data.total_output_tokens}, 总计=${data.data.total_tokens}`); - this.$forceUpdate(); - } else { - console.warn('获取Token统计失败:', data.error); - // 保持当前统计,不重置 - } - } catch (error) { - console.warn('获取Token统计异常:', error); - // 保持当前统计,不重置 - } - }, - - // Token面板折叠/展开切换 - toggleTokenPanel() { - this.tokenPanelCollapsed = !this.tokenPanelCollapsed; - }, - - // ========================================== - // 对话管理核心功能 - // ========================================== - - async loadConversationsList() { - this.conversationsLoading = true; - try { - const response = await fetch(`/api/conversations?limit=${this.conversationsLimit}&offset=${this.conversationsOffset}`); - const data = await response.json(); - - if (data.success) { - if (this.conversationsOffset === 0) { - this.conversations = data.data.conversations; - } else { - this.conversations.push(...data.data.conversations); - } - this.hasMoreConversations = data.data.has_more; - console.log(`已加载 ${this.conversations.length} 个对话`); - - if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { - const latestConversation = this.conversations[0]; - if (latestConversation && latestConversation.id) { - await this.loadConversation(latestConversation.id); - } - } - } else { - console.error('加载对话列表失败:', data.error); - } - } catch (error) { - console.error('加载对话列表异常:', error); - } finally { - this.conversationsLoading = false; - } - }, - - async loadMoreConversations() { - if (this.loadingMoreConversations || !this.hasMoreConversations) return; - - this.loadingMoreConversations = true; - this.conversationsOffset += this.conversationsLimit; - await this.loadConversationsList(); - this.loadingMoreConversations = false; - }, - - async loadConversation(conversationId) { - console.log('加载对话:', conversationId); - - if (conversationId === this.currentConversationId) { - console.log('已是当前对话,跳过加载'); - return; - } - - try { - // 1. 调用加载API - const response = await fetch(`/api/conversations/${conversationId}/load`, { - method: 'PUT' - }); - const result = await response.json(); - - if (result.success) { - console.log('对话加载API成功:', result); - - // 2. 更新当前对话信息 - this.currentConversationId = conversationId; - this.currentConversationTitle = result.title; - history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`); - - // 3. 重置UI状态 - this.resetAllStates(); - - // 4. 延迟获取并显示历史对话内容(关键功能) - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - }, 500); - - } else { - console.error('对话加载失败:', result.message); - alert(`加载对话失败: ${result.message}`); - } - } catch (error) { - console.error('加载对话异常:', error); - alert(`加载对话异常: ${error.message}`); - } - }, - - // ========================================== - // 关键功能:获取并显示历史对话内容 - // ========================================== - async fetchAndDisplayHistory() { - console.log('开始获取历史对话内容...'); - - if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { - console.log('没有当前对话ID,跳过历史加载'); - return; - } - - try { - // 使用专门的API获取对话消息历史 - const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); - - if (!messagesResponse.ok) { - console.warn('无法获取消息历史,尝试备用方法'); - // 备用方案:通过状态API获取 - const statusResponse = await fetch('/api/status'); - const status = await statusResponse.json(); - console.log('系统状态:', status); - - // 如果状态中有对话历史字段 - if (status.conversation_history && Array.isArray(status.conversation_history)) { - this.renderHistoryMessages(status.conversation_history); - return; - } - - console.log('备用方案也无法获取历史消息'); - return; - } - - const messagesData = await messagesResponse.json(); - console.log('获取到消息数据:', messagesData); - - if (messagesData.success && messagesData.data && messagesData.data.messages) { - const messages = messagesData.data.messages; - console.log(`发现 ${messages.length} 条历史消息`); - - if (messages.length > 0) { - // 清空当前显示的消息 - this.messages = []; - - // 渲染历史消息 - 这是关键功能 - this.renderHistoryMessages(messages); - - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - - console.log('历史对话内容显示完成'); - } else { - console.log('对话存在但没有历史消息'); - this.messages = []; - } - } else { - console.log('消息数据格式不正确:', messagesData); - this.messages = []; - } - - } catch (error) { - console.error('获取历史对话失败:', error); - console.log('尝试不显示错误弹窗,仅在控制台记录'); - // 不显示alert,避免打断用户体验 - this.messages = []; - } - }, - - // ========================================== - // 关键功能:渲染历史消息 - // ========================================== - renderHistoryMessages(historyMessages) { - console.log('开始渲染历史消息...', historyMessages); - console.log('历史消息数量:', historyMessages.length); - - if (!Array.isArray(historyMessages)) { - console.error('历史消息不是数组格式'); - return; - } - - let currentAssistantMessage = null; - - historyMessages.forEach((message, index) => { - console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); - - if (message.role === 'user') { - // 用户消息 - 先结束之前的assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - this.messages.push({ - role: 'user', - content: message.content || '' - }); - console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); - - } else if (message.role === 'assistant') { - // AI消息 - 如果没有当前assistant消息,创建一个 - if (!currentAssistantMessage) { - currentAssistantMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - } - - // 处理思考内容 - 支持多种格式 - const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; - - let allThinkingContent = ''; - for (const pattern of thinkPatterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - allThinkingContent += match[1].trim() + '\n'; - } - } - - if (allThinkingContent) { - currentAssistantMessage.actions.push({ - id: `history-think-${Date.now()}-${Math.random()}`, - type: 'thinking', - content: allThinkingContent.trim(), - streaming: false, - timestamp: Date.now() - }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); - } - - // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - - let textContent = content - .replace(/[\s\S]*?<\/think>/g, '') - .replace(/[\s\S]*?<\/thinking>/g, '') - .trim(); - - if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: appendPayloadMeta.path || '未知文件', - forced: !!appendPayloadMeta.forced, - success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, - lines: appendPayloadMeta.lines ?? null, - bytes: appendPayloadMeta.bytes ?? null - }, - timestamp: Date.now() - }); - console.log('添加append占位信息:', appendPayloadMeta.path); - } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: modifyPayloadMeta.path || '未知文件', - total: modifyPayloadMeta.total_blocks ?? null, - completed: modifyPayloadMeta.completed || [], - failed: modifyPayloadMeta.failed || [], - forced: !!modifyPayloadMeta.forced, - details: modifyPayloadMeta.details || [] - }, - timestamp: Date.now() - }); - console.log('添加modify占位信息:', modifyPayloadMeta.path); - } - - if (textContent && !appendPayloadMeta && !modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-text-${Date.now()}-${Math.random()}`, - type: 'text', - content: textContent, - streaming: false, - timestamp: Date.now() - }); - console.log('添加文本内容:', textContent.substring(0, 50) + '...'); - } - - // 处理工具调用 - if (message.tool_calls && Array.isArray(message.tool_calls)) { - message.tool_calls.forEach((toolCall, tcIndex) => { - let arguments_obj = {}; - try { - arguments_obj = typeof toolCall.function.arguments === 'string' - ? JSON.parse(toolCall.function.arguments || '{}') - : (toolCall.function.arguments || {}); - } catch (e) { - console.warn('解析工具参数失败:', e); - arguments_obj = {}; - } - - currentAssistantMessage.actions.push({ - id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, - type: 'tool', - tool: { - id: toolCall.id, - name: toolCall.function.name, - arguments: arguments_obj, - status: 'preparing', - result: null - }, - timestamp: Date.now() - }); - console.log('添加工具调用:', toolCall.function.name); - }); - } - - } else if (message.role === 'tool') { - // 工具结果 - 更新当前assistant消息中对应的工具 - if (currentAssistantMessage) { - // 查找对应的工具action - 使用更灵活的匹配 - let toolAction = null; - - // 优先按tool_call_id匹配 - if (message.tool_call_id) { - toolAction = currentAssistantMessage.actions.find(action => - action.type === 'tool' && - action.tool.id === message.tool_call_id - ); - } - - // 如果找不到,按name匹配最后一个同名工具 - if (!toolAction && message.name) { - const sameNameTools = currentAssistantMessage.actions.filter(action => - action.type === 'tool' && - action.tool.name === message.name - ); - toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 - } - - if (toolAction) { - // 解析工具结果 - let result; - try { - // 尝试解析为JSON - result = JSON.parse(message.content); - } catch (e) { - // 如果不是JSON,就作为纯文本 - result = { - output: message.content, - success: true - }; - } - - toolAction.tool.status = 'completed'; - toolAction.tool.result = result; - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } - console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - if (message.name === 'append_to_file' && result && typeof result === 'object') { - const appendSummary = { - path: result.path || '未知文件', - success: result.success !== false, - summary: result.message || (result.success === false ? '追加失败' : '追加完成'), - lines: result.lines || 0, - bytes: result.bytes || 0, - forced: !!result.forced - }; - currentAssistantMessage.actions.push({ - id: `history-append-${Date.now()}-${Math.random()}`, - type: 'append', - append: appendSummary, - timestamp: Date.now() - }); - } - } else { - console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); - } - } - - } else { - // 其他类型消息(如system)- 先结束当前assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - console.log('处理其他类型消息:', message.role); - this.messages.push({ - role: message.role, - content: message.content || '' - }); - } - }); - - // 处理最后一个assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - } - - console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); - - // 强制更新视图 - this.$forceUpdate(); - - // 确保滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - }, - - async createNewConversation() { - console.log('创建新对话...'); - - try { - const response = await fetch('/api/conversations', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thinking_mode: this.thinkingMode !== '快速模式' - }) - }); - - const result = await response.json(); - - if (result.success) { - console.log('新对话创建成功:', result.conversation_id); - - // 清空当前消息 - this.messages = []; - this.currentConversationId = result.conversation_id; - this.currentConversationTitle = '新对话'; - history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); - - // 重置Token统计 - this.resetTokenStatistics(); - - // 重置状态 - this.resetAllStates(); - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('创建对话失败:', result.message); - alert(`创建对话失败: ${result.message}`); - } - } catch (error) { - console.error('创建对话异常:', error); - alert(`创建对话异常: ${error.message}`); - } - }, - - async deleteConversation(conversationId) { - if (!confirm('确定要删除这个对话吗?删除后无法恢复。')) { - return; - } - - console.log('删除对话:', conversationId); - - try { - const response = await fetch(`/api/conversations/${conversationId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - console.log('对话删除成功'); - - // 如果删除的是当前对话,清空界面 - if (conversationId === this.currentConversationId) { - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - this.resetAllStates(); - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('删除对话失败:', result.message); - alert(`删除对话失败: ${result.message}`); - } - } catch (error) { - console.error('删除对话异常:', error); - alert(`删除对话异常: ${error.message}`); - } - }, - - async duplicateConversation(conversationId) { - console.log('复制对话:', conversationId); - try { - const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.duplicate_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - - this.conversationsOffset = 0; - await this.loadConversationsList(); - } else { - const message = result.message || result.error || '复制失败'; - alert(`复制失败: ${message}`); - } - } catch (error) { - console.error('复制对话异常:', error); - alert(`复制对话异常: ${error.message}`); - } - }, - - searchConversations() { - // 简单的搜索功能,实际实现可以调用搜索API - if (this.searchTimer) { - clearTimeout(this.searchTimer); - } - - this.searchTimer = setTimeout(() => { - if (this.searchQuery.trim()) { - console.log('搜索对话:', this.searchQuery); - // TODO: 实现搜索API调用 - // this.searchConversationsAPI(this.searchQuery); - } else { - // 清空搜索,重新加载全部对话 - this.conversationsOffset = 0; - this.loadConversationsList(); - } - }, 300); - }, - - toggleSidebar() { - this.sidebarCollapsed = !this.sidebarCollapsed; - }, - - formatTime(timeString) { - if (!timeString) return ''; - - const date = new Date(timeString); - const now = new Date(); - const diffMs = now - date; - const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - const diffDays = Math.floor(diffHours / 24); - - if (diffHours < 1) { - return '刚刚'; - } else if (diffHours < 24) { - return `${diffHours}小时前`; - } else if (diffDays < 7) { - return `${diffDays}天前`; - } else { - return date.toLocaleDateString('zh-CN', { - month: 'short', - day: 'numeric' - }); - } - }, - - // ========================================== - // 原有功能保持不变 - // ========================================== - - updateFileTree(structure) { - const treeDictionary = structure && structure.tree ? structure.tree : {}; - - const buildNodes = (treeMap) => { - if (!treeMap) { - return []; - } - - const entries = Object.keys(treeMap).map((name) => { - const node = treeMap[name] || {}; - if (node.type === 'folder') { - return { - type: 'folder', - name, - path: node.path || name, - children: buildNodes(node.children) - }; - } - return { - type: 'file', - name, - path: node.path || name, - annotation: node.annotation || '' - }; - }); - - entries.sort((a, b) => { - if (a.type !== b.type) { - return a.type === 'folder' ? -1 : 1; - } - return a.name.localeCompare(b.name, 'zh-CN'); - }); - - return entries; - }; - - const nodes = buildNodes(treeDictionary); - - const expanded = { ...this.expandedFolders }; - const validFolderPaths = new Set(); - - const ensureExpansion = (list, depth = 0) => { - list.forEach((item) => { - if (item.type === 'folder') { - validFolderPaths.add(item.path); - if (expanded[item.path] === undefined) { - expanded[item.path] = false; - } - ensureExpansion(item.children || [], depth + 1); - } - }); - }; - - ensureExpansion(nodes); - - Object.keys(expanded).forEach((path) => { - if (!validFolderPaths.has(path)) { - delete expanded[path]; - } - }); - - this.expandedFolders = expanded; - this.fileTree = nodes; - }, - - toggleFolder(path) { - this.hideContextMenu(); - if (!path) { - return; - } - const current = !!this.expandedFolders[path]; - this.expandedFolders = { - ...this.expandedFolders, - [path]: !current - }; - }, - - toggleTodoPanel() { - this.showTodoList = !this.showTodoList; - }, - - formatTaskStatus(task) { - if (!task) { - return ''; - } - return task.status === 'done' - ? `${this.todoDoneEmoji} 完成` - : `${this.todoPendingEmoji} 未完成`; - }, - - toolCategoryEmoji(categoryId) { - return this.toolCategoryEmojis[categoryId] || '⚙️'; - }, - - async fetchTodoList() { - try { - const response = await fetch('/api/todo-list'); - const data = await response.json(); - if (data.success) { - this.todoList = data.data || null; - } - } catch (error) { - console.error('获取待办列表失败:', error); - } - }, - - triggerFileUpload() { - if (this.uploading) { - return; - } - const input = this.$refs.fileUploadInput; - if (input) { - input.click(); - } - }, - - handleFileSelected(event) { - const fileInput = event?.target; - if (!fileInput || !fileInput.files || fileInput.files.length === 0) { - return; - } - const [file] = fileInput.files; - this.uploadSelectedFile(file); - }, - - resetFileInput() { - const input = this.$refs.fileUploadInput; - if (input) { - input.value = ''; - } - }, - - async uploadSelectedFile(file) { - if (!file || this.uploading) { - this.resetFileInput(); - return; - } - - this.uploading = true; - - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/upload', { - method: 'POST', - body: formData - }); - - let result = {}; - try { - result = await response.json(); - } catch (parseError) { - throw new Error('服务器响应无法解析'); - } - - if (!response.ok || !result.success) { - const message = result.error || result.message || '上传失败'; - throw new Error(message); - } - - await this.refreshFileTree(); - alert(`上传成功:${result.path || file.name}`); - } catch (error) { - console.error('文件上传失败:', error); - alert(`文件上传失败:${error.message}`); - } finally { - this.uploading = false; - this.resetFileInput(); - } - }, - - handleSendOrStop() { - if (this.streamingMessage) { - this.stopTask(); - } else { - this.sendMessage(); - } - }, - - sendMessage() { - if (this.streamingMessage || !this.isConnected) { - return; - } - - if (!this.inputMessage.trim()) { - return; - } - - const message = this.inputMessage; - - if (message.startsWith('/')) { - this.socket.emit('send_command', { command: message }); - this.inputMessage = ''; - this.settingsOpen = false; - return; - } - - this.messages.push({ - role: 'user', - content: message - }); - - this.currentMessageIndex = -1; - this.socket.emit('send_message', { message: message, conversation_id: this.currentConversationId }); - this.inputMessage = ''; - this.autoScrollEnabled = true; - this.scrollToBottom(); - this.settingsOpen = false; - - // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) - setTimeout(() => { - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - } - }, 1000); - }, - - // 新增:停止任务方法 - stopTask() { - if (this.streamingMessage && !this.stopRequested) { - this.socket.emit('stop_task'); - this.stopRequested = true; - console.log('发送停止请求'); - } - this.settingsOpen = false; - }, - - clearChat() { - if (confirm('确定要清除所有对话记录吗?')) { - this.socket.emit('send_command', { command: '/clear' }); - } - this.settingsOpen = false; - }, - - async compressConversation() { - if (!this.currentConversationId) { - alert('当前没有可压缩的对话。'); - return; - } - - if (this.compressing) { - return; - } - - const confirmed = confirm('确定要压缩当前对话记录吗?压缩后会生成新的对话副本。'); - if (!confirmed) { - return; - } - - this.settingsOpen = false; - this.compressing = true; - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.compressed_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - console.log('对话压缩完成:', result); - } else { - const message = result.message || result.error || '压缩失败'; - alert(`压缩失败: ${message}`); - } - } catch (error) { - console.error('压缩对话异常:', error); - alert(`压缩对话异常: ${error.message}`); - } finally { - this.compressing = false; - } - }, - - toggleToolMenu() { - if (!this.isConnected) { - return; - } - const nextState = !this.toolMenuOpen; - this.toolMenuOpen = nextState; - if (nextState) { - this.settingsOpen = false; - this.loadToolSettings(); - } - }, - - handleClickOutsideToolMenu(event) { - if (!this.toolMenuOpen) { - return; - } - const dropdown = this.$refs.toolDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.toolMenuOpen = false; - } - }, - - applyToolSettingsSnapshot(categories) { - if (!Array.isArray(categories)) { - return; - } - this.toolSettings = categories.map((item) => ({ - id: item.id, - label: item.label || item.id, - enabled: !!item.enabled, - tools: Array.isArray(item.tools) ? item.tools : [] - })); - this.toolSettingsLoading = false; - }, - - async loadToolSettings(force = false) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - if (!force && this.toolSettings.length > 0) { - return; - } - this.toolSettingsLoading = true; - try { - const response = await fetch('/api/tool-settings'); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('获取工具设置失败:', data); - this.toolSettingsLoading = false; - } - } catch (error) { - console.error('获取工具设置异常:', error); - this.toolSettingsLoading = false; - } - }, - - async updateToolCategory(categoryId, enabled) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - const previousSnapshot = this.toolSettings.map((item) => ({ ...item })); - this.toolSettings = this.toolSettings.map((item) => { - if (item.id === categoryId) { - return { ...item, enabled }; - } - return item; - }); - try { - const response = await fetch('/api/tool-settings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - category: categoryId, - enabled - }) - }); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('更新工具设置失败:', data); - this.toolSettings = previousSnapshot; - } - } catch (error) { - console.error('更新工具设置异常:', error); - this.toolSettings = previousSnapshot; - } - this.toolSettingsLoading = false; - }, - - toggleSettings() { - if (!this.isConnected) { - return; - } - this.settingsOpen = !this.settingsOpen; - if (this.settingsOpen) { - this.toolMenuOpen = false; - } - }, - - toggleFocusPanel() { - this.rightCollapsed = !this.rightCollapsed; - if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - this.settingsOpen = false; - }, - - handleClickOutsideSettings(event) { - if (!this.settingsOpen) { - return; - } - const dropdown = this.$refs.settingsDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.settingsOpen = false; - } - }, - - addSystemMessage(content) { - this.messages.push({ - role: 'system', - content: content - }); - this.conditionalScrollToBottom(); - }, - - toggleBlock(id) { - if (this.expandedBlocks.has(id)) { - this.expandedBlocks.delete(id); - } else { - this.expandedBlocks.add(id); - } - this.$forceUpdate(); - }, - - // 修复:工具相关方法 - 接收tool对象而不是name - getToolIcon(tool) { - const toolName = typeof tool === 'string' ? tool : tool.name; - const icons = { - 'create_file': '📄', - 'sleep': '⏱️', - 'read_file': '📖', - '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': '❗' - }; - return icons[toolName] || '⚙️'; - }, - - getToolAnimationClass(tool) { - // 根据工具状态返回不同的动画类 - if (tool.status === 'hinted') { - return 'hint-animation pulse-slow'; - } else if (tool.status === 'preparing') { - return 'preparing-animation'; - } else if (tool.status === 'running') { - const animations = { - 'create_file': 'file-animation', - 'read_file': 'read-animation', - 'delete_file': 'file-animation', - 'rename_file': 'file-animation', - 'modify_file': 'file-animation', - 'append_to_file': 'file-animation', - 'create_folder': 'file-animation', - 'focus_file': 'focus-animation', - 'unfocus_file': 'focus-animation', - 'web_search': 'search-animation', - 'extract_webpage': 'search-animation', - 'save_webpage': 'file-animation', - 'run_python': 'code-animation', - 'run_command': 'terminal-animation', - 'update_memory': 'memory-animation', - 'sleep': 'wait-animation', - 'terminal_session': 'terminal-animation', - 'terminal_input': 'terminal-animation', - 'terminal_snapshot': 'terminal-animation', - 'terminal_reset': 'terminal-animation', - 'todo_create': 'file-animation', - 'todo_update_task': 'file-animation', - 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' - }; - return animations[tool.name] || 'default-animation'; - } - return ''; - }, - - // 修复:获取工具状态文本 - getToolStatusText(tool) { - // 优先使用自定义消息 - if (tool.message) { - return tool.message; - } - - if (tool.status === 'hinted') { - return `可能需要 ${tool.name}...`; - } else if (tool.status === 'preparing') { - return `准备调用 ${tool.name}...`; - } else if (tool.status === 'running') { - const texts = { - 'create_file': '正在创建文件...', - 'read_file': '正在读取文件...', - 'sleep': '正在等待...', - 'delete_file': '正在删除文件...', - 'rename_file': '正在重命名文件...', - 'modify_file': '正在修改文件...', - 'append_to_file': '正在追加文件...', - 'create_folder': '正在创建文件夹...', - 'focus_file': '正在聚焦文件...', - 'unfocus_file': '正在取消聚焦...', - 'web_search': '正在搜索网络...', - 'extract_webpage': '正在提取网页...', - 'save_webpage': '正在保存网页...', - 'run_python': '正在执行Python代码...', - 'run_command': '正在执行命令...', - 'update_memory': '正在更新记忆...', - 'terminal_session': '正在管理终端会话...', - 'terminal_input': '正在发送终端输入...', - 'terminal_snapshot': '正在获取终端快照...', - 'terminal_reset': '正在重置终端...' - }; - return texts[tool.name] || '正在执行...'; - } else if (tool.status === 'completed') { - // 修复:完成状态的文本 - const texts = { - 'create_file': '文件创建成功', - 'read_file': '文件读取完成', - 'delete_file': '文件删除成功', - 'sleep': '等待完成', - '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': '终端已重置' - }; - return texts[tool.name] || '执行完成'; - } else { - // 其他状态 - return `${tool.name} - ${tool.status}`; - } - }, - - getToolDescription(tool) { - // 如果有状态详情,优先显示 - if (tool.statusDetail) { - return tool.statusDetail; - } - - if (tool.result && typeof tool.result === 'object') { - if (tool.result.path) { - return tool.result.path.split('/').pop(); - } - } - - if (tool.arguments) { - if (tool.arguments.path) { - return tool.arguments.path.split('/').pop(); - } - if (tool.arguments.target_path) { - return tool.arguments.target_path.split('/').pop(); - } - if (tool.arguments.query) { - return `"${tool.arguments.query}"`; - } - if (tool.arguments.command) { - return tool.arguments.command; - } - if (tool.arguments.seconds) { - return `${tool.arguments.seconds} 秒`; - } - } - return ''; - }, - - formatSearchTopic(filters) { - const mapping = { - 'general': '通用', - 'news': '新闻', - 'finance': '金融' - }; - const topic = (filters && filters.topic) ? String(filters.topic).toLowerCase() : 'general'; - return mapping[topic] || '通用'; - }, - - formatSearchTime(filters) { - if (!filters) { - return '未限定时间'; - } - if (filters.time_range) { - const mapping = { - 'day': '过去24小时', - 'week': '过去7天', - 'month': '过去30天', - 'year': '过去365天' - }; - return mapping[filters.time_range] || `相对范围:${filters.time_range}`; - } - if (typeof filters.days === 'number') { - return `过去${filters.days}天`; - } - if (filters.start_date && filters.end_date) { - return `${filters.start_date} 至 ${filters.end_date}`; - } - return '未限定时间'; - }, - - renderMarkdown(text, isStreaming = false) { - if (!text) return ''; - - if (typeof marked === 'undefined') { - return text; - } - - marked.setOptions({ - breaks: true, - gfm: true, - sanitize: false - }); - - if (!isStreaming) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - - const cacheKey = `${text.length}_${text.substring(0, 100)}`; - - if (this.markdownCache.has(cacheKey)) { - return this.markdownCache.get(cacheKey); - } - } - - let html = marked.parse(text); - html = this.wrapCodeBlocks(html, isStreaming); - - if (!isStreaming && text.length < 10000) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - this.markdownCache.set(`${text.length}_${text.substring(0, 100)}`, html); - if (this.markdownCache.size > 20) { - const firstKey = this.markdownCache.keys().next().value; - this.markdownCache.delete(firstKey); - } - } - - // 只在非流式状态处理(流式状态由renderLatexInRealtime处理) - if (!isStreaming) { - setTimeout(() => { - // 代码高亮 - if (typeof Prism !== 'undefined') { - const codeBlocks = document.querySelectorAll('.code-block-wrapper pre code:not([data-highlighted])'); - codeBlocks.forEach(block => { - try { - Prism.highlightElement(block); - block.setAttribute('data-highlighted', 'true'); - } catch (e) { - console.warn('代码高亮失败:', e); - } - }); - } - - // LaTeX最终渲染 - if (typeof renderMathInElement !== 'undefined') { - const elements = document.querySelectorAll('.text-output .text-content:not(.streaming-text)'); - elements.forEach(element => { - if (element.hasAttribute('data-math-rendered')) return; - - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - element.setAttribute('data-math-rendered', 'true'); - } catch (e) { - console.warn('LaTeX渲染失败:', e); - } - }); - } - }, 100); - } - - return html; - }, - // 实时LaTeX渲染(用于流式输出) - renderLatexInRealtime() { - if (typeof renderMathInElement === 'undefined') { - return; - } - - // 使用requestAnimationFrame优化性能 - if (this._latexRenderTimer) { - cancelAnimationFrame(this._latexRenderTimer); - } - - this._latexRenderTimer = requestAnimationFrame(() => { - const elements = document.querySelectorAll('.text-output .streaming-text'); - elements.forEach(element => { - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - } catch (e) { - // 忽略错误,继续渲染 - } - }); - }); - }, - // 用字符串替换包装代码块 - // 用字符串替换包装代码块 - 添加streaming参数 - wrapCodeBlocks(html, isStreaming = false) { - // 如果是流式输出,不包装代码块,保持原样 - if (isStreaming) { - return html; - } - - let counter = 0; - - // 匹配
...
- return html.replace(/
]*)>([\s\S]*?)<\/code><\/pre>/g, (match, attributes, content) => {
-                    // 提取语言
-                    const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
-                    const language = langMatch ? langMatch[1] : 'text';
-                    
-                    // 生成唯一ID
-                    const blockId = `code-${Date.now()}-${counter++}`;
-                    
-                    // 转义引号用于data属性
-                    const escapedContent = content
-                        .replace(/&/g, '&')
-                        .replace(//g, '>')
-                        .replace(/"/g, '"');
-                    
-                    // 构建新的HTML,保持code元素原样
-                    return `
-            
-
- ${language} - -
-
${content}
-
`; - }); - }, - - getLanguageClass(path) { - const ext = path.split('.').pop().toLowerCase(); - const langMap = { - 'py': 'language-python', - 'js': 'language-javascript', - 'html': 'language-html', - 'css': 'language-css', - 'json': 'language-json', - 'md': 'language-markdown', - 'txt': 'language-plain' - }; - return langMap[ext] || 'language-plain'; - }, - - scrollToBottom() { - setTimeout(() => { - const messagesArea = this.$refs.messagesArea; - if (messagesArea) { - // 标记为程序触发的滚动 - if (this._setScrollingFlag) { - this._setScrollingFlag(true); - } - - messagesArea.scrollTop = messagesArea.scrollHeight; - - // 滚动完成后重置标记 - setTimeout(() => { - if (this._setScrollingFlag) { - this._setScrollingFlag(false); - } - }, 100); - } - }, 50); - }, - - conditionalScrollToBottom() { - // 严格检查:只在明确允许时才滚动 - if (this.autoScrollEnabled === true && this.userScrolling === false) { - this.scrollToBottom(); - } - }, - - toggleScrollLock() { - const currentlyLocked = this.autoScrollEnabled && !this.userScrolling; - if (currentlyLocked) { - this.autoScrollEnabled = false; - this.userScrolling = true; - } else { - this.autoScrollEnabled = true; - this.userScrolling = false; - this.scrollToBottom(); - } - }, - - // 面板调整方法 - startResize(panel, event) { - this.isResizing = true; - this.resizingPanel = panel; - if (panel === 'right' && this.rightCollapsed) { - this.rightCollapsed = false; - if (this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - } - document.addEventListener('mousemove', this.handleResize); - document.addEventListener('mouseup', this.stopResize); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; - event.preventDefault(); - }, - - handleResize(event) { - if (!this.isResizing) return; - - const containerWidth = document.querySelector('.main-container').offsetWidth; - - if (this.resizingPanel === 'left') { - let newWidth = event.clientX - (this.sidebarCollapsed ? 60 : 300); - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.leftWidth = newWidth; - } else if (this.resizingPanel === 'right') { - let newWidth = containerWidth - event.clientX; - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.rightWidth = newWidth; - } else if (this.resizingPanel === 'conversation') { - // 对话侧边栏宽度调整 - let newWidth = event.clientX; - newWidth = Math.max(200, Math.min(newWidth, 400)); - // 这里可以动态调整对话侧边栏宽度,暂时不实现 - } - }, - - stopResize() { - this.isResizing = false; - this.resizingPanel = null; - document.removeEventListener('mousemove', this.handleResize); - document.removeEventListener('mouseup', this.stopResize); - document.body.style.userSelect = ''; - document.body.style.cursor = ''; - }, - - // 格式化token显示(修复NaN问题) - formatTokenCount(tokens) { - // 确保tokens是数字,防止NaN - const num = Number(tokens) || 0; - if (num < 1000) { - return num.toString(); - } else if (num < 1000000) { - return (num / 1000).toFixed(1) + 'K'; - } else { - return (num / 1000000).toFixed(1) + 'M'; - } - } - } - }); - - app.component('file-node', { - name: 'FileNode', - props: { - node: { - type: Object, - required: true - }, - level: { - type: Number, - default: 0 - }, - expandedFolders: { - type: Object, - required: true - } - }, - emits: ['toggle-folder', 'context-menu'], - computed: { - isExpanded() { - if (this.node.type !== 'folder') { - return false; - } - const value = this.expandedFolders[this.node.path]; - return value === undefined ? true : value; - }, - folderPadding() { - return { - paddingLeft: `${12 + this.level * 16}px` - }; - }, - filePadding() { - return { - paddingLeft: `${40 + this.level * 16}px` - }; - } - }, - methods: { - toggle() { - if (this.node.type === 'folder') { - this.$emit('toggle-folder', this.node.path); - } - } - }, - template: ` -
-
- -
- -
-
-
- 📄 - {{ node.name }} - {{ node.annotation }} -
-
- ` - }); - - app.mount('#app'); - console.log('Vue应用初始化完成'); -} - -window.addEventListener('load', () => { - bootstrapApp(); -}); diff --git a/static/backup_20251026_184346/claude-colors-simple.html b/static/backup_20251026_184346/claude-colors-simple.html deleted file mode 100644 index aebd82d..0000000 --- a/static/backup_20251026_184346/claude-colors-simple.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - Claude颜色展示 - 简化版 - - - -
-

Claude聊天界面颜色

- -

核心颜色

-
-
-
-
背景颜色
-
#eeece2
-
-
- -
-
-
-
字体颜色
-
#3d3929
-
-
- -
-
-
-
品牌主色
-
#da7756
-
-
- -
-
-
-
按钮颜色
-
#bd5d3a
-
-
- -

字体样式

-
- 字体栈:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif -

- 斜体样式展示 | 粗体样式展示 -
- -

界面元素

- - -

对话界面模拟

-
-
你好,Claude!今天天气怎么样?
-
你好!我无法获取实时天气信息,但你可以通过天气应用查看当地天气。
-
- -
- 这种配色方案创造出温暖、专业的对话环境,背景是米白色(#eeece2),文字是深棕色(#3d3929),给人舒适友好的感觉。 -
-
- - diff --git a/static/backup_20251026_184346/debug.html b/static/backup_20251026_184346/debug.html deleted file mode 100644 index 0a945c9..0000000 --- a/static/backup_20251026_184346/debug.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - AI Agent 调试监控 - - - - - -
-

🔧 AI Agent 调试监控

- - -
-

连接状态

-
Socket连接: {{ isConnected ? '已连接' : '未连接' }}
-
当前消息索引: {{ currentMessageIndex }}
-
消息总数: {{ messages.length }}
-
- - -
-

控制面板

- - - - -
- - -
-

WebSocket事件流 (最新 {{ events.length }} 条)

-
-
- {{ event.time }} - {{ event.type }} - : {{ JSON.stringify(event.data).slice(0, 200) }} -
-
-
- - -
-

当前消息Actions状态 (消息 #{{ currentMessageIndex }})

-
-
-
Action #{{ idx }}: {{ action.type }}
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
- 工具: {{ action.tool.name }}
- 状态: {{ action.tool.status }}
- ID: {{ action.tool.id }}
- 有结果: {{ !!action.tool.result }} -
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
-
-
- - -
-

最新消息原始数据

-
{{ JSON.stringify(messages[messages.length - 1], null, 2) }}
-
-
- - - - \ No newline at end of file diff --git a/static/backup_20251026_184346/index.html b/static/backup_20251026_184346/index.html deleted file mode 100644 index 22bac8f..0000000 --- a/static/backup_20251026_184346/index.html +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - AI Agent System - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-

正在连接服务器...

-

如果长时间无响应,请刷新页面

-
- - - -
- - -
-
- - - - - diff --git a/static/backup_20251026_184346/login.html b/static/backup_20251026_184346/login.html deleted file mode 100644 index cff0db6..0000000 --- a/static/backup_20251026_184346/login.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 登录 - AI Agent - - - - - - - diff --git a/static/backup_20251026_184346/register.html b/static/backup_20251026_184346/register.html deleted file mode 100644 index 0943626..0000000 --- a/static/backup_20251026_184346/register.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - 注册 - AI Agent - - - -
-

创建账号

-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - - diff --git a/static/backup_20251026_184346/style.css b/static/backup_20251026_184346/style.css deleted file mode 100644 index 8049abf..0000000 --- a/static/backup_20251026_184346/style.css +++ /dev/null @@ -1,2049 +0,0 @@ -/* static/style-enhanced.css - 增强版,包含对话历史管理功能 */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --claude-bg: #eeece2; - --claude-panel: rgba(255, 255, 255, 0.82); - --claude-sidebar: rgba(255, 255, 255, 0.68); - --claude-border: rgba(118, 103, 84, 0.25); - --claude-text: #3d3929; - --claude-text-secondary: #7f7766; - --claude-muted: rgba(121, 109, 94, 0.4); - --claude-accent: #da7756; - --claude-accent-strong: #bd5d3a; - --claude-highlight: rgba(218, 119, 86, 0.14); - --claude-button-hover: #c76541; - --claude-button-active: #a95331; - --claude-shadow: 0 14px 36px rgba(61, 57, 41, 0.12); - --claude-success: #76b086; - --claude-warning: #d99845; -} - -body { - font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - background: var(--claude-bg); - color: var(--claude-text); - height: 100vh; - overflow: hidden; - -webkit-font-smoothing: antialiased; -} - -/* 顶部状态栏 */ -.header { - background: var(--claude-panel); - backdrop-filter: blur(24px); - border-bottom: 1px solid var(--claude-border); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 56px; - z-index: 100; - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.05); -} - -.header-left { - display: flex; - align-items: center; - gap: 20px; -} - -.logo { - font-size: 18px; - font-weight: 600; - color: var(--claude-text); - letter-spacing: 0.02em; -} - -.project-path { - color: var(--claude-text-secondary); - font-size: 14px; -} - -.header-right { - display: flex; - align-items: center; - gap: 20px; -} - -.thinking-mode { - background: var(--claude-accent); - color: #fff8f2; - padding: 5px 14px; - border-radius: 980px; - font-size: 13px; - font-weight: 500; - letter-spacing: 0.04em; -} - -.connection-status { - font-size: 13px; - color: var(--claude-text-secondary); - display: flex; - align-items: center; - gap: 6px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--claude-muted); - transition: all 0.3s ease; -} - -.status-dot.active { - background: var(--claude-success); - box-shadow: 0 0 8px rgba(118, 176, 134, 0.45); -} - -/* 主容器 */ -.main-container { - display: flex; - height: calc(100vh - 56px); - background: var(--claude-bg); - position: relative; - align-items: stretch; -} - -/* ========================================= */ -/* 新增:对话历史侧边栏样式 */ -/* ========================================= */ - -.conversation-sidebar { - width: 280px; - background: var(--claude-sidebar); - border-right: none; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 50; - backdrop-filter: blur(12px); - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; - border-bottom: 1px solid var(--claude-border); -} - -.conversation-collapsed-spacer { - flex: 1 1 auto; - background: transparent; -} - -.conversation-sidebar.collapsed { - width: 50px; - overflow: hidden; - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; -} - -.conversation-sidebar.collapsed .conversation-header { - justify-content: center; -} - -.conversation-sidebar.collapsed .conversation-header .toggle-sidebar-btn { - margin-left: 0; -} - -.conversation-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(118, 103, 84, 0.12); - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - position: sticky; - top: 0; - z-index: 60; - min-height: 68px; -} - -.new-conversation-btn { - flex: 1; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border: 1px solid rgba(189, 93, 58, 0.4); - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 6px; - box-shadow: 0 6px 14px rgba(189, 93, 58, 0.18); -} - -.new-conversation-btn:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); - transform: translateY(-1px); -} - -.btn-icon { - font-size: 16px; - font-weight: bold; -} - -.btn-text { - white-space: nowrap; -} - -.toggle-sidebar-btn { - background: rgba(255, 255, 255, 0.7); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.18); - padding: 6px 8px; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; - min-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.toggle-sidebar-btn:hover { - background: rgba(255, 255, 255, 0.9); -} - -.conversation-search { - padding: 12px; - background: transparent; - border-bottom: 1px solid var(--claude-border); -} - -.search-input { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--claude-border); - border-radius: 6px; - font-size: 13px; - background: rgba(255, 255, 255, 0.55); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.search-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.85); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.18); -} - -.conversation-list { - flex: 1; - overflow-y: auto; - padding: 8px 0; - background: transparent; -} - -.loading-conversations, -.no-conversations { - text-align: center; - color: var(--claude-text-secondary); - padding: 30px 15px; - font-size: 13px; -} - -.conversation-item { - padding: 12px 16px; - margin: 2px 8px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid transparent; - position: relative; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.04); -} - -.conversation-item:hover { - background: rgba(255, 255, 255, 0.85); - border-color: rgba(218, 119, 86, 0.35); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.conversation-item.active { - background: rgba(218, 119, 86, 0.18); - border-color: var(--claude-accent); - box-shadow: 0 10px 28px rgba(189, 93, 58, 0.18); -} - -.conversation-title { - font-size: 14px; - font-weight: 500; - color: var(--claude-text); - margin-bottom: 6px; - word-wrap: break-word; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.3; -} - -.conversation-meta { - font-size: 11px; - color: var(--claude-text-secondary); - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2px; -} - -.conversation-time { - flex-shrink: 0; -} - -.conversation-counts { - text-align: right; - white-space: nowrap; - font-size: 10px; -} - -.conversation-actions { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); - opacity: 0; - transition: opacity 0.2s ease; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; -} - -.conversation-item:hover .conversation-actions { - opacity: 1; -} - -.conversation-action-btn { - border: none; - border-radius: 4px; - font-size: 13px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - color: white; -} - -.conversation-action-btn.copy-btn { - background: var(--claude-accent); -} - -.conversation-action-btn.copy-btn:hover { - background: var(--claude-button-hover); - transform: translateY(-1px); -} - -.conversation-action-btn.delete-btn { - background: #d85a42; -} - -.conversation-action-btn.delete-btn:hover { - background: #bf422b; - transform: translateY(-1px); -} - -.load-more { - padding: 12px 16px; - text-align: center; -} - -.load-more-btn { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent); - border: 1px solid rgba(218, 119, 86, 0.35); - padding: 6px 14px; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; -} - -.load-more-btn:hover:not(:disabled) { - background: var(--claude-accent); - color: #fffdf8; -} - -.load-more-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* 当前对话信息栏 */ -.current-conversation-info { - background: var(--claude-panel); - backdrop-filter: blur(18px); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -.conversation-title-display { - font-weight: 500; - color: var(--claude-text); -} - -.conversation-message-count { - font-size: 12px; -} - -/* 拖拽手柄 */ -.resize-handle { - width: 4px; - background: var(--claude-sidebar); - cursor: col-resize; - position: relative; - transition: background 0.2s; - flex-shrink: 0; -} - -.resize-handle:hover { - background: rgba(218, 119, 86, 0.22); -} - -/* 侧边栏 */ -.sidebar { - background: rgba(255, 255, 255, 0.75); - overflow-y: auto; - flex-shrink: 0; - border-left: none; -} - -.sidebar-header { - padding: 23px; - border-bottom: 1px solid var(--claude-border); - position: sticky; - top: 0; - background: rgba(255, 255, 255, 0.85); - z-index: 10; - backdrop-filter: blur(16px); - display: flex; - align-items: center; - gap: 10px; -} - -.sidebar-header h3 { - font-size: 15px; - font-weight: 600; - color: var(--claude-text); - margin: 0; -} - -.sidebar-view-toggle { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(118, 103, 84, 0.3); - background: rgba(255, 255, 255, 0.85); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.2s ease; - color: var(--claude-text); -} - -.sidebar-view-toggle:hover { - background: rgba(255, 255, 255, 0.95); -} - -.sidebar.right-sidebar.collapsed { - width: 0 !important; - min-width: 0 !important; - border-left: none; - overflow: hidden; -} - -.sidebar.right-sidebar.collapsed .sidebar-header, -.sidebar.right-sidebar.collapsed .focused-files { - display: none; -} - -/* 文件树 */ -.file-tree { - padding: 12px 0 20px; - color: var(--claude-text); -} - -.todo-panel { - padding: 16px 20px 24px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.todo-empty { - font-size: 14px; - color: var(--claude-text-secondary); - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.8); - border: 1px dashed var(--claude-border); - text-align: center; -} - -.todo-task { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.18); - border-radius: 10px; - font-size: 13px; - color: var(--claude-text); - background: rgba(255, 255, 255, 0.9); -} - -.todo-task.done { - background: rgba(92, 190, 125, 0.08); - border-color: rgba(92, 190, 125, 0.3); -} - -.todo-task-title { - flex: 1; -} - -.todo-task-status { - font-weight: 600; - font-size: 12px; - margin-left: 12px; -} - -.todo-instruction { - font-size: 12px; - color: var(--claude-text-secondary); - margin-top: 8px; -} - -.file-node-wrapper { - font-size: 14px; - color: var(--claude-text); - font-family: inherit; -} - -.folder-header { - width: 100%; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s ease, transform 0.2s ease; - text-align: left; - font-family: inherit; -} - -.folder-header:hover { - background: rgba(218, 119, 86, 0.12); - transform: translateX(2px); -} - -.folder-arrow { - width: 12px; - text-align: center; - color: var(--claude-text-secondary); - font-size: 12px; -} - -.folder-icon, -.file-icon { - width: 18px; - text-align: center; -} - -.folder-name, -.file-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: inherit; -} - - -.folder-children { - margin-left: 14px; - padding-left: 6px; - border-left: 1px dashed rgba(0, 0, 0, 0.08); -} - -.folder-children .file-node-wrapper { - margin-left: 0; -} - -.file-node.file-leaf { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border-radius: 8px; - font-family: inherit; -} - -.file-node.file-leaf:hover { - background: rgba(218, 119, 86, 0.1); -} - -.file-node .annotation { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -.context-menu { - position: fixed; - background: #ffffff; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.15); - z-index: 3000; - min-width: 180px; - padding: 6px 0; - backdrop-filter: blur(8px); -} - -.context-menu button { - width: 100%; - padding: 8px 18px; - background: transparent; - border: none; - text-align: left; - font-size: 13px; - color: #1f2933; - cursor: pointer; - transition: background 0.15s ease; -} - -.context-menu button:hover { - background: rgba(59, 130, 246, 0.12); -} - -.context-menu button:disabled { - color: #9ca3af; - cursor: not-allowed; - background: transparent; -} - -/* 聊天容器 */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - background: rgba(255, 255, 255, 0.78); - min-width: 0; - min-height: 0; - position: relative; - backdrop-filter: blur(6px); -} - -/* 消息区域 */ -.messages-area { - flex: 1; - overflow-y: auto; - padding: 24px; - min-height: 0; -} - -.scroll-lock-toggle { - position: absolute; - right: 28px; - bottom: 200px; - z-index: 25; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.scroll-lock-btn { - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.92); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); -} - -.scroll-lock-btn:hover { - transform: translateY(-2px); - box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); -} - -.scroll-lock-toggle.locked .scroll-lock-btn { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); -} - -.scroll-lock-btn svg { - width: 18px; - height: 18px; - stroke: var(--claude-text); - stroke-width: 1.8; - fill: none; - transition: stroke 0.2s ease; -} - -.scroll-lock-toggle.locked .scroll-lock-btn svg { - stroke: var(--claude-accent); -} - -/* 滚动条 */ -.messages-area::-webkit-scrollbar, -.sidebar::-webkit-scrollbar, -.conversation-list::-webkit-scrollbar { - width: 8px; -} - -.messages-area::-webkit-scrollbar-track, -.sidebar::-webkit-scrollbar-track, -.conversation-list::-webkit-scrollbar-track { - background: transparent; -} - -.messages-area::-webkit-scrollbar-thumb, -.sidebar::-webkit-scrollbar-thumb, -.conversation-list::-webkit-scrollbar-thumb { - background: rgba(121, 109, 94, 0.4); - border-radius: 8px; -} - -/* 消息块 */ -.message-block { - margin-bottom: 24px; -} - -/* 用户消息 */ -.user-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 8px; - letter-spacing: 0.02em; -} - -.user-message .message-text { - background: rgba(255, 255, 255, 0.85); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - white-space: pre-wrap; -} - -/* AI消息 */ -.assistant-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 12px; - letter-spacing: 0.02em; -} - -.assistant-message .message-text { - background: rgba(218, 119, 86, 0.12); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - border-left: 4px solid var(--claude-accent); - white-space: pre-wrap; -} - -/* Action项入场动画 */ -@keyframes slideInFade { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes quickFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.action-item { - animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 0ms; -} - -/* 流式内容:立即显示 */ -.action-item.streaming-content, -.action-item.immediate-show { - animation: quickFadeIn 0.2s ease-out both; - animation-delay: 0ms !important; -} - -/* 已完成的工具:统一延迟看动画 */ -.action-item.completed-tool { - animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 100ms; -} - -/* 文本输出 - 无缩进,与思考块对齐 */ -.text-output { - margin: 16px 0; - color: var(--claude-text); - font-size: 15px; - line-height: 1.7; -} - -.text-output .text-content { - padding: 0 20px 0 15px; /* 左边56px与思考块对齐 */ -} - -.text-output .text-content p { - margin-bottom: 12px; -} - -.text-output .text-content p:last-child { - margin-bottom: 0; -} - -.append-block { - margin: 12px 0; - padding: 12px 16px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); - display: flex; - flex-direction: column; - gap: 8px; -} - -.append-block.append-error { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); - box-shadow: inset 0 0 0 1px rgba(216, 90, 66, 0.08); -} - -.append-header { - display: flex; - align-items: center; - gap: 10px; - font-weight: 600; - color: var(--claude-text); - font-size: 15px; -} - -.append-block.append-error .append-header { - color: #b0432a; -} - -.append-icon { - font-size: 18px; -} - -.append-summary { - flex: 1; -} - -.append-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-meta-item { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.append-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder { - margin: 12px 0; -} - -.modify-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.modify-placeholder-content .modify-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder-content .modify-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-placeholder { - margin: 12px 0; -} - - -.append-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.append-placeholder-content .append-warning { - margin-top: 4px; -} - -.append-placeholder.append-error .append-placeholder-content { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); -} -/* Markdown表格样式 */ -.text-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; -} - -.text-content table th, -.text-content table td { - border: 1px solid #e0e0e0; - padding: 10px 14px; - text-align: left; -} - -.text-content table th { - background: rgba(218, 119, 86, 0.08); - font-weight: 600; - color: var(--claude-text); -} - -.text-content table tr:hover { - background: #fafafa; -} - -/* 代码块包装器 - 防止跳动 */ -.code-block-wrapper { - border: 2px solid rgba(118, 103, 84, 0.25); - border-radius: 12px; - overflow: hidden; - margin: 16px 0; - background: rgba(255, 255, 255, 0.78); - min-height: 80px; /* 添加最小高度防止跳动 */ - contain: layout; /* CSS containment 优化渲染 */ -} - -/* 代码块头部 */ -.code-block-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(118, 103, 84, 0.25); -} - -.code-language { - color: var(--claude-text-secondary); - font-size: 13px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-weight: 500; -} - -.copy-code-btn { - background: transparent; - color: var(--claude-text-secondary); - border: 1px solid rgba(121, 109, 94, 0.35); - padding: 6px 10px; - border-radius: 6px; - font-size: 16px; - cursor: pointer; - transition: all 0.2s ease; - line-height: 1; -} - -.copy-code-btn:hover { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent-strong); -} - -.copy-code-btn.copied { - background: var(--claude-success); - border-color: var(--claude-success); - color: #f6fff8; -} - -/* 代码块内容区 */ -.code-block-wrapper pre { - background: #ffffff !important; - padding: 16px !important; - margin: 0 !important; - border-radius: 0 !important; - border: none !important; -} - -.code-block-wrapper pre code { - background: transparent !important; - padding: 0 !important; - color: #000000; -} -/* 流式文本 */ -.streaming-text { - display: block; -} - -.cursor-blink { - animation: blink 1s steps(1) infinite; - color: var(--claude-accent); - font-weight: normal; -} - -/* 思考内容样式 - 保留换行 */ -.thinking-content { - white-space: pre-wrap; /* 保留换行和空格 */ - word-wrap: break-word; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.6; - color: var(--claude-text-secondary); -} - -/* 可折叠块 */ -.collapsible-block { - background: rgba(255, 255, 255, 0.78); - border-radius: 12px; - margin-bottom: 12px; - overflow: hidden; - border: 1px solid var(--claude-border); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.collapsible-block:hover { - box-shadow: 0 14px 28px rgba(61, 57, 41, 0.1); -} - -.collapsible-header { - padding: 14px 20px; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - user-select: none; - background: rgba(255, 255, 255, 0.72); - transition: background-color 0.2s ease; - position: relative; -} - -.collapsible-header:hover { - background: rgba(218, 119, 86, 0.07); -} - -/* 箭头 */ -.arrow { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: var(--claude-text-secondary); -} - -.arrow::before { - content: '›'; - font-size: 18px; -} - -.collapsible-block.expanded .arrow { - transform: rotate(90deg); -} - -/* 状态图标 */ -.status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; -} - -/* 内容区域 */ -.collapsible-content { - max-height: 0; - overflow: hidden; - opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.collapsible-block.expanded .collapsible-content { - max-height: 600px; - overflow-y: auto; - opacity: 1; -} - -.content-inner { - padding: 20px 20px 20px 56px; - color: var(--claude-text-secondary); - font-size: 14px; - line-height: 1.6; -} - -/* 工具动画 */ -@keyframes brain-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.15); } -} - -.thinking-icon { - animation: brain-pulse 1.5s ease-in-out infinite; -} - -@keyframes file-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -.file-animation { - animation: file-bounce 1.5s ease-in-out infinite; -} - -@keyframes scan-effect { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -.read-animation { - animation: scan-effect 1.5s ease-in-out infinite; -} - -@keyframes search-rotate { - 0% { transform: rotate(-10deg); } - 50% { transform: rotate(10deg); } - 100% { transform: rotate(-10deg); } -} - -.search-animation { - animation: search-rotate 1s ease-in-out infinite; -} - -@keyframes code-breathe { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -.code-animation { - animation: code-breathe 2s ease-in-out infinite; -} - -.terminal-animation::after { - content: '_'; - animation: blink 1s steps(1) infinite; - margin-left: 2px; -} - -@keyframes memory-fade { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.95); } -} - -.memory-animation { - animation: memory-fade 2s ease-in-out infinite; -} - -@keyframes focus-glow { - 0%, 100% { filter: brightness(1); } - 50% { filter: brightness(1.3); } -} - -.focus-animation { - animation: focus-glow 1.5s ease-in-out infinite; -} - -/* 进度条 */ -.progress-indicator { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--claude-accent); - animation: progress 2s ease-in-out infinite; - opacity: 0; - transition: opacity 0.3s ease; -} - -.collapsible-block.processing .progress-indicator { - opacity: 1; -} - -@keyframes progress { - 0% { width: 0%; left: 0%; } - 50% { width: 40%; left: 30%; } - 100% { width: 0%; left: 100%; } -} - -/* 状态文字 */ -.status-text { - font-size: 14px; - color: var(--claude-text); - font-weight: 500; -} - -.processing .status-text { - color: var(--claude-text-secondary); -} - -/* 完成勾号 */ -.checkmark { - color: var(--claude-success); - font-weight: 600; - font-size: 16px; -} - -/* 工具描述 */ -.tool-desc { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -/* 输入区域 */ -.token-wrapper { - flex-shrink: 0; -} - -.input-area { - background: rgba(255, 255, 255, 0.82); - border-top: 1px solid var(--claude-border); - padding: 20px; - backdrop-filter: blur(12px); - flex-shrink: 0; -} - -.input-wrapper { - display: flex; - flex-direction: column; - gap: 12px; -} - -.message-input { - width: 100%; - padding: 14px 16px; - border: 1px solid var(--claude-border); - border-radius: 12px; - font-size: 15px; - resize: none; - font-family: inherit; - background: rgba(255, 255, 255, 0.75); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.message-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.2); -} - -.input-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - flex-wrap: wrap; -} - -/* 按钮 */ -.btn { - padding: 10px 24px; - border: none; - border-radius: 980px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - letter-spacing: 0.03em; -} - -.send-btn { - background: var(--claude-accent); - color: #fffdf8; - box-shadow: 0 12px 28px rgba(189, 93, 58, 0.25); -} - -.send-btn:hover:not(:disabled) { - background: var(--claude-button-hover); - transform: scale(1.02); - box-shadow: 0 14px 32px rgba(189, 93, 58, 0.3); -} - -.send-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.stop-btn { - background: #d85a42; - color: #fffaf5; -} - -.stop-btn:hover { - background: #bf422b; -} - -.settings-dropdown { - position: relative; - display: flex; - align-items: center; -} - -.tool-dropdown { - position: relative; - display: flex; - align-items: center; - margin-right: auto; -} - -.upload-control { - display: flex; - align-items: center; -} - -.file-input-hidden { - display: none; -} - -.upload-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.upload-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.upload-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.tool-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.tool-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.tool-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.settings-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.settings-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* 适应折叠屏/矮屏幕,保证输入区完整可见 */ -@media (max-height: 900px) { - .messages-area { - padding: 16px 18px; - } - .token-wrapper { - margin-bottom: 8px; - } - .input-area { - padding: 14px; - } - .message-input { - padding: 12px 14px; - min-height: 120px; - } - .btn { - padding: 8px 18px; - } -} - -.settings-menu { - position: absolute; - right: 0; - bottom: calc(100% + 12px); - background: rgba(255, 255, 255, 0.96); - border: 1px solid var(--claude-border); - border-radius: 12px; - box-shadow: var(--claude-shadow); - 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: rgba(255, 255, 255, 0.96) 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: 320px; - max-width: 380px; - padding: 14px 18px; -} - -.settings-menu.tool-menu::before { - left: 32px; - right: auto; -} - -.tool-menu .tool-menu-status, -.tool-menu .tool-menu-empty { - font-size: 13px; - color: rgba(61, 57, 41, 0.78); - text-align: left; -} - -.tool-menu .tool-menu-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.tool-menu .tool-category-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 10px; - background: rgba(255, 255, 255, 0.88); - font-size: 13px; -} - -.tool-menu .tool-category-item.disabled { - opacity: 0.55; -} - -.tool-menu .tool-category-label { - flex: 1; - white-space: nowrap; - font-size: 13px; - font-weight: 500; - color: var(--claude-text); - display: inline-flex; - align-items: center; - gap: 6px; -} - -.tool-category-icon { - font-size: 16px; -} - -.tool-menu .tool-category-toggle { - width: auto !important; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 18px; - text-align: center; - white-space: nowrap; -} - -.menu-btn { - width: 100%; - padding: 8px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 8px; - font-size: 13px; - font-weight: 500; - text-align: left; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - cursor: pointer; - transition: all 0.2s ease; -} - -.menu-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); - transform: translateY(-1px); -} - -.menu-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.menu-btn.compress-entry { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-success); -} - -.menu-btn.compress-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.menu-btn.clear-entry { - background: rgba(255, 255, 255, 0.78); - color: #bf422b; -} - -.menu-btn.clear-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.settings-menu-enter-active, -.settings-menu-leave-active { - transition: opacity 0.18s ease, transform 0.18s ease; -} - -.settings-menu-enter-from, -.settings-menu-leave-to { - opacity: 0; - transform: translateY(6px); -} - -/* 聚焦文件 */ -.focused-files { - padding: 16px; -} - -.no-files { - text-align: center; - color: var(--claude-text-secondary); - padding: 60px 20px; - font-size: 14px; -} - -.file-tabs { - display: flex; - flex-direction: column; - gap: 12px; -} - -.file-tab { - border: 1px solid var(--claude-border); - border-radius: 12px; - overflow: hidden; - background: rgba(255, 255, 255, 0.75); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.tab-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-bottom: 1px solid var(--claude-border); -} - -.file-name { - font-weight: 500; - color: var(--claude-text); -} - -.file-size { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.file-content { - max-height: 320px; - overflow-y: auto; - background: #1e1e1e; -} - -.file-content pre { - margin: 0; - padding: 16px; -} - -.file-content code { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - color: #aed581; -} - -/* 系统消息 */ -.system-message { - text-align: center; - color: var(--claude-text-secondary); - font-size: 13px; - margin: 20px 0; - padding: 10px; - background: rgba(255, 255, 255, 0.7); - border-radius: 12px; - border: 1px solid var(--claude-border); -} - -/* Markdown样式 */ -.text-content h1, -.text-content h2, -.text-content h3 { - margin-top: 20px; - margin-bottom: 12px; - font-weight: 600; - color: var(--claude-text); -} - -.text-content p { - margin-bottom: 12px; -} - -.text-content pre { - background: rgba(255, 255, 255, 0.78); - padding: 16px; - border-radius: 12px; - overflow-x: auto; - margin: 16px 0; - border: 1px solid var(--claude-border); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.text-content code { - background: rgba(218, 119, 86, 0.1); - padding: 2px 6px; - border-radius: 4px; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--claude-accent-strong); -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} - - -/* ========================================= */ -/* 响应式设计 */ -/* ========================================= */ - -@media (max-width: 1200px) { - .conversation-sidebar { - width: 260px; - } - - .left-sidebar, - .right-sidebar { - width: 300px !important; - } -} - -@media (max-width: 768px) { - .conversation-sidebar { - position: absolute; - left: 0; - top: 0; - height: 100%; - z-index: 1000; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); - transform: translateX(-100%); - } - - .conversation-sidebar:not(.collapsed) { - transform: translateX(0); - } - - .left-sidebar, - .right-sidebar { - display: none; - } - - .resize-handle { - display: none; - } -} -.conversation-stats { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--claude-text-secondary); -} - -.token-count { - color: var(--claude-accent); - font-weight: 500; -} -/* ========================================= */ -/* Token 统计面板样式(无缝一体版)*/ -/* ========================================= */ - -/* Token区域包装器 */ -.token-wrapper { - position: relative; - z-index: 5; - margin-bottom: 0; -} - -/* 当前对话信息栏 - 移除底部边框 */ -.current-conversation-info { - position: relative; - z-index: 10; - background: var(--claude-panel); - backdrop-filter: blur(18px); - border-bottom: none; /* 移除边框,让它和下面的面板融为一体 */ - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - border-radius: 0; /* 顶部保持直角 */ - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -/* Token面板 - 与标题栏完全一体,底部圆角 */ -.token-display-panel { - background: var(--claude-panel); - backdrop-filter: blur(18px); - border: none; - border-radius: 0 0 16px 16px; - box-shadow: 0 8px 18px rgba(189, 93, 58, 0.12); - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); - width: 100%; - margin: 0; - padding: 0; -} - -/* 展开状态 */ -.token-display-panel:not(.collapsed) { - height: 80px; - opacity: 1; -} - -/* 收起状态 */ -.token-display-panel.collapsed { - height: 0; - opacity: 0; - border: none; - box-shadow: none; -} - -.token-panel-content { - padding: 16px 24px; - height: 100%; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.token-display-panel.collapsed .token-panel-content { - opacity: 0; - pointer-events: none; -} - -.token-stats { - display: flex; - gap: 32px; - align-items: center; - justify-content: center; - font-size: 13px; - height: 100%; -} - -.token-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; - min-width: 80px; -} - -.token-label { - color: var(--claude-text-secondary); - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.token-value { - color: var(--claude-text); - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; -} - -.token-value.current { - color: var(--claude-accent); - font-size: 20px; -} -.token-value.input { color: var(--claude-success); } -.token-value.output { color: var(--claude-warning); } - -.token-separator { - width: 1px; - height: 35px; - background: linear-gradient(to bottom, - transparent, - rgba(218, 119, 86, 0.25) 20%, - rgba(218, 119, 86, 0.25) 80%, - transparent - ); - margin: 0 8px; -} - -/* 切换按钮 - 独立定位 */ -.token-toggle-btn { - position: absolute; - right: 24px; - bottom: -18px; /* 相对于wrapper底部 */ - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(218, 119, 86, 0.3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 15; - font-size: 14px; - font-weight: bold; -} - -/* 展开状态 */ -.token-toggle-btn:not(.collapsed) { - background: linear-gradient(135deg, #ffffff 0%, rgba(255, 248, 242, 0.9) 100%); - color: var(--claude-accent); - box-shadow: 0 3px 10px rgba(189, 93, 58, 0.18); -} - -/* 收起状态 - 在标题栏下方露出一半 */ -.token-toggle-btn.collapsed { - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border-color: rgba(255, 248, 242, 0.55); - box-shadow: 0 3px 11px rgba(189, 93, 58, 0.22); -} - -.token-toggle-btn:hover { - transform: scale(1.05); - box-shadow: 0 5px 16px rgba(189, 93, 58, 0.26); -} - -.token-toggle-btn.collapsed:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); -} - -.token-toggle-btn:active { - transform: scale(1.02); -} - -/* 箭头样式 - 移除浮动动画 */ -.token-toggle-btn span { - transition: all 0.3s ease; - display: inline-block; -} - -/* 移除动画效果 */ -/* .token-toggle-btn:not(.collapsed) span { - animation: arrowBounceUp 2s ease-in-out infinite; -} - -.token-toggle-btn.collapsed span { - animation: arrowBounceDown 2s ease-in-out infinite; -} */ - -/* 保留动画定义,但不使用 */ -@keyframes arrowBounceUp { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -@keyframes arrowBounceDown { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(3px); } -} - -/* 响应式调整 */ -@media (max-width: 768px) { - .token-stats { - gap: 16px; - } - - .token-item { - min-width: 60px; - } - - .token-value { - font-size: 15px; - } - - .token-value.current { - font-size: 17px; - } - - .token-label { - font-size: 10px; - } - - .token-toggle-btn { - width: 32px; - height: 32px; - font-size: 12px; - right: 16px; - } -} -/* Markdown列表样式 - 修复偏左问题 */ -.text-content ul, -.text-content ol { - margin-left: 24px; /* 增加左边距 */ - padding-left: 0; - margin-bottom: 12px; -} - -.text-content ul { - list-style-type: disc; /* 实心圆点 */ -} - -.text-content ul ul { - list-style-type: circle; /* 空心圆点 */ - margin-top: 6px; -} - -.text-content ol { - list-style-type: decimal; /* 数字列表 */ -} - -.text-content li { - margin-bottom: 6px; - line-height: 1.6; -} - -/* 搜索结果展示 */ -.search-meta { - font-size: 14px; - color: var(--claude-text-secondary); - line-height: 1.6; - margin-bottom: 12px; - word-break: break-word; -} - -.search-result-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.search-result-item { - padding: 10px 12px; - border: 1px solid rgba(118, 103, 84, 0.16); - border-radius: 8px; - background: rgba(255, 255, 255, 0.65); - word-break: break-word; -} - -.search-result-title { - font-weight: 600; - margin-bottom: 6px; - color: var(--claude-text); -} - -.search-result-url a { - color: var(--claude-accent-strong); - text-decoration: none; - word-break: break-all; -} - -.search-result-url a:hover { - text-decoration: underline; -} - -.search-empty { - font-size: 13px; - color: var(--claude-text-secondary); - font-style: italic; -} diff --git a/static/backup_20251026_184346/terminal.html b/static/backup_20251026_184346/terminal.html deleted file mode 100644 index f0a31f0..0000000 --- a/static/backup_20251026_184346/terminal.html +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - AI Terminal Monitor - 实时终端查看器 - - - - - - - - -
-

- 🖥️ AI Terminal Monitor - - 连接中... -

-
- - -
-
- - 等待终端会话... -
-
- - -
- -
-
-
- 无活动会话 - - -
-
-
-
-
-
-
-
-
- - - -
- - -
-
-
- - 延迟: 0ms -
-
- 📡 - 0 KB/s -
-
- 🕐 - -
-
-
- AI Agent Terminal Monitor v1.0 -
-
- - -
- - - - - - - - - - diff --git a/sub_agent/core/tool_config.py b/sub_agent/core/tool_config.py index 48a9d7b..af4c746 100644 --- a/sub_agent/core/tool_config.py +++ b/sub_agent/core/tool_config.py @@ -56,8 +56,4 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = { label="待办事项", tools=["todo_create", "todo_update_task", "todo_finish", "todo_finish_confirm"], ), - "sub_agent": ToolCategory( - label="子智能体", - tools=["create_sub_agent", "wait_sub_agent"], - ), } diff --git a/sub_agent/prompts/sub_agent_system.txt b/sub_agent/prompts/sub_agent_system.txt index 67f6cbc..3e6dfdc 100644 --- a/sub_agent/prompts/sub_agent_system.txt +++ b/sub_agent/prompts/sub_agent_system.txt @@ -1,13 +1,20 @@ -你是子智能体 {agent_id}(任务编号 {task_id})。你与主智能体完全隔离,必须遵守以下规则: +你是子智能体 {agent_id}(任务编号 {task_id}),与主智能体完全隔离,唯一的输入来源就是本任务描述与 `references/` 中的只读资料。你当前的工作区结构如下: -1. 只能在工作目录 `{workspace}` 内读写文件;该目录已准备好可写的 `deliverables/` 与只读的 `references/`。 -2. 所有交付成果必须放入 `{deliverables}`,并维护一份 `result.md`,用中文或中英双语说明完成情况、交付列表、后续建议。 -3. 可在 `references/` 内查阅主智能体提供的快照,但不得修改这些文件。 -4. 不得调用任何记忆或待办事项相关工具。 -5. 只有在所有交付准备完毕且 `result.md` 填写完成后,才能调用 `finish_sub_agent` 工具结束任务。 +- 工作区:`{workspace}` —— 只能在此路径内创建/修改文件。 +- 参考目录:`{references}` —— 主智能体提供的上下文快照,严格只读,用于查阅需求、接口或示例实现。 +- 交付目录:`{deliverables}` —— 必须放置交付成果与 `result.md`;主系统会把整个交付目录复制到 `{target_project_dir}` 下对应的 `*_deliverables` 文件夹。 + +请严格遵守以下原则: +1. **明确职责**:根据“任务摘要/详细任务”在工作区内独立完成实现,无权回问主智能体或等待额外说明;若信息不足,应在 `result.md` 中说明局限。 +2. **参考使用**:需要引用 `references/` 中的文件时,复制或转述其必要片段,不可修改原文件;引用时注明来源,避免与交付混淆。 +3. **交付规范**:`deliverables/` 至少包含: + - `result.md`:总结完成度、关键实现、测试/验收情况、未解决问题与后续建议(中文或中英双语)。 + - 任务成果文件:遵循约定的目录/命名,可附 README 或使用说明,确保主智能体复制后即可查阅。 +4. **禁止事项**:不得调用记忆/待办类工具,不得越出工作区,也不要尝试联系主智能体;所有说明请写入正常回复或 `result.md`。 +5. **流程要求**:先复盘任务、列出执行计划,再分步骤完成并自检。需要运行脚本或命令时务必记录要点,方便主智能体验收。 +6. **完成条件**:确认所有交付文件就绪且 `result.md` 信息完整后,才能调用 `finish_sub_agent`,并在 `reason` 中概括完成情况与下一步建议。 任务摘要:{summary} 详细任务:{task} -交付目标目录:{target_project_dir} -请分阶段规划、执行、验证。必要时使用终端运行命令并记录关键日志。遇到阻塞或需要澄清时,先在消息中说明,再等待主智能体指令。 +请在隔离环境中独立完成任务,遇到阻塞时在消息与 `result.md` 中说明原因及建议,而不是等待主智能体回应。 diff --git a/sub_agent/prompts/todo_guidelines.txt b/sub_agent/prompts/todo_guidelines.txt index 00654a1..ea53888 100644 --- a/sub_agent/prompts/todo_guidelines.txt +++ b/sub_agent/prompts/todo_guidelines.txt @@ -1,168 +1,35 @@ -# 待办事项系统使用指南(简化版) +# 子智能体待办事项速记 -待办事项就像你的任务清单,帮你把复杂的工作拆成小步骤。 +你无法向主智能体提问,只能依靠待办清单来规划和追踪工作。清单越精炼、越可执行,你就越能掌控节奏。 -## 什么时候用 +## 何时创建 +- 任务包含 2 步以上且每步需要独立确认。 +- 需要同时操作多个文件/工具,容易遗漏。 +- 需要自查进度或复盘交付内容。 -以下情况建议创建待办清单: -- ✅ 任务需要2步以上 -- ✅ 要操作多个文件 -- ✅ 需要用多个工具配合 -- ✅ 不确定具体怎么做,需要先规划 +## 如何编写 +1. **概述**:一句话写清楚你正在完成的目标(≤50 字)。 +2. **任务项**:2~4 条即可,按执行顺序罗列。每条必须描述“对哪个对象做什么动作”,例如 “读取 sales.xlsx,统计月度汇总”。 +3. **粒度**:避免含糊词(“处理”、“完善”等);能在十分钟内完成的最小可执行步骤即可。 -**例子**: -- "帮我整理这些照片" → 需要:分类、重命名、压缩、打包 -- "写一份周报" → 需要:收集数据、整理内容、格式排版、生成PDF -- "分析这个表格" → 需要:读取数据、清洗数据、统计分析、制图 +## 使用流程 +1. **先规划**:在创建清单前,用自然语言写下你准备执行的流程,让自己确认无遗漏。 +2. **todo_create**:把概述与任务数组一次性写对,创建后尽量不要反复删除重建。 +3. **todo_update_task**:每完成一项立刻勾选;若步骤发生变化,先写明原因再修改对应任务。 +4. **todo_finish**:所有任务完成后调用。若仍有未完项但必须停止,先调用 `todo_finish`,再用 `todo_finish_confirm` 说明原因与后续建议。 -## 怎么创建清单 - -### 1. 概述(一句话说明目标) -- **要求**:不超过50字 -- **包含**:做什么事、主要约束条件 -- **例子**: - - ✅ "整理家庭照片,按年份分类并压缩,不超过2GB" - - ❌ "处理照片"(太模糊) - -### 2. 任务列表(最多4条) -- **数量**:建议2-4条,最多不超过4条 -- **顺序**:按照实际操作顺序排列 -- **要求**:每条任务要说清楚具体做什么 - -**✅ 好的任务描述**: -- "读取sales.xlsx文件,统计各月销售额" -- "创建summary.txt文件,写入统计结果" -- "用Python生成柱状图,保存为chart.png" -- "整理所有文件到report文件夹" - -**❌ 不好的任务描述**: -- "处理数据"(不知道处理什么) -- "优化文件"(不知道怎么优化) -- "完善内容"(太模糊) - -## 执行流程 - -### 第1步:先沟通 -创建清单前,要先: -1. 复述理解的任务 -2. 说明计划怎么做 -3. 列出主要步骤 -4. 等用户确认 - -**例子**: +## 编写示例 ``` -用户:"帮我整理这周的工作日志" -你应该说: -"我理解您想整理工作日志。我计划这样做: -1. 读取所有日志文件 -2. 按时间排序合并 -3. 提取关键事项 -4. 生成一份汇总文档 -您看这样可以吗?" -``` - -### 第2步:创建清单 -用户确认后,调用 `todo_create` 创建清单 - -### 第3步:逐项执行 -- 完成一项任务后,立即调用 `todo_update_task` 勾选 -- 如果发现计划需要调整,先告诉用户,再修改 - -### 第4步:结束清单 -- 全部完成:直接调用 `todo_finish` -- 中途需要停止:说明原因,询问是否结束 - -## 常见场景示例 - -### 场景1:文档整理 -``` -概述:合并三个Word文档为一个PDF,统一格式 -任务1:读取doc1.docx、doc2.docx、doc3.docx -任务2:统一字体和标题格式 -任务3:合并内容到report.docx -任务4:转换为PDF并保存 -``` - -### 场景2:数据分析 -``` -概述:分析销售表格,生成月度报告图表 -任务1:读取sales.xlsx,提取本月数据 -任务2:计算总销售额和环比增长 -任务3:用Python生成折线图和柱状图 -任务4:整理结果到report文件夹 -``` - -### 场景3:批量处理 -``` -概述:重命名photos文件夹的照片,按日期排序 -任务1:扫描photos文件夹所有jpg文件 -任务2:读取照片拍摄日期 -任务3:按"YYYYMMDD_序号.jpg"格式重命名 -任务4:移动到organized文件夹 -``` - -### 场景4:信息收集 -``` -概述:搜集人工智能相关资料并整理成文档 -任务1:搜索AI最新发展和应用案例 -任务2:提取3-5篇重要文章内容 -任务3:整理成结构化文档 -任务4:保存为ai_report.md +概述:整理 physics_test 题解,生成 deliverables/result.md +任务1:read_file physics_problems.txt,列出 5 道题 +任务2:在 workspace/solutions.md 中逐题写解答 +任务3:整理 result.md,概括完成情况与风险 +任务4:检查 deliverables/ 是否包含 result.md 与 solutions.md ``` ## 注意事项 +- 只写你能够自主完成的步骤;不要写“等待主智能体确认”之类无法执行的任务。 +- 如果任务被新的发现打断,先在普通回复里说明,再用待办系统更新下一步。 +- 清单结束前必须保证 deliverables/ 与 result.md 已同步更新,否则不要 finish。 -### ✅ 应该做的 -- 任务之间有清晰的先后顺序 -- 每个任务可以独立完成 -- 任务描述具体明确 -- 完成一项立即勾选 - -### ❌ 不应该做的 -- 不要把"先草稿后修改"分成两个任务(一次做完) -- 不要创建重复的清单(已有清单就延续使用) -- 不要跳过步骤(按顺序执行) -- 不要忘记勾选已完成的任务 - -## 如果任务未完成就要结束 - -有时候会遇到: -- 缺少必要信息,无法继续 -- 发现技术限制,做不了 -- 用户改变想法,不做了 - -**正确做法**: -1. 调用 `todo_finish` 尝试结束 -2. 系统会提示有未完成任务 -3. 调用 `todo_finish_confirm` 并说明原因 -4. 告诉用户哪些完成了,哪些没做 - -**例子**: -``` -"由于xxx文件找不到,任务2无法执行。 -已完成:任务1(读取文件) -未完成:任务2-4 -是否结束当前清单?" -``` - -## 快速参考 - -| 工具 | 用途 | 什么时候用 | -|-----|------|---------| -| todo_create | 创建清单 | 开始多步骤任务时 | -| todo_update_task | 勾选任务 | 每完成一项任务后 | -| todo_finish | 结束清单 | 全部任务完成时 | -| todo_finish_confirm | 确认提前结束 | 有未完成任务但需要停止时 | - -## 总结 - -待办事项系统的核心是: -1. **确认需求**:对于复杂项目先和用户探讨 -2. **先想后做**:不要拿到任务就开始执行 -3. **明确指令**:在用户明确给出“好的,请开始”的指令时,才能开始创建待办事项 -4. **拆解清晰**:把大任务分成小步骤 -5. **及时反馈**:完成一步说一步 -6. **灵活调整**:发现问题及时沟通 - -记住:清单是给你自己看的,要给自己明确可执行的规划,同时要让用户知道你在做什么、完成到哪一步了。在用户明确给出“好的,请开始”的指令时,才能开始创建待办事项哦! - +遵循以上规则能让子任务自洽、可追踪,也方便最终在 `result.md` 中回溯整个执行过程。*** diff --git a/sub_agent/prompts/tool_prompts.txt b/sub_agent/prompts/tool_prompts.txt deleted file mode 100644 index a65c542..0000000 --- a/sub_agent/prompts/tool_prompts.txt +++ /dev/null @@ -1,35 +0,0 @@ -以下为需更新的工具描述与提示词草案(供集成到 `define_tools` 及相关返回信息中使用): - ---- - -### read_file -- **描述**:`统一的阅读工具。通过 type 参数在 read(直接阅读)、search(全文搜索)、extract(按行抽取)之间切换,始终返回 UTF-8 文本。所有模式都会在响应前根据 max_chars 截断输出,保证不会超量。` -- **模式提示**: - - `read`:可选 `start_line`/`end_line`,适合一次性查看短片段。 - - `search`:需提供 `query`,并可设置 `max_matches`、`context_before`、`context_after`、`case_sensitive`,自动合并重复命中,以窗口形式返回。 - - `extract`:传入 `segments=[{start_line,end_line,label?},...]`,适合按多段行号提取关键信息。 -- **失败/限制提示**:若因编码或体积被拒绝,提醒:`文件不是 UTF-8 或体量过大,请改用 run_python(可结合 python-docx、pandas 等库)读取。` 若多次需要查看同一长文件,建议直接调用 `focus_file`。 - -### focus_file -- **描述**:`持续在上下文中展示 UTF-8 文本文件的完整内容,适合频繁查看/修改的核心文件。文件非 UTF-8 或体积超限将直接拒绝;如需了解二进制/Office 文件,请改用 run_python。` - -### run_python -- **描述**:`执行一次性 Python 脚本,可用于处理二进制或非 UTF-8 文件(如 Excel、Word、PDF、图片),或进行数据分析与验证。请在代码内显式读取文件并打印必要结果,避免长时间阻塞。` -- **成功消息补充**:鼓励记录输出摘要,提醒用户脚本已在云端运行,可复用逻辑写入脚本文件。 - -### run_command -- **描述**:`执行一次性命令并返回结果。适合快速查看文件信息(如 file/ls/stat/iconv)、转换编码、或调用 cli 工具。禁止启动交互式程序;对已聚焦文件仅允许使用 grep -n 等定位命令。若需要解析二进制/Office 文件,可结合 run_python 或专门命令行工具(如 xlsx2csv)。` - -### terminal_session / terminal_input / terminal_reset -- **描述补充**:强调云端多用户环境:`请在受限工作区内使用,避免切换到未经授权的路径。禁止启动需完整 TTY 的程序;如误触,请使用 terminal_reset 恢复,并说明原因。` - -### web_search -- **描述**:`在外部信息确实缺失时才调用的网络搜索工具。调用前应确认问题无法通过现有上下文解决,并向用户说明搜索目的。尽量精准撰写 query,合理设置时间/主题参数,避免无意义或重复搜索。` -- **失败/过度使用提示**:当搜索结果空或不必要时返回建议:`考虑先梳理现有信息或向用户确认,再决定是否继续搜索。` - -### extract_webpage / save_webpage -- **描述**:`仅在 web_search 结果不足以提供所需细节时使用。提醒会显著增加 token 消耗,提取后应考虑落地为文本文件,并告知用户整理计划。` -- **成功消息补充**:提示已获取的内容体量及后续处理建议(如“建议整理要点写入笔记或待办”)。 - -### todo_create / todo_update_task / todo_finish -- **描述补充**:强调沟通闭环:`建/更/结单前需先向用户同步当前理解、风险及下一步。` `todo_finish` 在仍有未完成任务时要求提供剩余事项及后续建议。*** diff --git a/sub_agent/static/app.js b/sub_agent/static/app.js index 506c491..8779d61 100644 --- a/sub_agent/static/app.js +++ b/sub_agent/static/app.js @@ -155,6 +155,7 @@ async function bootstrapApp() { reference_manifest: [], last_tool: '' }, + readonlyNoticeShown: false, // 连接状态 isConnected: false, socket: null, @@ -189,6 +190,8 @@ async function bootstrapApp() { // 滚动控制 userScrolling: false, autoScrollEnabled: true, + scrollListenerAttached: false, + scrollListenerRetryWarned: false, // 面板宽度控制 leftWidth: 280, @@ -230,24 +233,6 @@ async function bootstrapApp() { conversationsOffset: 0, conversationsLimit: 20, - // ========================================== - // Token统计相关状态(修复版) - // ========================================== - - // 当前上下文Token(动态计算,包含完整prompt) - currentContextTokens: 0, - - // 累计Token统计(从对话文件和WebSocket获取) - currentConversationTokens: { - // 累计统计字段 - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - - }, - // Token面板折叠状态 - tokenPanelCollapsed: false, - // 对话压缩状态 compressing: false, @@ -275,7 +260,8 @@ async function bootstrapApp() { terminal_realtime: '🖥️', terminal_command: '⌨️', memory: '🧠', - todo: '🗒️' + todo: '🗒️', + sub_agent: '🤖' }, // 右键菜单相关 @@ -431,8 +417,23 @@ async function bootstrapApp() { this.loadInitialData(); return; } - await this.fetchSubAgentTaskInfo(); + const info = await this.fetchSubAgentTaskInfo(); await this.fetchSubAgentConversation(); + await this.fetchSubAgentFileTree(); + await this.fetchTodoList(); + if (!this.socket || !this.socket.connected) { + setTimeout(() => { + if (!this.socket || !this.socket.connected) { + console.warn('WebSocket尚未建立,启用只读回放模式。'); + this.isConnected = true; + if (!this.readonlyNoticeShown) { + this.readonlyNoticeShown = true; + const status = (info && info.status) ? info.status : 'unknown'; + this.addSystemMessage(`🔗 子智能体连接尚未建立,已进入只读模式(当前状态:${status})。`); + } + } + }, 1200); + } }, async fetchSubAgentTaskInfo(silent = false) { @@ -442,7 +443,23 @@ async function bootstrapApp() { try { const resp = await fetch(`/tasks/${encodeURIComponent(this.subAgentTaskId)}`); if (!resp.ok) { - throw new Error(await resp.text()); + let payload; + try { + payload = await resp.json(); + } catch (err) { + payload = { error: await resp.text() }; + } + if (resp.status === 404) { + const fallback = { + task_id: this.subAgentTaskId, + status: 'archived', + summary: this.currentConversationTitle || '', + message: payload && payload.message ? payload.message : '任务不存在' + }; + this.subAgentTaskInfo = { ...this.subAgentTaskInfo, ...fallback }; + return fallback; + } + throw new Error(payload && (payload.error || payload.message) ? (payload.error || payload.message) : '加载失败'); } const data = await resp.json(); if (!data.success) { @@ -452,6 +469,12 @@ async function bootstrapApp() { ...this.subAgentTaskInfo, ...data }; + if (data.task_id && data.task_id !== this.subAgentTaskId) { + this.subAgentTaskId = data.task_id; + if (this.socket && this.socket.connected) { + this.socket.emit('sub_agent_join', { task_id: data.task_id }); + } + } if (data.workspace_dir) { this.projectPath = data.workspace_dir; } @@ -468,6 +491,9 @@ async function bootstrapApp() { if (!silent) { console.warn('获取子智能体任务信息失败:', error); } + if (!this.isConnected) { + this.addSystemMessage(`⚠️ 无法获取子智能体任务信息:${error.message || error}`); + } return null; } }, @@ -479,43 +505,79 @@ async function bootstrapApp() { try { const resp = await fetch(`/tasks/${encodeURIComponent(this.subAgentTaskId)}/conversation`); if (!resp.ok) { - throw new Error(await resp.text()); + const fallbackHandled = await this.tryFetchArchivedConversation(); + if (!fallbackHandled) { + throw new Error(await resp.text()); + } + return; } const data = await resp.json(); if (!data.success) { - throw new Error(data.error || '加载失败'); + const fallbackHandled = await this.tryFetchArchivedConversation(); + if (!fallbackHandled) { + throw new Error(data.error || '加载失败'); + } + return; } if (data.conversation_id) { this.currentConversationId = data.conversation_id; } - this.messages = Array.isArray(data.messages) - ? data.messages.map((msg) => this.normalizeSubAgentMessage(msg)) - : []; + const transformed = this.transformHistoryMessages(data.messages || []); + this.messages = transformed; this.$nextTick(() => this.scrollToBottom(true)); if (!this.currentConversationTitle && this.subAgentTaskInfo.summary) { this.currentConversationTitle = this.subAgentTaskInfo.summary; } } catch (error) { console.warn('加载子智能体对话失败:', error); + if (!this.isConnected) { + this.isConnected = true; + } + this.addSystemMessage(`⚠️ 加载子智能体对话失败:${error.message || error}`); + } + }, + + async tryFetchArchivedConversation() { + if (!this.currentConversationId) { + return false; + } + try { + const response = await fetch(`/sub_agent/conversations/${this.currentConversationId}`); + if (!response.ok) { + return false; + } + const payload = await response.json(); + if (!payload.success) { + return false; + } + const transformed = this.transformHistoryMessages(payload.messages || []); + this.messages = transformed; + this.$nextTick(() => this.scrollToBottom(true)); + return true; + } catch (err) { + console.warn('加载归档子智能体对话失败:', err); + return false; } }, - normalizeSubAgentMessage(raw) { - const actions = Array.isArray(raw.actions) - ? raw.actions.map((action) => ({ ...action })) - : []; - return { - role: raw.role || 'assistant', - content: raw.content || '', - name: raw.name, - tool_calls: raw.tool_calls || [], - metadata: raw.metadata || {}, - actions, - timestamp: raw.timestamp || Date.now(), - streamingText: '', - streamingThinking: '', - currentStreamingType: null - }; + async fetchSubAgentFileTree() { + if (!this.isSubAgentView || !this.subAgentTaskId) { + return; + } + try { + const resp = await fetch(`/tasks/${encodeURIComponent(this.subAgentTaskId)}/files`); + if (!resp.ok) { + throw new Error(await resp.text()); + } + const data = await resp.json(); + if (data.success && data.data) { + this.updateFileTree(data.data); + } else { + console.warn('获取子智能体文件树失败:', data.error || data.message); + } + } catch (error) { + console.warn('加载子智能体文件树失败:', error); + } }, async sendSubAgentMessage() { @@ -634,10 +696,9 @@ async function bootstrapApp() { this.currentConversationTitle = '新对话'; this.messages = []; this.resetAllStates(); - this.resetTokenStatistics(); return; - } - this.loadConversation(convId); + } + this.loadConversation(convId); }, stripConversationPrefix(conversationId) { @@ -751,37 +812,49 @@ async function bootstrapApp() { }, initScrollListener() { - const messagesArea = this.$refs.messagesArea; - if (!messagesArea) { - console.warn('消息区域未找到'); + if (this.scrollListenerAttached) { return; } - - let isProgrammaticScroll = false; - const bottomThreshold = 12; - - this._setScrollingFlag = (flag) => { - isProgrammaticScroll = !!flag; - }; - - messagesArea.addEventListener('scroll', () => { - if (isProgrammaticScroll) { + const attach = () => { + const messagesArea = this.$refs.messagesArea; + if (!messagesArea) { + if (!this.scrollListenerRetryWarned) { + console.warn('消息区域未找到,等待渲染后重试'); + this.scrollListenerRetryWarned = true; + } + setTimeout(attach, 200); return; } + this.scrollListenerRetryWarned = false; + this.scrollListenerAttached = true; - const scrollTop = messagesArea.scrollTop; - const scrollHeight = messagesArea.scrollHeight; - const clientHeight = messagesArea.clientHeight; - const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; + let isProgrammaticScroll = false; + const bottomThreshold = 12; - if (isAtBottom) { - this.userScrolling = false; - this.autoScrollEnabled = true; - } else { - this.userScrolling = true; - this.autoScrollEnabled = false; - } - }); + this._setScrollingFlag = (flag) => { + isProgrammaticScroll = !!flag; + }; + + messagesArea.addEventListener('scroll', () => { + if (isProgrammaticScroll) { + return; + } + + const scrollTop = messagesArea.scrollTop; + const scrollHeight = messagesArea.scrollHeight; + const clientHeight = messagesArea.clientHeight; + const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; + + if (isAtBottom) { + this.userScrolling = false; + this.autoScrollEnabled = true; + } else { + this.userScrolling = true; + this.autoScrollEnabled = false; + } + }); + }; + this.$nextTick(attach); }, initSocket() { @@ -801,6 +874,7 @@ async function bootstrapApp() { // 连接事件 this.socket.on('connect', () => { this.isConnected = true; + this.readonlyNoticeShown = false; console.log('WebSocket已连接'); if (this.isSubAgentView) { if (this.subAgentTaskId) { @@ -823,38 +897,16 @@ async function bootstrapApp() { console.error('WebSocket连接错误:', error.message); }); - // ========================================== - // Token统计WebSocket事件处理(修复版) - // ========================================== - - this.socket.on('token_update', (data) => { - console.log('收到token更新事件:', data); - - // 只处理当前对话的token更新 - if (data.conversation_id === this.currentConversationId) { - // 更新累计统计(使用后端提供的准确字段名) - this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; - - console.log(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); - - // 同时更新当前上下文Token(关键修复) - this.updateCurrentContextTokens(); - - this.$forceUpdate(); - } - }); - - if (!this.isSubAgentView) { - this.socket.on('todo_updated', (data) => { - console.log('收到todo更新事件:', data); - if (data && data.conversation_id) { - this.currentConversationId = data.conversation_id; - } - this.todoList = data && data.todo_list ? data.todo_list : null; - }); - } + this.socket.on('todo_updated', (data) => { + console.log('收到todo更新事件:', data); + if (!this.isSubAgentView && data && data.conversation_id) { + this.currentConversationId = data.conversation_id; + } + if (this.isSubAgentView && data && data.conversation_id && this.subAgentConversationId && data.conversation_id !== this.subAgentConversationId) { + return; + } + this.todoList = data && data.todo_list ? data.todo_list : null; + }); // 系统就绪 if (!this.isSubAgentView) { @@ -891,7 +943,6 @@ async function bootstrapApp() { this.messages = []; this.currentConversationId = null; this.currentConversationTitle = ''; - this.resetTokenStatistics(); history.replaceState({}, '', '/new'); } @@ -944,8 +995,6 @@ async function bootstrapApp() { // 延迟获取Token统计(累计+当前上下文) setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); this.fetchTodoList(); }, 500); }); @@ -1260,7 +1309,6 @@ async function bootstrapApp() { // 关键修复:每个工具完成后都更新当前上下文Token if (data.status === 'completed') { setTimeout(() => { - this.updateCurrentContextTokens(); }, 500); } }); @@ -1327,9 +1375,7 @@ async function bootstrapApp() { this.resetAllStates(); // 任务完成后立即更新Token统计(关键修复) - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - this.fetchConversationTokenStatistics(); + if (this.currentConversationId) { } }); @@ -1339,7 +1385,6 @@ async function bootstrapApp() { // 聚焦文件变化时更新当前上下文Token(关键修复) if (this.currentConversationId) { setTimeout(() => { - this.updateCurrentContextTokens(); }, 500); } }); @@ -1350,7 +1395,6 @@ async function bootstrapApp() { // 文件树变化时也可能影响上下文 if (this.currentConversationId) { setTimeout(() => { - this.updateCurrentContextTokens(); }, 500); } }); @@ -1377,8 +1421,6 @@ async function bootstrapApp() { this.messages = []; this.currentMessageIndex = -1; this.expandedBlocks.clear(); - // 清除对话时重置Token统计 - this.resetTokenStatistics(); } else if (data.command === 'status' && data.success) { this.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); } else if (!data.success) { @@ -1572,16 +1614,6 @@ async function bootstrapApp() { console.log('前端状态重置完成'); }, - // 重置Token统计 - resetTokenStatistics() { - this.currentContextTokens = 0; - this.currentConversationTokens = { - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - }; - }, - async loadInitialData() { if (this.isSubAgentView) { await this.loadSubAgentInitialData(); @@ -1621,8 +1653,6 @@ async function bootstrapApp() { } await this.fetchAndDisplayHistory(); // 获取当前对话的Token统计 - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); } catch (e) { console.warn('获取当前对话标题失败:', e); } @@ -1646,72 +1676,6 @@ async function bootstrapApp() { } }, - // ========================================== - // Token统计相关方法(完全修复版) - // ========================================== - - async updateCurrentContextTokens() { - // 获取当前上下文Token数(动态计算,包含完整prompt构建) - if (!this.currentConversationId) { - this.currentContextTokens = 0; - return; - } - - try { - console.log(`正在更新当前上下文Token: ${this.currentConversationId}`); - - // 关键修复:使用正确的动态API,包含文件结构+记忆+聚焦文件+终端内容+工具定义 - const response = await fetch(`/api/conversations/${this.currentConversationId}/tokens`); - const data = await response.json(); - - if (data.success && data.data) { - this.currentContextTokens = data.data.total_tokens || 0; - console.log(`当前上下文Token更新: ${this.currentContextTokens}`); - this.$forceUpdate(); - } else { - console.warn('获取当前上下文Token失败:', data.error); - this.currentContextTokens = 0; - } - } catch (error) { - console.warn('获取当前上下文Token异常:', error); - this.currentContextTokens = 0; - } - }, - - async fetchConversationTokenStatistics() { - // 获取对话累计Token统计(加载对话时、任务完成后调用) - if (!this.currentConversationId) { - this.resetTokenStatistics(); - return; - } - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/token-statistics`); - const data = await response.json(); - - if (data.success && data.data) { - // 更新累计统计 - this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; - - console.log(`累计Token统计: 输入=${data.data.total_input_tokens}, 输出=${data.data.total_output_tokens}, 总计=${data.data.total_tokens}`); - this.$forceUpdate(); - } else { - console.warn('获取Token统计失败:', data.error); - // 保持当前统计,不重置 - } - } catch (error) { - console.warn('获取Token统计异常:', error); - // 保持当前统计,不重置 - } - }, - - // Token面板折叠/展开切换 - toggleTokenPanel() { - this.tokenPanelCollapsed = !this.tokenPanelCollapsed; - }, - // ========================================== // 对话管理核心功能 // ========================================== @@ -1789,8 +1753,6 @@ async function bootstrapApp() { // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); }, 500); } else { @@ -1808,119 +1770,170 @@ async function bootstrapApp() { // ========================================== async fetchAndDisplayHistory() { console.log('开始获取历史对话内容...'); - if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { console.log('没有当前对话ID,跳过历史加载'); return; } - try { - // 使用专门的API获取对话消息历史 - const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); - - if (!messagesResponse.ok) { + const response = await fetch(`/api/conversations/${this.currentConversationId}/messages`); + if (!response.ok) { console.warn('无法获取消息历史,尝试备用方法'); - // 备用方案:通过状态API获取 const statusResponse = await fetch('/api/status'); const status = await statusResponse.json(); - console.log('系统状态:', status); - - // 如果状态中有对话历史字段 if (status.conversation_history && Array.isArray(status.conversation_history)) { this.renderHistoryMessages(status.conversation_history); - return; } - - console.log('备用方案也无法获取历史消息'); return; } - - const messagesData = await messagesResponse.json(); - console.log('获取到消息数据:', messagesData); - - if (messagesData.success && messagesData.data && messagesData.data.messages) { - const messages = messagesData.data.messages; - console.log(`发现 ${messages.length} 条历史消息`); - - if (messages.length > 0) { - // 清空当前显示的消息 - this.messages = []; - - // 渲染历史消息 - 这是关键功能 - this.renderHistoryMessages(messages); - - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - - console.log('历史对话内容显示完成'); - } else { - console.log('对话存在但没有历史消息'); - this.messages = []; - } + const payload = await response.json(); + if (payload.success && payload.data && Array.isArray(payload.data.messages)) { + this.renderHistoryMessages(payload.data.messages); } else { - console.log('消息数据格式不正确:', messagesData); + console.warn('历史消息结构异常:', payload); this.messages = []; } - } catch (error) { console.error('获取历史对话失败:', error); - console.log('尝试不显示错误弹窗,仅在控制台记录'); - // 不显示alert,避免打断用户体验 this.messages = []; } }, - - // ========================================== - // 关键功能:渲染历史消息 - // ========================================== + renderHistoryMessages(historyMessages) { - console.log('开始渲染历史消息...', historyMessages); - console.log('历史消息数量:', historyMessages.length); - + const transformed = this.transformHistoryMessages(historyMessages); + this.messages = transformed; + this.$forceUpdate(); + this.$nextTick(() => this.scrollToBottom()); + }, + + transformHistoryMessages(historyMessages = []) { if (!Array.isArray(historyMessages)) { - console.error('历史消息不是数组格式'); - return; + return []; } - + const output = []; let currentAssistantMessage = null; - - historyMessages.forEach((message, index) => { - console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); - - if (message.role === 'user') { - // 用户消息 - 先结束之前的assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - this.messages.push({ - role: 'user', - content: message.content || '' + + const flushAssistant = () => { + if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { + output.push(currentAssistantMessage); + } + currentAssistantMessage = null; + }; + + const ensureAssistantMessage = () => { + if (!currentAssistantMessage) { + currentAssistantMessage = { + role: 'assistant', + actions: [], + streamingThinking: '', + streamingText: '', + currentStreamingType: null + }; + } + return currentAssistantMessage; + }; + + const parseToolArguments = (toolCall) => { + if (!toolCall || !toolCall.function) { + return {}; + } + const raw = toolCall.function.arguments; + if (!raw) { + return {}; + } + try { + return typeof raw === 'string' ? JSON.parse(raw || '{}') : raw; + } catch (error) { + console.warn('解析工具参数失败:', error); + return {}; + } + }; + + const attachToolResult = (message, assistantMsg) => { + if (!assistantMsg || !assistantMsg.actions) { + return false; + } + const actions = assistantMsg.actions; + let toolAction = null; + if (message.tool_call_id) { + toolAction = actions.find(action => action.type === 'tool' && action.tool.id === message.tool_call_id); + } + if (!toolAction && message.name) { + const candidates = actions.filter(action => action.type === 'tool' && action.tool.name === message.name); + toolAction = candidates[candidates.length - 1]; + } + if (!toolAction) { + return false; + } + let resultObj; + try { + resultObj = JSON.parse(message.content); + } catch (_) { + resultObj = message.content ? { output: message.content } : {}; + } + toolAction.tool.status = resultObj.success === false ? 'failed' : 'completed'; + toolAction.tool.result = resultObj; + if (message.name === 'append_to_file' && resultObj && typeof resultObj === 'object') { + const summary = { + path: resultObj.path || '未知文件', + success: resultObj.success !== false, + summary: resultObj.message || (resultObj.success === false ? '追加失败' : '追加完成'), + lines: resultObj.lines || 0, + bytes: resultObj.bytes || 0, + forced: !!resultObj.forced + }; + assistantMsg.actions.push({ + id: `history-append-${Date.now()}-${Math.random()}`, + type: 'append', + append: summary, + timestamp: Date.now() }); - console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); - - } else if (message.role === 'assistant') { - // AI消息 - 如果没有当前assistant消息,创建一个 - if (!currentAssistantMessage) { - currentAssistantMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - } - - // 处理思考内容 - 支持多种格式 + } + return true; + }; + + const isToolResultNotice = (message) => { + if (!message || typeof message.content !== 'string') { + return false; + } + return message.content.trim().startsWith('[工具结果]'); + }; + + const applyToolResultNotice = (message, assistantMsg) => { + if (!isToolResultNotice(message) || !assistantMsg) { + return false; + } + const trimmed = message.content.trim(); + const headerMatch = trimmed.match(/\[工具结果\]\s*([^\s(]+)(?:\s*\(tool_call_id=([^)]+)\))?/); + const remainder = headerMatch ? trimmed.slice(headerMatch[0].length).trim() : ''; + const synthetic = { + name: headerMatch ? headerMatch[1] : message.name, + tool_call_id: message.tool_call_id || (headerMatch ? headerMatch[2] : undefined), + content: remainder || '{}' + }; + return attachToolResult(synthetic, assistantMsg); + }; + + historyMessages.forEach((message) => { + const role = (message.role || 'assistant').toLowerCase(); + if (role === 'user') { + flushAssistant(); + output.push({ + role: 'user', + content: message.content || '', + metadata: message.metadata || {}, + timestamp: message.timestamp + }); + return; + } + + if (role === 'assistant') { + const assistant = ensureAssistantMessage(); const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; - + const metadata = message.metadata || {}; + const appendPayloadMeta = metadata.append_payload; + const modifyPayloadMeta = metadata.modify_payload; + + const thinkPatterns = [/([\s\S]*?)<\/think>/g, /([\s\S]*?)<\/thinking>/g]; let allThinkingContent = ''; for (const pattern of thinkPatterns) { let match; @@ -1928,30 +1941,23 @@ async function bootstrapApp() { allThinkingContent += match[1].trim() + '\n'; } } - - if (allThinkingContent) { - currentAssistantMessage.actions.push({ + if (allThinkingContent.trim()) { + assistant.actions.push({ id: `history-think-${Date.now()}-${Math.random()}`, type: 'thinking', content: allThinkingContent.trim(), streaming: false, timestamp: Date.now() }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); } - - // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - + let textContent = content .replace(/[\s\S]*?<\/think>/g, '') .replace(/[\s\S]*?<\/thinking>/g, '') .trim(); - + if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ + assistant.actions.push({ id: `history-append-payload-${Date.now()}-${Math.random()}`, type: 'append_payload', append: { @@ -1963,9 +1969,8 @@ async function bootstrapApp() { }, timestamp: Date.now() }); - console.log('添加append占位信息:', appendPayloadMeta.path); } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ + assistant.actions.push({ id: `history-modify-payload-${Date.now()}-${Math.random()}`, type: 'modify_payload', modify: { @@ -1978,145 +1983,66 @@ async function bootstrapApp() { }, timestamp: Date.now() }); - console.log('添加modify占位信息:', modifyPayloadMeta.path); } - + if (textContent && !appendPayloadMeta && !modifyPayloadMeta) { - currentAssistantMessage.actions.push({ + assistant.actions.push({ id: `history-text-${Date.now()}-${Math.random()}`, type: 'text', content: textContent, streaming: false, timestamp: Date.now() }); - console.log('添加文本内容:', textContent.substring(0, 50) + '...'); } - - // 处理工具调用 + if (message.tool_calls && Array.isArray(message.tool_calls)) { message.tool_calls.forEach((toolCall, tcIndex) => { - let arguments_obj = {}; - try { - arguments_obj = typeof toolCall.function.arguments === 'string' - ? JSON.parse(toolCall.function.arguments || '{}') - : (toolCall.function.arguments || {}); - } catch (e) { - console.warn('解析工具参数失败:', e); - arguments_obj = {}; - } - - currentAssistantMessage.actions.push({ + assistant.actions.push({ id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, type: 'tool', tool: { id: toolCall.id, - name: toolCall.function.name, - arguments: arguments_obj, + name: toolCall.function?.name, + arguments: parseToolArguments(toolCall), status: 'preparing', result: null }, timestamp: Date.now() }); - console.log('添加工具调用:', toolCall.function.name); }); } - - } else if (message.role === 'tool') { - // 工具结果 - 更新当前assistant消息中对应的工具 - if (currentAssistantMessage) { - // 查找对应的工具action - 使用更灵活的匹配 - let toolAction = null; - - // 优先按tool_call_id匹配 - if (message.tool_call_id) { - toolAction = currentAssistantMessage.actions.find(action => - action.type === 'tool' && - action.tool.id === message.tool_call_id - ); - } - - // 如果找不到,按name匹配最后一个同名工具 - if (!toolAction && message.name) { - const sameNameTools = currentAssistantMessage.actions.filter(action => - action.type === 'tool' && - action.tool.name === message.name - ); - toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 - } - - if (toolAction) { - // 解析工具结果 - let result; - try { - // 尝试解析为JSON - result = JSON.parse(message.content); - } catch (e) { - // 如果不是JSON,就作为纯文本 - result = { - output: message.content, - success: true - }; - } - - toolAction.tool.status = 'completed'; - toolAction.tool.result = result; - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } - console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - if (message.name === 'append_to_file' && result && typeof result === 'object') { - const appendSummary = { - path: result.path || '未知文件', - success: result.success !== false, - summary: result.message || (result.success === false ? '追加失败' : '追加完成'), - lines: result.lines || 0, - bytes: result.bytes || 0, - forced: !!result.forced - }; - currentAssistantMessage.actions.push({ - id: `history-append-${Date.now()}-${Math.random()}`, - type: 'append', - append: appendSummary, - timestamp: Date.now() - }); - } - } else { - console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); - } - } - - } else { - // 其他类型消息(如system)- 先结束当前assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - console.log('处理其他类型消息:', message.role); - this.messages.push({ - role: message.role, - content: message.content || '' - }); + return; } + + if (role === 'tool') { + if (!attachToolResult(message, currentAssistantMessage)) { + flushAssistant(); + output.push({ + role: 'system', + content: message.content || '', + metadata: message.metadata || {}, + timestamp: message.timestamp + }); + } + return; + } + + if (applyToolResultNotice(message, currentAssistantMessage)) { + return; + } + + flushAssistant(); + output.push({ + role: message.role || 'system', + content: message.content || '', + metadata: message.metadata || {}, + timestamp: message.timestamp + }); }); - - // 处理最后一个assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - } - - console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); - - // 强制更新视图 - this.$forceUpdate(); - - // 确保滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); + + flushAssistant(); + return output; }, - async createNewConversation() { console.log('创建新对话...'); @@ -2142,9 +2068,6 @@ async function bootstrapApp() { this.currentConversationTitle = '新对话'; history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); - // 重置Token统计 - this.resetTokenStatistics(); - // 重置状态 this.resetAllStates(); @@ -2185,7 +2108,6 @@ async function bootstrapApp() { this.currentConversationId = null; this.currentConversationTitle = ''; this.resetAllStates(); - this.resetTokenStatistics(); history.replaceState({}, '', '/new'); } @@ -2360,6 +2282,11 @@ async function bootstrapApp() { cycleSidebarPanel() { if (this.isSubAgentView) { this.panelMode = this.panelMode === 'files' ? 'todo' : 'files'; + if (this.panelMode === 'files') { + this.fetchSubAgentFileTree(); + } else { + this.fetchTodoList(); + } return; } const order = ['files', 'todo', 'subAgents']; @@ -2416,7 +2343,12 @@ async function bootstrapApp() { const pathSuffix = convSegment ? `/${convSegment}+${agentLabel}` : `/sub_agent/${agent.task_id}`; - const url = `${base}${pathSuffix}`; + const params = new URLSearchParams(); + params.set('task_id', agent.task_id); + if (agent.conversation_id) { + params.set('conversation_id', agent.conversation_id); + } + const url = `${base}${pathSuffix}?${params.toString()}`; window.open(url, '_blank'); }, @@ -2547,7 +2479,6 @@ async function bootstrapApp() { // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) setTimeout(() => { if (this.currentConversationId) { - this.updateCurrentContextTokens(); } }, 1000); }, @@ -2796,7 +2727,9 @@ async function bootstrapApp() { 'todo_create': '🗒️', 'todo_update_task': '☑️', 'todo_finish': '🏁', - 'todo_finish_confirm': '❗' + 'todo_finish_confirm': '❗', + 'create_sub_agent': '🤖', + 'wait_sub_agent': '⏳' }; return icons[toolName] || '⚙️'; }, @@ -2832,7 +2765,9 @@ async function bootstrapApp() { 'todo_create': 'file-animation', 'todo_update_task': 'file-animation', 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' + 'todo_finish_confirm': 'file-animation', + 'create_sub_agent': 'terminal-animation', + 'wait_sub_agent': 'wait-animation' }; return animations[tool.name] || 'default-animation'; } @@ -3271,19 +3206,6 @@ async function bootstrapApp() { document.removeEventListener('mouseup', this.stopResize); document.body.style.userSelect = ''; document.body.style.cursor = ''; - }, - - // 格式化token显示(修复NaN问题) - formatTokenCount(tokens) { - // 确保tokens是数字,防止NaN - const num = Number(tokens) || 0; - if (num < 1000) { - return num.toString(); - } else if (num < 1000000) { - return (num / 1000).toFixed(1) + 'K'; - } else { - return (num / 1000000).toFixed(1) + 'M'; - } } } }); diff --git a/sub_agent/static/backup_20251026_183122/app.js b/sub_agent/static/backup_20251026_183122/app.js deleted file mode 100644 index 78636f0..0000000 --- a/sub_agent/static/backup_20251026_183122/app.js +++ /dev/null @@ -1,2801 +0,0 @@ -// static/app-enhanced.js - 修复版,正确实现Token实时更新 - -// 等待所有资源加载完成 -window.onload = function() { - - // 检查必要的库是否加载 - if (typeof Vue === 'undefined') { - console.error('错误:Vue.js 未加载'); - document.body.innerHTML = '

Vue.js 加载失败,请刷新页面

'; - return; - } - - if (typeof io === 'undefined') { - console.error('错误:Socket.IO 未加载'); - document.body.innerHTML = '

Socket.IO 加载失败,请刷新页面

'; - return; - } - - console.log('所有依赖加载成功,初始化Vue应用...'); - - const { createApp } = Vue; - - const app = createApp({ - data() { - return { - // 连接状态 - isConnected: false, - socket: null, - - // 系统信息 - projectPath: '', - thinkingMode: '未知', - - // 消息相关 - messages: [], - inputMessage: '', - - // 当前消息索引 - currentMessageIndex: -1, - streamingMessage: false, - - // 停止功能状态 - stopRequested: false, - - // 路由相关 - initialRouteResolved: false, - - // 文件相关 - fileTree: [], - focusedFiles: {}, - expandedFolders: {}, - - // 展开状态管理 - expandedBlocks: new Set(), - - // 滚动控制 - userScrolling: false, - autoScrollEnabled: true, - - // 面板宽度控制 - leftWidth: 280, - rightWidth: 420, - rightCollapsed: true, - isResizing: false, - resizingPanel: null, - minPanelWidth: 200, - maxPanelWidth: 600, - - // 工具状态跟踪 - preparingTools: new Map(), - activeTools: new Map(), - toolActionIndex: new Map(), - toolActionIndex: new Map(), - - // ========================================== - // 对话管理相关状态 - // ========================================== - - // 对话历史侧边栏 - sidebarCollapsed: true, // 默认收起对话侧边栏 - showTodoList: false, - conversations: [], - conversationsLoading: false, - hasMoreConversations: false, - loadingMoreConversations: false, - currentConversationId: null, - currentConversationTitle: '当前对话', - - // 搜索功能 - searchQuery: '', - searchTimer: null, - - // 分页 - conversationsOffset: 0, - conversationsLimit: 20, - - // ========================================== - // Token统计相关状态(修复版) - // ========================================== - - // 当前上下文Token(动态计算,包含完整prompt) - currentContextTokens: 0, - - // 累计Token统计(从对话文件和WebSocket获取) - currentConversationTokens: { - // 累计统计字段 - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - - }, - // Token面板折叠状态 - tokenPanelCollapsed: false, - - // 对话压缩状态 - compressing: false, - - // 设置菜单状态 - settingsOpen: false, - - // 工具控制菜单 - toolMenuOpen: false, - toolSettings: [], - toolSettingsLoading: false, - - // 文件上传状态 - uploading: false, - - // TODO 列表 - todoList: null, - todoEmoji: '🗒️', - fileEmoji: '📁', - todoDoneEmoji: '☑️', - todoPendingEmoji: '⬜️', - toolCategoryEmojis: { - network: '🌐', - file_edit: '📝', - read_focus: '🔍', - terminal_realtime: '🖥️', - terminal_command: '⌨️', - memory: '🧠', - todo: '🗒️' - }, - - // 右键菜单相关 - contextMenu: { - visible: false, - x: 0, - y: 0, - node: null - }, - onDocumentClick: null, - onWindowScroll: null, - onKeydownListener: null - } - }, - - async mounted() { - console.log('Vue应用已挂载'); - await this.bootstrapRoute(); - this.initSocket(); - this.initScrollListener(); - - // 延迟加载初始数据 - setTimeout(() => { - this.loadInitialData(); - }, 500); - - document.addEventListener('click', this.handleClickOutsideSettings); - document.addEventListener('click', this.handleClickOutsideToolMenu); - window.addEventListener('popstate', this.handlePopState); - - this.onDocumentClick = (event) => { - if (!this.contextMenu.visible) { - return; - } - if (event.target && event.target.closest && event.target.closest('.context-menu')) { - return; - } - this.hideContextMenu(); - }; - - this.onWindowScroll = () => { - if (this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - this.onKeydownListener = (event) => { - if (event.key === 'Escape' && this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - document.addEventListener('click', this.onDocumentClick); - window.addEventListener('scroll', this.onWindowScroll, true); - document.addEventListener('keydown', this.onKeydownListener); - }, - - beforeUnmount() { - document.removeEventListener('click', this.handleClickOutsideSettings); - document.removeEventListener('click', this.handleClickOutsideToolMenu); - window.removeEventListener('popstate', this.handlePopState); - if (this.onDocumentClick) { - document.removeEventListener('click', this.onDocumentClick); - this.onDocumentClick = null; - } - if (this.onWindowScroll) { - window.removeEventListener('scroll', this.onWindowScroll, true); - this.onWindowScroll = null; - } - if (this.onKeydownListener) { - document.removeEventListener('keydown', this.onKeydownListener); - this.onKeydownListener = null; - } - }, - - methods: { - async bootstrapRoute() { - const path = window.location.pathname.replace(/^\/+/, ''); - if (!path || path === 'new') { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.initialRouteResolved = true; - return; - } - - const convId = path.startsWith('conv_') ? path : `conv_${path}`; - try { - const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' }); - const result = await resp.json(); - if (result.success) { - this.currentConversationId = convId; - this.currentConversationTitle = result.title || '对话'; - history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`); - } else { - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } - } catch (error) { - console.warn('初始化路由失败:', error); - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } finally { - this.initialRouteResolved = true; - } - }, - - handlePopState(event) { - const state = event.state || {}; - const convId = state.conversationId; - if (!convId) { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.messages = []; - this.resetAllStates(); - this.resetTokenStatistics(); - return; - } - this.loadConversation(convId); - }, - - stripConversationPrefix(conversationId) { - if (!conversationId) return ''; - return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId; - }, - - showContextMenu(payload) { - if (!payload || !payload.node) { - return; - } - const { node, event } = payload; - console.log('context menu', node.path, node.type); - if (event && typeof event.preventDefault === 'function') { - event.preventDefault(); - } - if (event && typeof event.stopPropagation === 'function') { - event.stopPropagation(); - } - if (!node.path && node.path !== '') { - this.hideContextMenu(); - return; - } - if (node.type !== 'file' && node.type !== 'folder') { - this.hideContextMenu(); - return; - } - - this.hideContextMenu(); - - let x = (event && event.clientX) || 0; - let y = (event && event.clientY) || 0; - const menuWidth = 200; - const menuHeight = 50; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - if (x + menuWidth > viewportWidth) { - x = viewportWidth - menuWidth - 8; - } - if (y + menuHeight > viewportHeight) { - y = viewportHeight - menuHeight - 8; - } - - this.contextMenu.visible = true; - this.contextMenu.x = Math.max(8, x); - this.contextMenu.y = Math.max(8, y); - this.contextMenu.node = node; - }, - - hideContextMenu() { - if (!this.contextMenu.visible) { - return; - } - this.contextMenu.visible = false; - this.contextMenu.node = null; - }, - - async downloadFile(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/file?path=${encodeURIComponent(path)}`; - const name = path.split('/').pop() || 'file'; - await this.downloadResource(url, name); - }, - - async downloadFolder(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/folder?path=${encodeURIComponent(path)}`; - const segments = path.split('/').filter(Boolean); - const folderName = segments.length ? segments.pop() : 'folder'; - await this.downloadResource(url, `${folderName}.zip`); - }, - - async downloadResource(url, filename) { - try { - const response = await fetch(url); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - message = errorData.error || errorData.message || message; - } catch (err) { - message = await response.text(); - } - alert(`下载失败: ${message}`); - return; - } - - const blob = await response.blob(); - const downloadName = filename || 'download'; - const link = document.createElement('a'); - const href = URL.createObjectURL(blob); - link.href = href; - link.download = downloadName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(href); - } catch (error) { - console.error('下载失败:', error); - alert(`下载失败: ${error.message || error}`); - } finally { - this.hideContextMenu(); - } - }, - - initScrollListener() { - const messagesArea = this.$refs.messagesArea; - if (!messagesArea) { - console.warn('消息区域未找到'); - return; - } - - let isProgrammaticScroll = false; - const bottomThreshold = 12; - - this._setScrollingFlag = (flag) => { - isProgrammaticScroll = !!flag; - }; - - messagesArea.addEventListener('scroll', () => { - if (isProgrammaticScroll) { - return; - } - - const scrollTop = messagesArea.scrollTop; - const scrollHeight = messagesArea.scrollHeight; - const clientHeight = messagesArea.clientHeight; - const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; - - if (isAtBottom) { - this.userScrolling = false; - this.autoScrollEnabled = true; - } else { - this.userScrolling = true; - this.autoScrollEnabled = false; - } - }); - }, - - initSocket() { - try { - console.log('初始化WebSocket连接...'); - - const usePollingOnly = window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1'; - - this.socket = io('/', usePollingOnly ? { - transports: ['polling'], - upgrade: false - } : { - transports: ['websocket', 'polling'] - }); - - // 连接事件 - this.socket.on('connect', () => { - this.isConnected = true; - console.log('WebSocket已连接'); - // 连接时重置所有状态 - this.resetAllStates(); - }); - - this.socket.on('disconnect', () => { - this.isConnected = false; - console.log('WebSocket已断开'); - // 断线时也重置状态,防止状态混乱 - this.resetAllStates(); - }); - - this.socket.on('connect_error', (error) => { - console.error('WebSocket连接错误:', error.message); - }); - - // ========================================== - // Token统计WebSocket事件处理(修复版) - // ========================================== - - this.socket.on('token_update', (data) => { - console.log('收到token更新事件:', data); - - // 只处理当前对话的token更新 - if (data.conversation_id === this.currentConversationId) { - // 更新累计统计(使用后端提供的准确字段名) - this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; - - console.log(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); - - // 同时更新当前上下文Token(关键修复) - this.updateCurrentContextTokens(); - - this.$forceUpdate(); - } - }); - - this.socket.on('todo_updated', (data) => { - console.log('收到todo更新事件:', data); - if (data && data.conversation_id) { - this.currentConversationId = data.conversation_id; - } - this.todoList = data && data.todo_list ? data.todo_list : null; - }); - - // 系统就绪 - this.socket.on('system_ready', (data) => { - this.projectPath = data.project_path || ''; - this.thinkingMode = data.thinking_mode || '未知'; - console.log('系统就绪:', data); - - // 系统就绪后立即加载对话列表 - this.loadConversationsList(); - }); - - this.socket.on('tool_settings_updated', (data) => { - console.log('收到工具设置更新:', data); - if (data && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } - }); - - // ========================================== - // 对话管理相关Socket事件 - // ========================================== - - // 监听对话变更事件 - this.socket.on('conversation_changed', (data) => { - console.log('对话已切换:', data); - this.currentConversationId = data.conversation_id; - this.currentConversationTitle = data.title || ''; - - if (data.cleared) { - // 对话被清空 - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - // 重置Token统计 - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.loadConversationsList(); - this.fetchTodoList(); - }); - - this.socket.on('conversation_resolved', (data) => { - if (!data || !data.conversation_id) { - return; - } - const convId = data.conversation_id; - this.currentConversationId = convId; - if (data.title) { - this.currentConversationTitle = data.title; - } - const pathFragment = this.stripConversationPrefix(convId); - const currentPath = window.location.pathname.replace(/^\/+/, ''); - if (data.created) { - history.pushState({ conversationId: convId }, '', `/${pathFragment}`); - } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); - } - }); - - // 监听对话加载事件 - this.socket.on('conversation_loaded', (data) => { - console.log('对话已加载:', data); - if (data.clear_ui) { - // 清理当前UI状态,准备显示历史内容 - this.resetAllStates(); - } - - // 延迟获取并显示历史对话内容 - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 延迟获取Token统计(累计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - this.fetchTodoList(); - }, 500); - }); - - // 监听对话列表更新事件 - this.socket.on('conversation_list_update', (data) => { - console.log('对话列表已更新:', data); - // 刷新对话列表 - this.loadConversationsList(); - }); - - // 监听状态更新事件 - this.socket.on('status_update', (status) => { - // 更新系统状态信息 - if (status.conversation && status.conversation.current_id) { - this.currentConversationId = status.conversation.current_id; - } - }); - - // AI消息开始 - this.socket.on('ai_message_start', () => { - console.log('AI消息开始'); - this.cleanupStaleToolActions(); - const newMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(newMessage); - this.currentMessageIndex = this.messages.length - 1; - this.streamingMessage = true; - this.stopRequested = false; - this.autoScrollEnabled = true; - this.scrollToBottom(); - }); - - // 思考流开始 - this.socket.on('thinking_start', () => { - console.log('思考开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking = ''; - msg.currentStreamingType = 'thinking'; - - const action = { - id: Date.now() + Math.random(), - type: 'thinking', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - - const blockId = `${this.currentMessageIndex}-thinking-${msg.actions.length - 1}`; - this.expandedBlocks.add(blockId); - this.$forceUpdate(); - } - }); - - // 思考内容块 - this.socket.on('thinking_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 思考结束 - this.socket.on('thinking_end', (data) => { - console.log('思考结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.streaming = false; - lastAction.content = data.full_content; - } - msg.streamingThinking = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 文本流开始 - this.socket.on('text_start', () => { - console.log('文本开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText = ''; - msg.currentStreamingType = 'text'; - - const action = { - id: Date.now() + Math.random(), - type: 'text', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - } - }); - - // 文本内容块 - this.socket.on('text_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'text') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - - // 实时渲染LaTeX - this.renderLatexInRealtime(); - } - }); - - // 文本结束 - this.socket.on('text_end', (data) => { - console.log('文本结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找当前流式文本的action - for (let i = msg.actions.length - 1; i >= 0; i--) { - const action = msg.actions[i]; - if (action.type === 'text' && action.streaming) { - action.streaming = false; - action.content = data.full_content; - console.log('文本action已更新为完成状态'); - break; - } - } - - msg.streamingText = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 工具提示事件(可选) - this.socket.on('tool_hint', (data) => { - console.log('工具提示:', data.name); - // 可以在这里添加提示UI - }); - - // 工具准备中事件 - 实时显示 - this.socket.on('tool_preparing', (data) => { - console.log('工具准备中:', data.name); - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - const action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'preparing', - result: null, - message: data.message || `准备调用 ${data.name}...` - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.preparingTools.set(data.id, action); - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 工具状态更新事件 - 实时显示详细状态 - this.socket.on('tool_status', (data) => { - console.log('工具状态:', data); - const target = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (target) { - target.tool.statusDetail = data.detail; - target.tool.statusType = data.status; - this.$forceUpdate(); - return; - } - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找对应的工具action并更新状态 - for (let action of msg.actions) { - if (action.type === 'tool' && action.tool.name === data.tool) { - action.tool.statusDetail = data.detail; - action.tool.statusType = data.status; - this.$forceUpdate(); - break; - } - } - } - }); - - // 工具开始(从准备转为执行) - this.socket.on('tool_start', (data) => { - console.log('工具开始执行:', data.name); - let action = null; - if (data.preparing_id && this.preparingTools.has(data.preparing_id)) { - action = this.preparingTools.get(data.preparing_id); - this.preparingTools.delete(data.preparing_id); - } else { - action = this.findToolAction(data.id, data.preparing_id, data.execution_id); - } - if (!action) { - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'running', - result: null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - } - action.tool.status = 'running'; - action.tool.arguments = data.arguments; - action.tool.message = null; - action.tool.id = data.id; - action.tool.executionId = data.id; - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 更新action(工具完成) - this.socket.on('update_action', (data) => { - console.log('更新action:', data.id, 'status:', data.status); - let targetAction = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) { - targetAction = this.preparingTools.get(data.preparing_id); - } - if (!targetAction) { - outer: for (const message of this.messages) { - if (!message.actions) continue; - for (const action of message.actions) { - if (action.type !== 'tool') continue; - const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; - const matchByToolId = action.tool.id === data.id; - const matchByPreparingId = action.id === data.preparing_id; - if (matchByExecution || matchByToolId || matchByPreparingId) { - targetAction = action; - break outer; - } - } - } - } - if (targetAction) { - if (data.status) { - targetAction.tool.status = data.status; - } - if (data.result !== undefined) { - targetAction.tool.result = data.result; - } - if (data.message !== undefined) { - targetAction.tool.message = data.message; - } - if (data.awaiting_content) { - targetAction.tool.awaiting_content = true; - } else if (data.status === 'completed') { - targetAction.tool.awaiting_content = false; - } - if (!targetAction.tool.executionId && (data.execution_id || data.id)) { - targetAction.tool.executionId = data.execution_id || data.id; - } - this.registerToolAction(targetAction, data.execution_id || data.id); - if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { - this.unregisterToolAction(targetAction); - if (data.id) { - this.preparingTools.delete(data.id); - } - if (data.preparing_id) { - this.preparingTools.delete(data.preparing_id); - } - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - - // 关键修复:每个工具完成后都更新当前上下文Token - if (data.status === 'completed') { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - this.socket.on('append_payload', (data) => { - console.log('收到append_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: data.path || '未知文件', - forced: !!data.forced, - success: data.success === undefined ? true : !!data.success, - lines: data.lines ?? null, - bytes: data.bytes ?? null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - this.socket.on('modify_payload', (data) => { - console.log('收到modify_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: data.path || '未知文件', - total: data.total ?? null, - completed: data.completed || [], - failed: data.failed || [], - forced: !!data.forced - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 停止请求确认 - this.socket.on('stop_requested', (data) => { - console.log('停止请求已接收:', data.message); - // 可以显示提示信息 - }); - - // 任务停止 - this.socket.on('task_stopped', (data) => { - console.log('任务已停止:', data.message); - this.resetAllStates(); - }); - - // 任务完成(重点:更新Token统计) - this.socket.on('task_complete', (data) => { - console.log('任务完成', data); - this.resetAllStates(); - - // 任务完成后立即更新Token统计(关键修复) - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - this.fetchConversationTokenStatistics(); - } - }); - - // 聚焦文件更新 - this.socket.on('focused_files_update', (data) => { - this.focusedFiles = data || {}; - // 聚焦文件变化时更新当前上下文Token(关键修复) - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 文件树更新 - this.socket.on('file_tree_update', (data) => { - this.updateFileTree(data); - // 文件树变化时也可能影响上下文 - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 系统消息 - this.socket.on('system_message', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `system-${Date.now()}-${Math.random()}`, - type: 'system', - content: data.content, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } else { - this.addSystemMessage(data.content); - } - }); - - // 错误处理 - this.socket.on('error', (data) => { - this.addSystemMessage(`错误: ${data.message}`); - // 仅标记当前流结束,避免状态错乱 - this.streamingMessage = false; - this.stopRequested = false; - }); - - // 命令结果 - this.socket.on('command_result', (data) => { - if (data.command === 'clear' && data.success) { - this.messages = []; - this.currentMessageIndex = -1; - this.expandedBlocks.clear(); - // 清除对话时重置Token统计 - this.resetTokenStatistics(); - } else if (data.command === 'status' && data.success) { - this.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); - } else if (!data.success) { - this.addSystemMessage(`命令失败: ${data.message}`); - } - }); - - } catch (error) { - console.error('Socket初始化失败:', error); - } - }, - - registerToolAction(action, executionId = null) { - if (!action || action.type !== 'tool') { - return; - } - const keys = new Set(); - if (action.id) { - keys.add(action.id); - } - if (action.tool && action.tool.id) { - keys.add(action.tool.id); - } - if (executionId) { - keys.add(executionId); - } - if (action.tool && action.tool.executionId) { - keys.add(action.tool.executionId); - } - keys.forEach(key => { - if (!key) { - return; - } - this.toolActionIndex.set(key, action); - }); - }, - - unregisterToolAction(action) { - if (!action || action.type !== 'tool') { - return; - } - const keysToRemove = []; - for (const [key, stored] of this.toolActionIndex.entries()) { - if (stored === action) { - keysToRemove.push(key); - } - } - keysToRemove.forEach(key => this.toolActionIndex.delete(key)); - }, - - findToolAction(id, preparingId, executionId) { - if (!this.toolActionIndex) { - return null; - } - const candidates = [executionId, id, preparingId]; - for (const key of candidates) { - if (key && this.toolActionIndex.has(key)) { - return this.toolActionIndex.get(key); - } - } - return null; - }, - - cleanupStaleToolActions() { - this.messages.forEach(msg => { - if (!msg.actions) { - return; - } - msg.actions.forEach(action => { - if (action.type !== 'tool' || !action.tool) { - return; - } - if (['running', 'preparing'].includes(action.tool.status)) { - action.tool.status = 'stale'; - action.tool.message = action.tool.message || '已被新的响应中断'; - this.unregisterToolAction(action); - } - }); - }); - this.preparingTools.clear(); - this.toolActionIndex.clear(); - }, - - ensureAssistantMessage() { - if (this.currentMessageIndex >= 0) { - return this.messages[this.currentMessageIndex]; - } - const message = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(message); - this.currentMessageIndex = this.messages.length - 1; - return message; - }, - - // 完整重置所有状态 - resetAllStates() { - console.log('重置所有前端状态'); - this.hideContextMenu(); - - // 重置消息和流状态 - this.streamingMessage = false; - this.currentMessageIndex = -1; - this.stopRequested = false; - - // 清理工具状态 - this.preparingTools.clear(); - this.activeTools.clear(); - this.toolActionIndex.clear(); - - // ✨ 新增:将所有未完成的工具标记为已完成 - this.messages.forEach(msg => { - if (msg.role === 'assistant' && msg.actions) { - msg.actions.forEach(action => { - if (action.type === 'tool' && - (action.tool.status === 'preparing' || action.tool.status === 'running')) { - action.tool.status = 'completed'; - } - }); - } - }); - - // 重置滚动状态 - this.userScrolling = false; - this.autoScrollEnabled = true; - - // 清理Markdown缓存 - if (this.markdownCache) { - this.markdownCache.clear(); - } - - // 强制更新视图 - this.$forceUpdate(); - - this.settingsOpen = false; - this.toolMenuOpen = false; - this.toolSettingsLoading = false; - this.toolSettings = []; - - console.log('前端状态重置完成'); - }, - - // 重置Token统计 - resetTokenStatistics() { - this.currentContextTokens = 0; - this.currentConversationTokens = { - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - }; - }, - - async loadInitialData() { - try { - console.log('加载初始数据...'); - - const filesResponse = await fetch('/api/files'); - const filesData = await filesResponse.json(); - this.updateFileTree(filesData); - - const focusedResponse = await fetch('/api/focused'); - const focusedData = await focusedResponse.json(); - this.focusedFiles = focusedData || {}; - - await this.fetchTodoList(); - - const statusResponse = await fetch('/api/status'); - const statusData = await statusResponse.json(); - this.projectPath = statusData.project_path || ''; - this.thinkingMode = statusData.thinking_mode || '未知'; - - // 获取当前对话信息 - const statusConversationId = statusData.conversation && statusData.conversation.current_id; - if (statusConversationId) { - if (!this.currentConversationId) { - this.currentConversationId = statusConversationId; - } - // 如果有当前对话,尝试获取标题和Token统计 - try { - const convResponse = await fetch(`/api/conversations/current`); - const convData = await convResponse.json(); - if (convData.success && convData.data) { - this.currentConversationTitle = convData.data.title; - } - await this.fetchAndDisplayHistory(); - // 获取当前对话的Token统计 - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - } catch (e) { - console.warn('获取当前对话标题失败:', e); - } - } - - await this.loadToolSettings(true); - - console.log('初始数据加载完成'); - } catch (error) { - console.error('加载初始数据失败:', error); - } - }, - - async refreshFileTree() { - try { - const response = await fetch('/api/files'); - const data = await response.json(); - this.updateFileTree(data); - } catch (error) { - console.error('刷新文件树失败:', error); - } - }, - - // ========================================== - // Token统计相关方法(完全修复版) - // ========================================== - - async updateCurrentContextTokens() { - // 获取当前上下文Token数(动态计算,包含完整prompt构建) - if (!this.currentConversationId) { - this.currentContextTokens = 0; - return; - } - - try { - console.log(`正在更新当前上下文Token: ${this.currentConversationId}`); - - // 关键修复:使用正确的动态API,包含文件结构+记忆+聚焦文件+终端内容+工具定义 - const response = await fetch(`/api/conversations/${this.currentConversationId}/tokens`); - const data = await response.json(); - - if (data.success && data.data) { - this.currentContextTokens = data.data.total_tokens || 0; - console.log(`当前上下文Token更新: ${this.currentContextTokens}`); - this.$forceUpdate(); - } else { - console.warn('获取当前上下文Token失败:', data.error); - this.currentContextTokens = 0; - } - } catch (error) { - console.warn('获取当前上下文Token异常:', error); - this.currentContextTokens = 0; - } - }, - - async fetchConversationTokenStatistics() { - // 获取对话累计Token统计(加载对话时、任务完成后调用) - if (!this.currentConversationId) { - this.resetTokenStatistics(); - return; - } - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/token-statistics`); - const data = await response.json(); - - if (data.success && data.data) { - // 更新累计统计 - this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; - - console.log(`累计Token统计: 输入=${data.data.total_input_tokens}, 输出=${data.data.total_output_tokens}, 总计=${data.data.total_tokens}`); - this.$forceUpdate(); - } else { - console.warn('获取Token统计失败:', data.error); - // 保持当前统计,不重置 - } - } catch (error) { - console.warn('获取Token统计异常:', error); - // 保持当前统计,不重置 - } - }, - - // Token面板折叠/展开切换 - toggleTokenPanel() { - this.tokenPanelCollapsed = !this.tokenPanelCollapsed; - }, - - // ========================================== - // 对话管理核心功能 - // ========================================== - - async loadConversationsList() { - this.conversationsLoading = true; - try { - const response = await fetch(`/api/conversations?limit=${this.conversationsLimit}&offset=${this.conversationsOffset}`); - const data = await response.json(); - - if (data.success) { - if (this.conversationsOffset === 0) { - this.conversations = data.data.conversations; - } else { - this.conversations.push(...data.data.conversations); - } - this.hasMoreConversations = data.data.has_more; - console.log(`已加载 ${this.conversations.length} 个对话`); - - if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { - const latestConversation = this.conversations[0]; - if (latestConversation && latestConversation.id) { - await this.loadConversation(latestConversation.id); - } - } - } else { - console.error('加载对话列表失败:', data.error); - } - } catch (error) { - console.error('加载对话列表异常:', error); - } finally { - this.conversationsLoading = false; - } - }, - - async loadMoreConversations() { - if (this.loadingMoreConversations || !this.hasMoreConversations) return; - - this.loadingMoreConversations = true; - this.conversationsOffset += this.conversationsLimit; - await this.loadConversationsList(); - this.loadingMoreConversations = false; - }, - - async loadConversation(conversationId) { - console.log('加载对话:', conversationId); - - if (conversationId === this.currentConversationId) { - console.log('已是当前对话,跳过加载'); - return; - } - - try { - // 1. 调用加载API - const response = await fetch(`/api/conversations/${conversationId}/load`, { - method: 'PUT' - }); - const result = await response.json(); - - if (result.success) { - console.log('对话加载API成功:', result); - - // 2. 更新当前对话信息 - this.currentConversationId = conversationId; - this.currentConversationTitle = result.title; - history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`); - - // 3. 重置UI状态 - this.resetAllStates(); - - // 4. 延迟获取并显示历史对话内容(关键功能) - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - }, 500); - - } else { - console.error('对话加载失败:', result.message); - alert(`加载对话失败: ${result.message}`); - } - } catch (error) { - console.error('加载对话异常:', error); - alert(`加载对话异常: ${error.message}`); - } - }, - - // ========================================== - // 关键功能:获取并显示历史对话内容 - // ========================================== - async fetchAndDisplayHistory() { - console.log('开始获取历史对话内容...'); - - if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { - console.log('没有当前对话ID,跳过历史加载'); - return; - } - - try { - // 使用专门的API获取对话消息历史 - const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); - - if (!messagesResponse.ok) { - console.warn('无法获取消息历史,尝试备用方法'); - // 备用方案:通过状态API获取 - const statusResponse = await fetch('/api/status'); - const status = await statusResponse.json(); - console.log('系统状态:', status); - - // 如果状态中有对话历史字段 - if (status.conversation_history && Array.isArray(status.conversation_history)) { - this.renderHistoryMessages(status.conversation_history); - return; - } - - console.log('备用方案也无法获取历史消息'); - return; - } - - const messagesData = await messagesResponse.json(); - console.log('获取到消息数据:', messagesData); - - if (messagesData.success && messagesData.data && messagesData.data.messages) { - const messages = messagesData.data.messages; - console.log(`发现 ${messages.length} 条历史消息`); - - if (messages.length > 0) { - // 清空当前显示的消息 - this.messages = []; - - // 渲染历史消息 - 这是关键功能 - this.renderHistoryMessages(messages); - - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - - console.log('历史对话内容显示完成'); - } else { - console.log('对话存在但没有历史消息'); - this.messages = []; - } - } else { - console.log('消息数据格式不正确:', messagesData); - this.messages = []; - } - - } catch (error) { - console.error('获取历史对话失败:', error); - console.log('尝试不显示错误弹窗,仅在控制台记录'); - // 不显示alert,避免打断用户体验 - this.messages = []; - } - }, - - // ========================================== - // 关键功能:渲染历史消息 - // ========================================== - renderHistoryMessages(historyMessages) { - console.log('开始渲染历史消息...', historyMessages); - console.log('历史消息数量:', historyMessages.length); - - if (!Array.isArray(historyMessages)) { - console.error('历史消息不是数组格式'); - return; - } - - let currentAssistantMessage = null; - - historyMessages.forEach((message, index) => { - console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); - - if (message.role === 'user') { - // 用户消息 - 先结束之前的assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - this.messages.push({ - role: 'user', - content: message.content || '' - }); - console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); - - } else if (message.role === 'assistant') { - // AI消息 - 如果没有当前assistant消息,创建一个 - if (!currentAssistantMessage) { - currentAssistantMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - } - - // 处理思考内容 - 支持多种格式 - const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; - - let allThinkingContent = ''; - for (const pattern of thinkPatterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - allThinkingContent += match[1].trim() + '\n'; - } - } - - if (allThinkingContent) { - currentAssistantMessage.actions.push({ - id: `history-think-${Date.now()}-${Math.random()}`, - type: 'thinking', - content: allThinkingContent.trim(), - streaming: false, - timestamp: Date.now() - }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); - } - - // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - - let textContent = content - .replace(/[\s\S]*?<\/think>/g, '') - .replace(/[\s\S]*?<\/thinking>/g, '') - .trim(); - - if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: appendPayloadMeta.path || '未知文件', - forced: !!appendPayloadMeta.forced, - success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, - lines: appendPayloadMeta.lines ?? null, - bytes: appendPayloadMeta.bytes ?? null - }, - timestamp: Date.now() - }); - console.log('添加append占位信息:', appendPayloadMeta.path); - } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: modifyPayloadMeta.path || '未知文件', - total: modifyPayloadMeta.total_blocks ?? null, - completed: modifyPayloadMeta.completed || [], - failed: modifyPayloadMeta.failed || [], - forced: !!modifyPayloadMeta.forced, - details: modifyPayloadMeta.details || [] - }, - timestamp: Date.now() - }); - console.log('添加modify占位信息:', modifyPayloadMeta.path); - } - - if (textContent && !appendPayloadMeta && !modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-text-${Date.now()}-${Math.random()}`, - type: 'text', - content: textContent, - streaming: false, - timestamp: Date.now() - }); - console.log('添加文本内容:', textContent.substring(0, 50) + '...'); - } - - // 处理工具调用 - if (message.tool_calls && Array.isArray(message.tool_calls)) { - message.tool_calls.forEach((toolCall, tcIndex) => { - let arguments_obj = {}; - try { - arguments_obj = typeof toolCall.function.arguments === 'string' - ? JSON.parse(toolCall.function.arguments || '{}') - : (toolCall.function.arguments || {}); - } catch (e) { - console.warn('解析工具参数失败:', e); - arguments_obj = {}; - } - - currentAssistantMessage.actions.push({ - id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, - type: 'tool', - tool: { - id: toolCall.id, - name: toolCall.function.name, - arguments: arguments_obj, - status: 'preparing', - result: null - }, - timestamp: Date.now() - }); - console.log('添加工具调用:', toolCall.function.name); - }); - } - - } else if (message.role === 'tool') { - // 工具结果 - 更新当前assistant消息中对应的工具 - if (currentAssistantMessage) { - // 查找对应的工具action - 使用更灵活的匹配 - let toolAction = null; - - // 优先按tool_call_id匹配 - if (message.tool_call_id) { - toolAction = currentAssistantMessage.actions.find(action => - action.type === 'tool' && - action.tool.id === message.tool_call_id - ); - } - - // 如果找不到,按name匹配最后一个同名工具 - if (!toolAction && message.name) { - const sameNameTools = currentAssistantMessage.actions.filter(action => - action.type === 'tool' && - action.tool.name === message.name - ); - toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 - } - - if (toolAction) { - // 解析工具结果 - let result; - try { - // 尝试解析为JSON - result = JSON.parse(message.content); - } catch (e) { - // 如果不是JSON,就作为纯文本 - result = { - output: message.content, - success: true - }; - } - - toolAction.tool.status = 'completed'; - toolAction.tool.result = result; - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } - console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - if (message.name === 'append_to_file' && result && typeof result === 'object') { - const appendSummary = { - path: result.path || '未知文件', - success: result.success !== false, - summary: result.message || (result.success === false ? '追加失败' : '追加完成'), - lines: result.lines || 0, - bytes: result.bytes || 0, - forced: !!result.forced - }; - currentAssistantMessage.actions.push({ - id: `history-append-${Date.now()}-${Math.random()}`, - type: 'append', - append: appendSummary, - timestamp: Date.now() - }); - } - } else { - console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); - } - } - - } else { - // 其他类型消息(如system)- 先结束当前assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - console.log('处理其他类型消息:', message.role); - this.messages.push({ - role: message.role, - content: message.content || '' - }); - } - }); - - // 处理最后一个assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - } - - console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); - - // 强制更新视图 - this.$forceUpdate(); - - // 确保滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - }, - - async createNewConversation() { - console.log('创建新对话...'); - - try { - const response = await fetch('/api/conversations', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thinking_mode: this.thinkingMode !== '快速模式' - }) - }); - - const result = await response.json(); - - if (result.success) { - console.log('新对话创建成功:', result.conversation_id); - - // 清空当前消息 - this.messages = []; - this.currentConversationId = result.conversation_id; - this.currentConversationTitle = '新对话'; - history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); - - // 重置Token统计 - this.resetTokenStatistics(); - - // 重置状态 - this.resetAllStates(); - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('创建对话失败:', result.message); - alert(`创建对话失败: ${result.message}`); - } - } catch (error) { - console.error('创建对话异常:', error); - alert(`创建对话异常: ${error.message}`); - } - }, - - async deleteConversation(conversationId) { - if (!confirm('确定要删除这个对话吗?删除后无法恢复。')) { - return; - } - - console.log('删除对话:', conversationId); - - try { - const response = await fetch(`/api/conversations/${conversationId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - console.log('对话删除成功'); - - // 如果删除的是当前对话,清空界面 - if (conversationId === this.currentConversationId) { - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - this.resetAllStates(); - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('删除对话失败:', result.message); - alert(`删除对话失败: ${result.message}`); - } - } catch (error) { - console.error('删除对话异常:', error); - alert(`删除对话异常: ${error.message}`); - } - }, - - async duplicateConversation(conversationId) { - console.log('复制对话:', conversationId); - try { - const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.duplicate_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - - this.conversationsOffset = 0; - await this.loadConversationsList(); - } else { - const message = result.message || result.error || '复制失败'; - alert(`复制失败: ${message}`); - } - } catch (error) { - console.error('复制对话异常:', error); - alert(`复制对话异常: ${error.message}`); - } - }, - - searchConversations() { - // 简单的搜索功能,实际实现可以调用搜索API - if (this.searchTimer) { - clearTimeout(this.searchTimer); - } - - this.searchTimer = setTimeout(() => { - if (this.searchQuery.trim()) { - console.log('搜索对话:', this.searchQuery); - // TODO: 实现搜索API调用 - // this.searchConversationsAPI(this.searchQuery); - } else { - // 清空搜索,重新加载全部对话 - this.conversationsOffset = 0; - this.loadConversationsList(); - } - }, 300); - }, - - toggleSidebar() { - this.sidebarCollapsed = !this.sidebarCollapsed; - }, - - formatTime(timeString) { - if (!timeString) return ''; - - const date = new Date(timeString); - const now = new Date(); - const diffMs = now - date; - const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - const diffDays = Math.floor(diffHours / 24); - - if (diffHours < 1) { - return '刚刚'; - } else if (diffHours < 24) { - return `${diffHours}小时前`; - } else if (diffDays < 7) { - return `${diffDays}天前`; - } else { - return date.toLocaleDateString('zh-CN', { - month: 'short', - day: 'numeric' - }); - } - }, - - // ========================================== - // 原有功能保持不变 - // ========================================== - - updateFileTree(structure) { - const treeDictionary = structure && structure.tree ? structure.tree : {}; - - const buildNodes = (treeMap) => { - if (!treeMap) { - return []; - } - - const entries = Object.keys(treeMap).map((name) => { - const node = treeMap[name] || {}; - if (node.type === 'folder') { - return { - type: 'folder', - name, - path: node.path || name, - children: buildNodes(node.children) - }; - } - return { - type: 'file', - name, - path: node.path || name, - annotation: node.annotation || '' - }; - }); - - entries.sort((a, b) => { - if (a.type !== b.type) { - return a.type === 'folder' ? -1 : 1; - } - return a.name.localeCompare(b.name, 'zh-CN'); - }); - - return entries; - }; - - const nodes = buildNodes(treeDictionary); - - const expanded = { ...this.expandedFolders }; - const validFolderPaths = new Set(); - - const ensureExpansion = (list, depth = 0) => { - list.forEach((item) => { - if (item.type === 'folder') { - validFolderPaths.add(item.path); - if (expanded[item.path] === undefined) { - expanded[item.path] = false; - } - ensureExpansion(item.children || [], depth + 1); - } - }); - }; - - ensureExpansion(nodes); - - Object.keys(expanded).forEach((path) => { - if (!validFolderPaths.has(path)) { - delete expanded[path]; - } - }); - - this.expandedFolders = expanded; - this.fileTree = nodes; - }, - - toggleFolder(path) { - this.hideContextMenu(); - if (!path) { - return; - } - const current = !!this.expandedFolders[path]; - this.expandedFolders = { - ...this.expandedFolders, - [path]: !current - }; - }, - - toggleTodoPanel() { - this.showTodoList = !this.showTodoList; - }, - - formatTaskStatus(task) { - if (!task) { - return ''; - } - return task.status === 'done' - ? `${this.todoDoneEmoji} 完成` - : `${this.todoPendingEmoji} 未完成`; - }, - - toolCategoryEmoji(categoryId) { - return this.toolCategoryEmojis[categoryId] || '⚙️'; - }, - - async fetchTodoList() { - try { - const response = await fetch('/api/todo-list'); - const data = await response.json(); - if (data.success) { - this.todoList = data.data || null; - } - } catch (error) { - console.error('获取待办列表失败:', error); - } - }, - - triggerFileUpload() { - if (this.uploading) { - return; - } - const input = this.$refs.fileUploadInput; - if (input) { - input.click(); - } - }, - - handleFileSelected(event) { - const fileInput = event?.target; - if (!fileInput || !fileInput.files || fileInput.files.length === 0) { - return; - } - const [file] = fileInput.files; - this.uploadSelectedFile(file); - }, - - resetFileInput() { - const input = this.$refs.fileUploadInput; - if (input) { - input.value = ''; - } - }, - - async uploadSelectedFile(file) { - if (!file || this.uploading) { - this.resetFileInput(); - return; - } - - this.uploading = true; - - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/upload', { - method: 'POST', - body: formData - }); - - let result = {}; - try { - result = await response.json(); - } catch (parseError) { - throw new Error('服务器响应无法解析'); - } - - if (!response.ok || !result.success) { - const message = result.error || result.message || '上传失败'; - throw new Error(message); - } - - await this.refreshFileTree(); - alert(`上传成功:${result.path || file.name}`); - } catch (error) { - console.error('文件上传失败:', error); - alert(`文件上传失败:${error.message}`); - } finally { - this.uploading = false; - this.resetFileInput(); - } - }, - - handleSendOrStop() { - if (this.streamingMessage) { - this.stopTask(); - } else { - this.sendMessage(); - } - }, - - sendMessage() { - if (this.streamingMessage || !this.isConnected) { - return; - } - - if (!this.inputMessage.trim()) { - return; - } - - const message = this.inputMessage; - - if (message.startsWith('/')) { - this.socket.emit('send_command', { command: message }); - this.inputMessage = ''; - this.settingsOpen = false; - return; - } - - this.messages.push({ - role: 'user', - content: message - }); - - this.currentMessageIndex = -1; - this.socket.emit('send_message', { message: message, conversation_id: this.currentConversationId }); - this.inputMessage = ''; - this.autoScrollEnabled = true; - this.scrollToBottom(); - this.settingsOpen = false; - - // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) - setTimeout(() => { - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - } - }, 1000); - }, - - // 新增:停止任务方法 - stopTask() { - if (this.streamingMessage && !this.stopRequested) { - this.socket.emit('stop_task'); - this.stopRequested = true; - console.log('发送停止请求'); - } - this.settingsOpen = false; - }, - - clearChat() { - if (confirm('确定要清除所有对话记录吗?')) { - this.socket.emit('send_command', { command: '/clear' }); - } - this.settingsOpen = false; - }, - - async compressConversation() { - if (!this.currentConversationId) { - alert('当前没有可压缩的对话。'); - return; - } - - if (this.compressing) { - return; - } - - const confirmed = confirm('确定要压缩当前对话记录吗?压缩后会生成新的对话副本。'); - if (!confirmed) { - return; - } - - this.settingsOpen = false; - this.compressing = true; - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.compressed_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - console.log('对话压缩完成:', result); - } else { - const message = result.message || result.error || '压缩失败'; - alert(`压缩失败: ${message}`); - } - } catch (error) { - console.error('压缩对话异常:', error); - alert(`压缩对话异常: ${error.message}`); - } finally { - this.compressing = false; - } - }, - - toggleToolMenu() { - if (!this.isConnected) { - return; - } - const nextState = !this.toolMenuOpen; - this.toolMenuOpen = nextState; - if (nextState) { - this.settingsOpen = false; - this.loadToolSettings(); - } - }, - - handleClickOutsideToolMenu(event) { - if (!this.toolMenuOpen) { - return; - } - const dropdown = this.$refs.toolDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.toolMenuOpen = false; - } - }, - - applyToolSettingsSnapshot(categories) { - if (!Array.isArray(categories)) { - return; - } - this.toolSettings = categories.map((item) => ({ - id: item.id, - label: item.label || item.id, - enabled: !!item.enabled, - tools: Array.isArray(item.tools) ? item.tools : [] - })); - this.toolSettingsLoading = false; - }, - - async loadToolSettings(force = false) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - if (!force && this.toolSettings.length > 0) { - return; - } - this.toolSettingsLoading = true; - try { - const response = await fetch('/api/tool-settings'); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('获取工具设置失败:', data); - this.toolSettingsLoading = false; - } - } catch (error) { - console.error('获取工具设置异常:', error); - this.toolSettingsLoading = false; - } - }, - - async updateToolCategory(categoryId, enabled) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - const previousSnapshot = this.toolSettings.map((item) => ({ ...item })); - this.toolSettings = this.toolSettings.map((item) => { - if (item.id === categoryId) { - return { ...item, enabled }; - } - return item; - }); - try { - const response = await fetch('/api/tool-settings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - category: categoryId, - enabled - }) - }); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('更新工具设置失败:', data); - this.toolSettings = previousSnapshot; - } - } catch (error) { - console.error('更新工具设置异常:', error); - this.toolSettings = previousSnapshot; - } - this.toolSettingsLoading = false; - }, - - toggleSettings() { - if (!this.isConnected) { - return; - } - this.settingsOpen = !this.settingsOpen; - if (this.settingsOpen) { - this.toolMenuOpen = false; - } - }, - - toggleFocusPanel() { - this.rightCollapsed = !this.rightCollapsed; - if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - this.settingsOpen = false; - }, - - handleClickOutsideSettings(event) { - if (!this.settingsOpen) { - return; - } - const dropdown = this.$refs.settingsDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.settingsOpen = false; - } - }, - - addSystemMessage(content) { - this.messages.push({ - role: 'system', - content: content - }); - this.conditionalScrollToBottom(); - }, - - toggleBlock(id) { - if (this.expandedBlocks.has(id)) { - this.expandedBlocks.delete(id); - } else { - this.expandedBlocks.add(id); - } - this.$forceUpdate(); - }, - - // 修复:工具相关方法 - 接收tool对象而不是name - getToolIcon(tool) { - const toolName = typeof tool === 'string' ? tool : tool.name; - const icons = { - 'create_file': '📄', - 'sleep': '⏱️', - 'read_file': '📖', - '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': '❗' - }; - return icons[toolName] || '⚙️'; - }, - - getToolAnimationClass(tool) { - // 根据工具状态返回不同的动画类 - if (tool.status === 'hinted') { - return 'hint-animation pulse-slow'; - } else if (tool.status === 'preparing') { - return 'preparing-animation'; - } else if (tool.status === 'running') { - const animations = { - 'create_file': 'file-animation', - 'read_file': 'read-animation', - 'delete_file': 'file-animation', - 'rename_file': 'file-animation', - 'modify_file': 'file-animation', - 'append_to_file': 'file-animation', - 'create_folder': 'file-animation', - 'focus_file': 'focus-animation', - 'unfocus_file': 'focus-animation', - 'web_search': 'search-animation', - 'extract_webpage': 'search-animation', - 'save_webpage': 'file-animation', - 'run_python': 'code-animation', - 'run_command': 'terminal-animation', - 'update_memory': 'memory-animation', - 'sleep': 'wait-animation', - 'terminal_session': 'terminal-animation', - 'terminal_input': 'terminal-animation', - 'terminal_snapshot': 'terminal-animation', - 'terminal_reset': 'terminal-animation', - 'todo_create': 'file-animation', - 'todo_update_task': 'file-animation', - 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' - }; - return animations[tool.name] || 'default-animation'; - } - return ''; - }, - - // 修复:获取工具状态文本 - getToolStatusText(tool) { - // 优先使用自定义消息 - if (tool.message) { - return tool.message; - } - - if (tool.status === 'hinted') { - return `可能需要 ${tool.name}...`; - } else if (tool.status === 'preparing') { - return `准备调用 ${tool.name}...`; - } else if (tool.status === 'running') { - const texts = { - 'create_file': '正在创建文件...', - 'read_file': '正在读取文件...', - 'sleep': '正在等待...', - 'delete_file': '正在删除文件...', - 'rename_file': '正在重命名文件...', - 'modify_file': '正在修改文件...', - 'append_to_file': '正在追加文件...', - 'create_folder': '正在创建文件夹...', - 'focus_file': '正在聚焦文件...', - 'unfocus_file': '正在取消聚焦...', - 'web_search': '正在搜索网络...', - 'extract_webpage': '正在提取网页...', - 'save_webpage': '正在保存网页...', - 'run_python': '正在执行Python代码...', - 'run_command': '正在执行命令...', - 'update_memory': '正在更新记忆...', - 'terminal_session': '正在管理终端会话...', - 'terminal_input': '正在发送终端输入...', - 'terminal_snapshot': '正在获取终端快照...', - 'terminal_reset': '正在重置终端...' - }; - return texts[tool.name] || '正在执行...'; - } else if (tool.status === 'completed') { - // 修复:完成状态的文本 - const texts = { - 'create_file': '文件创建成功', - 'read_file': '文件读取完成', - 'delete_file': '文件删除成功', - 'sleep': '等待完成', - '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': '终端已重置' - }; - return texts[tool.name] || '执行完成'; - } else { - // 其他状态 - return `${tool.name} - ${tool.status}`; - } - }, - - getToolDescription(tool) { - // 如果有状态详情,优先显示 - if (tool.statusDetail) { - return tool.statusDetail; - } - - if (tool.result && typeof tool.result === 'object') { - if (tool.result.path) { - return tool.result.path.split('/').pop(); - } - } - - if (tool.arguments) { - if (tool.arguments.path) { - return tool.arguments.path.split('/').pop(); - } - if (tool.arguments.target_path) { - return tool.arguments.target_path.split('/').pop(); - } - if (tool.arguments.query) { - return `"${tool.arguments.query}"`; - } - if (tool.arguments.command) { - return tool.arguments.command; - } - if (tool.arguments.seconds) { - return `${tool.arguments.seconds} 秒`; - } - } - return ''; - }, - - formatSearchTopic(filters) { - const mapping = { - 'general': '通用', - 'news': '新闻', - 'finance': '金融' - }; - const topic = (filters && filters.topic) ? String(filters.topic).toLowerCase() : 'general'; - return mapping[topic] || '通用'; - }, - - formatSearchTime(filters) { - if (!filters) { - return '未限定时间'; - } - if (filters.time_range) { - const mapping = { - 'day': '过去24小时', - 'week': '过去7天', - 'month': '过去30天', - 'year': '过去365天' - }; - return mapping[filters.time_range] || `相对范围:${filters.time_range}`; - } - if (typeof filters.days === 'number') { - return `过去${filters.days}天`; - } - if (filters.start_date && filters.end_date) { - return `${filters.start_date} 至 ${filters.end_date}`; - } - return '未限定时间'; - }, - - renderMarkdown(text, isStreaming = false) { - if (!text) return ''; - - if (typeof marked === 'undefined') { - return text; - } - - marked.setOptions({ - breaks: true, - gfm: true, - sanitize: false - }); - - if (!isStreaming) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - - const cacheKey = `${text.length}_${text.substring(0, 100)}`; - - if (this.markdownCache.has(cacheKey)) { - return this.markdownCache.get(cacheKey); - } - } - - let html = marked.parse(text); - html = this.wrapCodeBlocks(html, isStreaming); - - if (!isStreaming && text.length < 10000) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - this.markdownCache.set(`${text.length}_${text.substring(0, 100)}`, html); - if (this.markdownCache.size > 20) { - const firstKey = this.markdownCache.keys().next().value; - this.markdownCache.delete(firstKey); - } - } - - // 只在非流式状态处理(流式状态由renderLatexInRealtime处理) - if (!isStreaming) { - setTimeout(() => { - // 代码高亮 - if (typeof Prism !== 'undefined') { - const codeBlocks = document.querySelectorAll('.code-block-wrapper pre code:not([data-highlighted])'); - codeBlocks.forEach(block => { - try { - Prism.highlightElement(block); - block.setAttribute('data-highlighted', 'true'); - } catch (e) { - console.warn('代码高亮失败:', e); - } - }); - } - - // LaTeX最终渲染 - if (typeof renderMathInElement !== 'undefined') { - const elements = document.querySelectorAll('.text-output .text-content:not(.streaming-text)'); - elements.forEach(element => { - if (element.hasAttribute('data-math-rendered')) return; - - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - element.setAttribute('data-math-rendered', 'true'); - } catch (e) { - console.warn('LaTeX渲染失败:', e); - } - }); - } - }, 100); - } - - return html; - }, - // 实时LaTeX渲染(用于流式输出) - renderLatexInRealtime() { - if (typeof renderMathInElement === 'undefined') { - return; - } - - // 使用requestAnimationFrame优化性能 - if (this._latexRenderTimer) { - cancelAnimationFrame(this._latexRenderTimer); - } - - this._latexRenderTimer = requestAnimationFrame(() => { - const elements = document.querySelectorAll('.text-output .streaming-text'); - elements.forEach(element => { - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - } catch (e) { - // 忽略错误,继续渲染 - } - }); - }); - }, - // 用字符串替换包装代码块 - // 用字符串替换包装代码块 - 添加streaming参数 - wrapCodeBlocks(html, isStreaming = false) { - // 如果是流式输出,不包装代码块,保持原样 - if (isStreaming) { - return html; - } - - let counter = 0; - - // 匹配
...
- return html.replace(/
]*)>([\s\S]*?)<\/code><\/pre>/g, (match, attributes, content) => {
-                    // 提取语言
-                    const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
-                    const language = langMatch ? langMatch[1] : 'text';
-                    
-                    // 生成唯一ID
-                    const blockId = `code-${Date.now()}-${counter++}`;
-                    
-                    // 转义引号用于data属性
-                    const escapedContent = content
-                        .replace(/&/g, '&')
-                        .replace(//g, '>')
-                        .replace(/"/g, '"');
-                    
-                    // 构建新的HTML,保持code元素原样
-                    return `
-            
-
- ${language} - -
-
${content}
-
`; - }); - }, - - getLanguageClass(path) { - const ext = path.split('.').pop().toLowerCase(); - const langMap = { - 'py': 'language-python', - 'js': 'language-javascript', - 'html': 'language-html', - 'css': 'language-css', - 'json': 'language-json', - 'md': 'language-markdown', - 'txt': 'language-plain' - }; - return langMap[ext] || 'language-plain'; - }, - - scrollToBottom() { - setTimeout(() => { - const messagesArea = this.$refs.messagesArea; - if (messagesArea) { - // 标记为程序触发的滚动 - if (this._setScrollingFlag) { - this._setScrollingFlag(true); - } - - messagesArea.scrollTop = messagesArea.scrollHeight; - - // 滚动完成后重置标记 - setTimeout(() => { - if (this._setScrollingFlag) { - this._setScrollingFlag(false); - } - }, 100); - } - }, 50); - }, - - conditionalScrollToBottom() { - // 严格检查:只在明确允许时才滚动 - if (this.autoScrollEnabled === true && this.userScrolling === false) { - this.scrollToBottom(); - } - }, - - toggleScrollLock() { - const currentlyLocked = this.autoScrollEnabled && !this.userScrolling; - if (currentlyLocked) { - this.autoScrollEnabled = false; - this.userScrolling = true; - } else { - this.autoScrollEnabled = true; - this.userScrolling = false; - this.scrollToBottom(); - } - }, - - // 面板调整方法 - startResize(panel, event) { - this.isResizing = true; - this.resizingPanel = panel; - if (panel === 'right' && this.rightCollapsed) { - this.rightCollapsed = false; - if (this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - } - document.addEventListener('mousemove', this.handleResize); - document.addEventListener('mouseup', this.stopResize); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; - event.preventDefault(); - }, - - handleResize(event) { - if (!this.isResizing) return; - - const containerWidth = document.querySelector('.main-container').offsetWidth; - - if (this.resizingPanel === 'left') { - let newWidth = event.clientX - (this.sidebarCollapsed ? 60 : 300); - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.leftWidth = newWidth; - } else if (this.resizingPanel === 'right') { - let newWidth = containerWidth - event.clientX; - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.rightWidth = newWidth; - } else if (this.resizingPanel === 'conversation') { - // 对话侧边栏宽度调整 - let newWidth = event.clientX; - newWidth = Math.max(200, Math.min(newWidth, 400)); - // 这里可以动态调整对话侧边栏宽度,暂时不实现 - } - }, - - stopResize() { - this.isResizing = false; - this.resizingPanel = null; - document.removeEventListener('mousemove', this.handleResize); - document.removeEventListener('mouseup', this.stopResize); - document.body.style.userSelect = ''; - document.body.style.cursor = ''; - }, - - // 格式化token显示(修复NaN问题) - formatTokenCount(tokens) { - // 确保tokens是数字,防止NaN - const num = Number(tokens) || 0; - if (num < 1000) { - return num.toString(); - } else if (num < 1000000) { - return (num / 1000).toFixed(1) + 'K'; - } else { - return (num / 1000000).toFixed(1) + 'M'; - } - } - } - }); - - app.component('file-node', { - name: 'FileNode', - props: { - node: { - type: Object, - required: true - }, - level: { - type: Number, - default: 0 - }, - expandedFolders: { - type: Object, - required: true - } - }, - emits: ['toggle-folder', 'context-menu'], - computed: { - isExpanded() { - if (this.node.type !== 'folder') { - return false; - } - const value = this.expandedFolders[this.node.path]; - return value === undefined ? true : value; - }, - folderPadding() { - return { - paddingLeft: `${12 + this.level * 16}px` - }; - }, - filePadding() { - return { - paddingLeft: `${40 + this.level * 16}px` - }; - } - }, - methods: { - toggle() { - if (this.node.type === 'folder') { - this.$emit('toggle-folder', this.node.path); - } - } - }, - template: ` -
-
- -
- -
-
-
- 📄 - {{ node.name }} - {{ node.annotation }} -
-
- ` - }); - - app.mount('#app'); - console.log('Vue应用初始化完成'); - -}; diff --git a/sub_agent/static/backup_20251026_183122/claude-colors-simple.html b/sub_agent/static/backup_20251026_183122/claude-colors-simple.html deleted file mode 100644 index aebd82d..0000000 --- a/sub_agent/static/backup_20251026_183122/claude-colors-simple.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - Claude颜色展示 - 简化版 - - - -
-

Claude聊天界面颜色

- -

核心颜色

-
-
-
-
背景颜色
-
#eeece2
-
-
- -
-
-
-
字体颜色
-
#3d3929
-
-
- -
-
-
-
品牌主色
-
#da7756
-
-
- -
-
-
-
按钮颜色
-
#bd5d3a
-
-
- -

字体样式

-
- 字体栈:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif -

- 斜体样式展示 | 粗体样式展示 -
- -

界面元素

- - -

对话界面模拟

-
-
你好,Claude!今天天气怎么样?
-
你好!我无法获取实时天气信息,但你可以通过天气应用查看当地天气。
-
- -
- 这种配色方案创造出温暖、专业的对话环境,背景是米白色(#eeece2),文字是深棕色(#3d3929),给人舒适友好的感觉。 -
-
- - diff --git a/sub_agent/static/backup_20251026_183122/debug.html b/sub_agent/static/backup_20251026_183122/debug.html deleted file mode 100644 index 0a945c9..0000000 --- a/sub_agent/static/backup_20251026_183122/debug.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - AI Agent 调试监控 - - - - - -
-

🔧 AI Agent 调试监控

- - -
-

连接状态

-
Socket连接: {{ isConnected ? '已连接' : '未连接' }}
-
当前消息索引: {{ currentMessageIndex }}
-
消息总数: {{ messages.length }}
-
- - -
-

控制面板

- - - - -
- - -
-

WebSocket事件流 (最新 {{ events.length }} 条)

-
-
- {{ event.time }} - {{ event.type }} - : {{ JSON.stringify(event.data).slice(0, 200) }} -
-
-
- - -
-

当前消息Actions状态 (消息 #{{ currentMessageIndex }})

-
-
-
Action #{{ idx }}: {{ action.type }}
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
- 工具: {{ action.tool.name }}
- 状态: {{ action.tool.status }}
- ID: {{ action.tool.id }}
- 有结果: {{ !!action.tool.result }} -
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
-
-
- - -
-

最新消息原始数据

-
{{ JSON.stringify(messages[messages.length - 1], null, 2) }}
-
-
- - - - \ No newline at end of file diff --git a/sub_agent/static/backup_20251026_183122/index.html b/sub_agent/static/backup_20251026_183122/index.html deleted file mode 100644 index baa0ec5..0000000 --- a/sub_agent/static/backup_20251026_183122/index.html +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - AI Agent System - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-

正在连接服务器...

-

如果长时间无响应,请刷新页面

-
- - - -
- - -
-
- - - - - diff --git a/sub_agent/static/backup_20251026_183122/login.html b/sub_agent/static/backup_20251026_183122/login.html deleted file mode 100644 index cff0db6..0000000 --- a/sub_agent/static/backup_20251026_183122/login.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 登录 - AI Agent - - - - - - - diff --git a/sub_agent/static/backup_20251026_183122/register.html b/sub_agent/static/backup_20251026_183122/register.html deleted file mode 100644 index 0943626..0000000 --- a/sub_agent/static/backup_20251026_183122/register.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - 注册 - AI Agent - - - -
-

创建账号

-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - - diff --git a/sub_agent/static/backup_20251026_183122/style.css b/sub_agent/static/backup_20251026_183122/style.css deleted file mode 100644 index 5ba5ef8..0000000 --- a/sub_agent/static/backup_20251026_183122/style.css +++ /dev/null @@ -1,2021 +0,0 @@ -/* static/style-enhanced.css - 增强版,包含对话历史管理功能 */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --claude-bg: #eeece2; - --claude-panel: rgba(255, 255, 255, 0.82); - --claude-sidebar: rgba(255, 255, 255, 0.68); - --claude-border: rgba(118, 103, 84, 0.25); - --claude-text: #3d3929; - --claude-text-secondary: #7f7766; - --claude-muted: rgba(121, 109, 94, 0.4); - --claude-accent: #da7756; - --claude-accent-strong: #bd5d3a; - --claude-highlight: rgba(218, 119, 86, 0.14); - --claude-button-hover: #c76541; - --claude-button-active: #a95331; - --claude-shadow: 0 14px 36px rgba(61, 57, 41, 0.12); - --claude-success: #76b086; - --claude-warning: #d99845; -} - -body { - font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - background: var(--claude-bg); - color: var(--claude-text); - height: 100vh; - overflow: hidden; - -webkit-font-smoothing: antialiased; -} - -/* 顶部状态栏 */ -.header { - background: var(--claude-panel); - backdrop-filter: blur(24px); - border-bottom: 1px solid var(--claude-border); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 56px; - z-index: 100; - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.05); -} - -.header-left { - display: flex; - align-items: center; - gap: 20px; -} - -.logo { - font-size: 18px; - font-weight: 600; - color: var(--claude-text); - letter-spacing: 0.02em; -} - -.project-path { - color: var(--claude-text-secondary); - font-size: 14px; -} - -.header-right { - display: flex; - align-items: center; - gap: 20px; -} - -.thinking-mode { - background: var(--claude-accent); - color: #fff8f2; - padding: 5px 14px; - border-radius: 980px; - font-size: 13px; - font-weight: 500; - letter-spacing: 0.04em; -} - -.connection-status { - font-size: 13px; - color: var(--claude-text-secondary); - display: flex; - align-items: center; - gap: 6px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--claude-muted); - transition: all 0.3s ease; -} - -.status-dot.active { - background: var(--claude-success); - box-shadow: 0 0 8px rgba(118, 176, 134, 0.45); -} - -/* 主容器 */ -.main-container { - display: flex; - height: calc(100vh - 56px); - background: var(--claude-bg); - position: relative; - align-items: stretch; -} - -/* ========================================= */ -/* 新增:对话历史侧边栏样式 */ -/* ========================================= */ - -.conversation-sidebar { - width: 280px; - background: var(--claude-sidebar); - border-right: none; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 50; - backdrop-filter: blur(12px); - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; - border-bottom: 1px solid var(--claude-border); -} - -.conversation-collapsed-spacer { - flex: 1 1 auto; - background: transparent; -} - -.conversation-sidebar.collapsed { - width: 50px; - overflow: hidden; - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; -} - -.conversation-sidebar.collapsed .conversation-header { - justify-content: center; -} - -.conversation-sidebar.collapsed .conversation-header .toggle-sidebar-btn { - margin-left: 0; -} - -.conversation-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(118, 103, 84, 0.12); - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - position: sticky; - top: 0; - z-index: 60; - min-height: 68px; -} - -.new-conversation-btn { - flex: 1; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border: 1px solid rgba(189, 93, 58, 0.4); - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 6px; - box-shadow: 0 6px 14px rgba(189, 93, 58, 0.18); -} - -.new-conversation-btn:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); - transform: translateY(-1px); -} - -.btn-icon { - font-size: 16px; - font-weight: bold; -} - -.btn-text { - white-space: nowrap; -} - -.toggle-sidebar-btn { - background: rgba(255, 255, 255, 0.7); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.18); - padding: 6px 8px; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; - min-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.toggle-sidebar-btn:hover { - background: rgba(255, 255, 255, 0.9); -} - -.conversation-search { - padding: 12px; - background: transparent; - border-bottom: 1px solid var(--claude-border); -} - -.search-input { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--claude-border); - border-radius: 6px; - font-size: 13px; - background: rgba(255, 255, 255, 0.55); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.search-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.85); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.18); -} - -.conversation-list { - flex: 1; - overflow-y: auto; - padding: 8px 0; - background: transparent; -} - -.loading-conversations, -.no-conversations { - text-align: center; - color: var(--claude-text-secondary); - padding: 30px 15px; - font-size: 13px; -} - -.conversation-item { - padding: 12px 16px; - margin: 2px 8px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid transparent; - position: relative; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.04); -} - -.conversation-item:hover { - background: rgba(255, 255, 255, 0.85); - border-color: rgba(218, 119, 86, 0.35); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.conversation-item.active { - background: rgba(218, 119, 86, 0.18); - border-color: var(--claude-accent); - box-shadow: 0 10px 28px rgba(189, 93, 58, 0.18); -} - -.conversation-title { - font-size: 14px; - font-weight: 500; - color: var(--claude-text); - margin-bottom: 6px; - word-wrap: break-word; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.3; -} - -.conversation-meta { - font-size: 11px; - color: var(--claude-text-secondary); - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2px; -} - -.conversation-time { - flex-shrink: 0; -} - -.conversation-counts { - text-align: right; - white-space: nowrap; - font-size: 10px; -} - -.conversation-actions { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); - opacity: 0; - transition: opacity 0.2s ease; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; -} - -.conversation-item:hover .conversation-actions { - opacity: 1; -} - -.conversation-action-btn { - border: none; - border-radius: 4px; - font-size: 13px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - color: white; -} - -.conversation-action-btn.copy-btn { - background: var(--claude-accent); -} - -.conversation-action-btn.copy-btn:hover { - background: var(--claude-button-hover); - transform: translateY(-1px); -} - -.conversation-action-btn.delete-btn { - background: #d85a42; -} - -.conversation-action-btn.delete-btn:hover { - background: #bf422b; - transform: translateY(-1px); -} - -.load-more { - padding: 12px 16px; - text-align: center; -} - -.load-more-btn { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent); - border: 1px solid rgba(218, 119, 86, 0.35); - padding: 6px 14px; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; -} - -.load-more-btn:hover:not(:disabled) { - background: var(--claude-accent); - color: #fffdf8; -} - -.load-more-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* 当前对话信息栏 */ -.current-conversation-info { - background: var(--claude-panel); - backdrop-filter: blur(18px); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -.conversation-title-display { - font-weight: 500; - color: var(--claude-text); -} - -.conversation-message-count { - font-size: 12px; -} - -/* 拖拽手柄 */ -.resize-handle { - width: 4px; - background: var(--claude-sidebar); - cursor: col-resize; - position: relative; - transition: background 0.2s; - flex-shrink: 0; -} - -.resize-handle:hover { - background: rgba(218, 119, 86, 0.22); -} - -/* 侧边栏 */ -.sidebar { - background: rgba(255, 255, 255, 0.75); - overflow-y: auto; - flex-shrink: 0; - border-left: none; -} - -.sidebar-header { - padding: 23px; - border-bottom: 1px solid var(--claude-border); - position: sticky; - top: 0; - background: rgba(255, 255, 255, 0.85); - z-index: 10; - backdrop-filter: blur(16px); - display: flex; - align-items: center; - gap: 10px; -} - -.sidebar-header h3 { - font-size: 15px; - font-weight: 600; - color: var(--claude-text); - margin: 0; -} - -.sidebar-view-toggle { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(118, 103, 84, 0.3); - background: rgba(255, 255, 255, 0.85); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.2s ease; - color: var(--claude-text); -} - -.sidebar-view-toggle:hover { - background: rgba(255, 255, 255, 0.95); -} - -.sidebar.right-sidebar.collapsed { - width: 0 !important; - min-width: 0 !important; - border-left: none; - overflow: hidden; -} - -.sidebar.right-sidebar.collapsed .sidebar-header, -.sidebar.right-sidebar.collapsed .focused-files { - display: none; -} - -/* 文件树 */ -.file-tree { - padding: 12px 0 20px; - color: var(--claude-text); -} - -.todo-panel { - padding: 16px 20px 24px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.todo-empty { - font-size: 14px; - color: var(--claude-text-secondary); - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.8); - border: 1px dashed var(--claude-border); - text-align: center; -} - -.todo-task { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.18); - border-radius: 10px; - font-size: 13px; - color: var(--claude-text); - background: rgba(255, 255, 255, 0.9); -} - -.todo-task.done { - background: rgba(92, 190, 125, 0.08); - border-color: rgba(92, 190, 125, 0.3); -} - -.todo-task-title { - flex: 1; -} - -.todo-task-status { - font-weight: 600; - font-size: 12px; - margin-left: 12px; -} - -.todo-instruction { - font-size: 12px; - color: var(--claude-text-secondary); - margin-top: 8px; -} - -.file-node-wrapper { - font-size: 14px; - color: var(--claude-text); - font-family: inherit; -} - -.folder-header { - width: 100%; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s ease, transform 0.2s ease; - text-align: left; - font-family: inherit; -} - -.folder-header:hover { - background: rgba(218, 119, 86, 0.12); - transform: translateX(2px); -} - -.folder-arrow { - width: 12px; - text-align: center; - color: var(--claude-text-secondary); - font-size: 12px; -} - -.folder-icon, -.file-icon { - width: 18px; - text-align: center; -} - -.folder-name, -.file-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: inherit; -} - - -.folder-children { - margin-left: 14px; - padding-left: 6px; - border-left: 1px dashed rgba(0, 0, 0, 0.08); -} - -.folder-children .file-node-wrapper { - margin-left: 0; -} - -.file-node.file-leaf { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border-radius: 8px; - font-family: inherit; -} - -.file-node.file-leaf:hover { - background: rgba(218, 119, 86, 0.1); -} - -.file-node .annotation { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -.context-menu { - position: fixed; - background: #ffffff; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.15); - z-index: 3000; - min-width: 180px; - padding: 6px 0; - backdrop-filter: blur(8px); -} - -.context-menu button { - width: 100%; - padding: 8px 18px; - background: transparent; - border: none; - text-align: left; - font-size: 13px; - color: #1f2933; - cursor: pointer; - transition: background 0.15s ease; -} - -.context-menu button:hover { - background: rgba(59, 130, 246, 0.12); -} - -.context-menu button:disabled { - color: #9ca3af; - cursor: not-allowed; - background: transparent; -} - -/* 聊天容器 */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - background: rgba(255, 255, 255, 0.78); - min-width: 0; - position: relative; - backdrop-filter: blur(6px); -} - -/* 消息区域 */ -.messages-area { - flex: 1; - overflow-y: auto; - padding: 24px; -} - -.scroll-lock-toggle { - position: absolute; - right: 28px; - bottom: 200px; - z-index: 25; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.scroll-lock-btn { - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.92); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); -} - -.scroll-lock-btn:hover { - transform: translateY(-2px); - box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); -} - -.scroll-lock-toggle.locked .scroll-lock-btn { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); -} - -.scroll-lock-btn svg { - width: 18px; - height: 18px; - stroke: var(--claude-text); - stroke-width: 1.8; - fill: none; - transition: stroke 0.2s ease; -} - -.scroll-lock-toggle.locked .scroll-lock-btn svg { - stroke: var(--claude-accent); -} - -/* 滚动条 */ -.messages-area::-webkit-scrollbar, -.sidebar::-webkit-scrollbar, -.conversation-list::-webkit-scrollbar { - width: 8px; -} - -.messages-area::-webkit-scrollbar-track, -.sidebar::-webkit-scrollbar-track, -.conversation-list::-webkit-scrollbar-track { - background: transparent; -} - -.messages-area::-webkit-scrollbar-thumb, -.sidebar::-webkit-scrollbar-thumb, -.conversation-list::-webkit-scrollbar-thumb { - background: rgba(121, 109, 94, 0.4); - border-radius: 8px; -} - -/* 消息块 */ -.message-block { - margin-bottom: 24px; -} - -/* 用户消息 */ -.user-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 8px; - letter-spacing: 0.02em; -} - -.user-message .message-text { - background: rgba(255, 255, 255, 0.85); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - white-space: pre-wrap; -} - -/* AI消息 */ -.assistant-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 12px; - letter-spacing: 0.02em; -} - -.assistant-message .message-text { - background: rgba(218, 119, 86, 0.12); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - border-left: 4px solid var(--claude-accent); - white-space: pre-wrap; -} - -/* Action项入场动画 */ -@keyframes slideInFade { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes quickFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.action-item { - animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 0ms; -} - -/* 流式内容:立即显示 */ -.action-item.streaming-content, -.action-item.immediate-show { - animation: quickFadeIn 0.2s ease-out both; - animation-delay: 0ms !important; -} - -/* 已完成的工具:统一延迟看动画 */ -.action-item.completed-tool { - animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 100ms; -} - -/* 文本输出 - 无缩进,与思考块对齐 */ -.text-output { - margin: 16px 0; - color: var(--claude-text); - font-size: 15px; - line-height: 1.7; -} - -.text-output .text-content { - padding: 0 20px 0 15px; /* 左边56px与思考块对齐 */ -} - -.text-output .text-content p { - margin-bottom: 12px; -} - -.text-output .text-content p:last-child { - margin-bottom: 0; -} - -.append-block { - margin: 12px 0; - padding: 12px 16px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); - display: flex; - flex-direction: column; - gap: 8px; -} - -.append-block.append-error { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); - box-shadow: inset 0 0 0 1px rgba(216, 90, 66, 0.08); -} - -.append-header { - display: flex; - align-items: center; - gap: 10px; - font-weight: 600; - color: var(--claude-text); - font-size: 15px; -} - -.append-block.append-error .append-header { - color: #b0432a; -} - -.append-icon { - font-size: 18px; -} - -.append-summary { - flex: 1; -} - -.append-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-meta-item { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.append-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder { - margin: 12px 0; -} - -.modify-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.modify-placeholder-content .modify-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder-content .modify-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-placeholder { - margin: 12px 0; -} - - -.append-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.append-placeholder-content .append-warning { - margin-top: 4px; -} - -.append-placeholder.append-error .append-placeholder-content { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); -} -/* Markdown表格样式 */ -.text-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; -} - -.text-content table th, -.text-content table td { - border: 1px solid #e0e0e0; - padding: 10px 14px; - text-align: left; -} - -.text-content table th { - background: rgba(218, 119, 86, 0.08); - font-weight: 600; - color: var(--claude-text); -} - -.text-content table tr:hover { - background: #fafafa; -} - -/* 代码块包装器 - 防止跳动 */ -.code-block-wrapper { - border: 2px solid rgba(118, 103, 84, 0.25); - border-radius: 12px; - overflow: hidden; - margin: 16px 0; - background: rgba(255, 255, 255, 0.78); - min-height: 80px; /* 添加最小高度防止跳动 */ - contain: layout; /* CSS containment 优化渲染 */ -} - -/* 代码块头部 */ -.code-block-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(118, 103, 84, 0.25); -} - -.code-language { - color: var(--claude-text-secondary); - font-size: 13px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-weight: 500; -} - -.copy-code-btn { - background: transparent; - color: var(--claude-text-secondary); - border: 1px solid rgba(121, 109, 94, 0.35); - padding: 6px 10px; - border-radius: 6px; - font-size: 16px; - cursor: pointer; - transition: all 0.2s ease; - line-height: 1; -} - -.copy-code-btn:hover { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent-strong); -} - -.copy-code-btn.copied { - background: var(--claude-success); - border-color: var(--claude-success); - color: #f6fff8; -} - -/* 代码块内容区 */ -.code-block-wrapper pre { - background: #ffffff !important; - padding: 16px !important; - margin: 0 !important; - border-radius: 0 !important; - border: none !important; -} - -.code-block-wrapper pre code { - background: transparent !important; - padding: 0 !important; - color: #000000; -} -/* 流式文本 */ -.streaming-text { - display: block; -} - -.cursor-blink { - animation: blink 1s steps(1) infinite; - color: var(--claude-accent); - font-weight: normal; -} - -/* 思考内容样式 - 保留换行 */ -.thinking-content { - white-space: pre-wrap; /* 保留换行和空格 */ - word-wrap: break-word; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.6; - color: var(--claude-text-secondary); -} - -/* 可折叠块 */ -.collapsible-block { - background: rgba(255, 255, 255, 0.78); - border-radius: 12px; - margin-bottom: 12px; - overflow: hidden; - border: 1px solid var(--claude-border); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.collapsible-block:hover { - box-shadow: 0 14px 28px rgba(61, 57, 41, 0.1); -} - -.collapsible-header { - padding: 14px 20px; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - user-select: none; - background: rgba(255, 255, 255, 0.72); - transition: background-color 0.2s ease; - position: relative; -} - -.collapsible-header:hover { - background: rgba(218, 119, 86, 0.07); -} - -/* 箭头 */ -.arrow { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: var(--claude-text-secondary); -} - -.arrow::before { - content: '›'; - font-size: 18px; -} - -.collapsible-block.expanded .arrow { - transform: rotate(90deg); -} - -/* 状态图标 */ -.status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; -} - -/* 内容区域 */ -.collapsible-content { - max-height: 0; - overflow: hidden; - opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.collapsible-block.expanded .collapsible-content { - max-height: 600px; - overflow-y: auto; - opacity: 1; -} - -.content-inner { - padding: 20px 20px 20px 56px; - color: var(--claude-text-secondary); - font-size: 14px; - line-height: 1.6; -} - -/* 工具动画 */ -@keyframes brain-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.15); } -} - -.thinking-icon { - animation: brain-pulse 1.5s ease-in-out infinite; -} - -@keyframes file-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -.file-animation { - animation: file-bounce 1.5s ease-in-out infinite; -} - -@keyframes scan-effect { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -.read-animation { - animation: scan-effect 1.5s ease-in-out infinite; -} - -@keyframes search-rotate { - 0% { transform: rotate(-10deg); } - 50% { transform: rotate(10deg); } - 100% { transform: rotate(-10deg); } -} - -.search-animation { - animation: search-rotate 1s ease-in-out infinite; -} - -@keyframes code-breathe { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -.code-animation { - animation: code-breathe 2s ease-in-out infinite; -} - -.terminal-animation::after { - content: '_'; - animation: blink 1s steps(1) infinite; - margin-left: 2px; -} - -@keyframes memory-fade { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.95); } -} - -.memory-animation { - animation: memory-fade 2s ease-in-out infinite; -} - -@keyframes focus-glow { - 0%, 100% { filter: brightness(1); } - 50% { filter: brightness(1.3); } -} - -.focus-animation { - animation: focus-glow 1.5s ease-in-out infinite; -} - -/* 进度条 */ -.progress-indicator { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--claude-accent); - animation: progress 2s ease-in-out infinite; - opacity: 0; - transition: opacity 0.3s ease; -} - -.collapsible-block.processing .progress-indicator { - opacity: 1; -} - -@keyframes progress { - 0% { width: 0%; left: 0%; } - 50% { width: 40%; left: 30%; } - 100% { width: 0%; left: 100%; } -} - -/* 状态文字 */ -.status-text { - font-size: 14px; - color: var(--claude-text); - font-weight: 500; -} - -.processing .status-text { - color: var(--claude-text-secondary); -} - -/* 完成勾号 */ -.checkmark { - color: var(--claude-success); - font-weight: 600; - font-size: 16px; -} - -/* 工具描述 */ -.tool-desc { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -/* 输入区域 */ -.input-area { - background: rgba(255, 255, 255, 0.82); - border-top: 1px solid var(--claude-border); - padding: 20px; - backdrop-filter: blur(12px); -} - -.input-wrapper { - display: flex; - flex-direction: column; - gap: 12px; -} - -.message-input { - width: 100%; - padding: 14px 16px; - border: 1px solid var(--claude-border); - border-radius: 12px; - font-size: 15px; - resize: none; - font-family: inherit; - background: rgba(255, 255, 255, 0.75); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.message-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.2); -} - -.input-actions { - display: flex; - gap: 8px; - justify-content: flex-end; -} - -/* 按钮 */ -.btn { - padding: 10px 24px; - border: none; - border-radius: 980px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - letter-spacing: 0.03em; -} - -.send-btn { - background: var(--claude-accent); - color: #fffdf8; - box-shadow: 0 12px 28px rgba(189, 93, 58, 0.25); -} - -.send-btn:hover:not(:disabled) { - background: var(--claude-button-hover); - transform: scale(1.02); - box-shadow: 0 14px 32px rgba(189, 93, 58, 0.3); -} - -.send-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.stop-btn { - background: #d85a42; - color: #fffaf5; -} - -.stop-btn:hover { - background: #bf422b; -} - -.settings-dropdown { - position: relative; - display: flex; - align-items: center; -} - -.tool-dropdown { - position: relative; - display: flex; - align-items: center; - margin-right: auto; -} - -.upload-control { - display: flex; - align-items: center; -} - -.file-input-hidden { - display: none; -} - -.upload-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.upload-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.upload-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.tool-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.tool-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.tool-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.settings-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.settings-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-menu { - position: absolute; - right: 0; - bottom: calc(100% + 12px); - background: rgba(255, 255, 255, 0.96); - border: 1px solid var(--claude-border); - border-radius: 12px; - box-shadow: var(--claude-shadow); - 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: rgba(255, 255, 255, 0.96) 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: 320px; - max-width: 380px; - padding: 14px 18px; -} - -.settings-menu.tool-menu::before { - left: 32px; - right: auto; -} - -.tool-menu .tool-menu-status, -.tool-menu .tool-menu-empty { - font-size: 13px; - color: rgba(61, 57, 41, 0.78); - text-align: left; -} - -.tool-menu .tool-menu-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.tool-menu .tool-category-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 10px; - background: rgba(255, 255, 255, 0.88); - font-size: 13px; -} - -.tool-menu .tool-category-item.disabled { - opacity: 0.55; -} - -.tool-menu .tool-category-label { - flex: 1; - white-space: nowrap; - font-size: 13px; - font-weight: 500; - color: var(--claude-text); - display: inline-flex; - align-items: center; - gap: 6px; -} - -.tool-category-icon { - font-size: 16px; -} - -.tool-menu .tool-category-toggle { - width: auto !important; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 18px; - text-align: center; - white-space: nowrap; -} - -.menu-btn { - width: 100%; - padding: 8px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 8px; - font-size: 13px; - font-weight: 500; - text-align: left; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - cursor: pointer; - transition: all 0.2s ease; -} - -.menu-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); - transform: translateY(-1px); -} - -.menu-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.menu-btn.compress-entry { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-success); -} - -.menu-btn.compress-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.menu-btn.clear-entry { - background: rgba(255, 255, 255, 0.78); - color: #bf422b; -} - -.menu-btn.clear-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.settings-menu-enter-active, -.settings-menu-leave-active { - transition: opacity 0.18s ease, transform 0.18s ease; -} - -.settings-menu-enter-from, -.settings-menu-leave-to { - opacity: 0; - transform: translateY(6px); -} - -/* 聚焦文件 */ -.focused-files { - padding: 16px; -} - -.no-files { - text-align: center; - color: var(--claude-text-secondary); - padding: 60px 20px; - font-size: 14px; -} - -.file-tabs { - display: flex; - flex-direction: column; - gap: 12px; -} - -.file-tab { - border: 1px solid var(--claude-border); - border-radius: 12px; - overflow: hidden; - background: rgba(255, 255, 255, 0.75); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.tab-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-bottom: 1px solid var(--claude-border); -} - -.file-name { - font-weight: 500; - color: var(--claude-text); -} - -.file-size { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.file-content { - max-height: 320px; - overflow-y: auto; - background: #1e1e1e; -} - -.file-content pre { - margin: 0; - padding: 16px; -} - -.file-content code { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - color: #aed581; -} - -/* 系统消息 */ -.system-message { - text-align: center; - color: var(--claude-text-secondary); - font-size: 13px; - margin: 20px 0; - padding: 10px; - background: rgba(255, 255, 255, 0.7); - border-radius: 12px; - border: 1px solid var(--claude-border); -} - -/* Markdown样式 */ -.text-content h1, -.text-content h2, -.text-content h3 { - margin-top: 20px; - margin-bottom: 12px; - font-weight: 600; - color: var(--claude-text); -} - -.text-content p { - margin-bottom: 12px; -} - -.text-content pre { - background: rgba(255, 255, 255, 0.78); - padding: 16px; - border-radius: 12px; - overflow-x: auto; - margin: 16px 0; - border: 1px solid var(--claude-border); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.text-content code { - background: rgba(218, 119, 86, 0.1); - padding: 2px 6px; - border-radius: 4px; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--claude-accent-strong); -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} - - -/* ========================================= */ -/* 响应式设计 */ -/* ========================================= */ - -@media (max-width: 1200px) { - .conversation-sidebar { - width: 260px; - } - - .left-sidebar, - .right-sidebar { - width: 300px !important; - } -} - -@media (max-width: 768px) { - .conversation-sidebar { - position: absolute; - left: 0; - top: 0; - height: 100%; - z-index: 1000; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); - transform: translateX(-100%); - } - - .conversation-sidebar:not(.collapsed) { - transform: translateX(0); - } - - .left-sidebar, - .right-sidebar { - display: none; - } - - .resize-handle { - display: none; - } -} -.conversation-stats { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--claude-text-secondary); -} - -.token-count { - color: var(--claude-accent); - font-weight: 500; -} -/* ========================================= */ -/* Token 统计面板样式(无缝一体版)*/ -/* ========================================= */ - -/* Token区域包装器 */ -.token-wrapper { - position: relative; - z-index: 5; - margin-bottom: 0; -} - -/* 当前对话信息栏 - 移除底部边框 */ -.current-conversation-info { - position: relative; - z-index: 10; - background: var(--claude-panel); - backdrop-filter: blur(18px); - border-bottom: none; /* 移除边框,让它和下面的面板融为一体 */ - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - border-radius: 0; /* 顶部保持直角 */ - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -/* Token面板 - 与标题栏完全一体,底部圆角 */ -.token-display-panel { - background: var(--claude-panel); - backdrop-filter: blur(18px); - border: none; - border-radius: 0 0 16px 16px; - box-shadow: 0 8px 18px rgba(189, 93, 58, 0.12); - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); - width: 100%; - margin: 0; - padding: 0; -} - -/* 展开状态 */ -.token-display-panel:not(.collapsed) { - height: 80px; - opacity: 1; -} - -/* 收起状态 */ -.token-display-panel.collapsed { - height: 0; - opacity: 0; - border: none; - box-shadow: none; -} - -.token-panel-content { - padding: 16px 24px; - height: 100%; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.token-display-panel.collapsed .token-panel-content { - opacity: 0; - pointer-events: none; -} - -.token-stats { - display: flex; - gap: 32px; - align-items: center; - justify-content: center; - font-size: 13px; - height: 100%; -} - -.token-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; - min-width: 80px; -} - -.token-label { - color: var(--claude-text-secondary); - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.token-value { - color: var(--claude-text); - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; -} - -.token-value.current { - color: var(--claude-accent); - font-size: 20px; -} -.token-value.input { color: var(--claude-success); } -.token-value.output { color: var(--claude-warning); } - -.token-separator { - width: 1px; - height: 35px; - background: linear-gradient(to bottom, - transparent, - rgba(218, 119, 86, 0.25) 20%, - rgba(218, 119, 86, 0.25) 80%, - transparent - ); - margin: 0 8px; -} - -/* 切换按钮 - 独立定位 */ -.token-toggle-btn { - position: absolute; - right: 24px; - bottom: -18px; /* 相对于wrapper底部 */ - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(218, 119, 86, 0.3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 15; - font-size: 14px; - font-weight: bold; -} - -/* 展开状态 */ -.token-toggle-btn:not(.collapsed) { - background: linear-gradient(135deg, #ffffff 0%, rgba(255, 248, 242, 0.9) 100%); - color: var(--claude-accent); - box-shadow: 0 3px 10px rgba(189, 93, 58, 0.18); -} - -/* 收起状态 - 在标题栏下方露出一半 */ -.token-toggle-btn.collapsed { - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border-color: rgba(255, 248, 242, 0.55); - box-shadow: 0 3px 11px rgba(189, 93, 58, 0.22); -} - -.token-toggle-btn:hover { - transform: scale(1.05); - box-shadow: 0 5px 16px rgba(189, 93, 58, 0.26); -} - -.token-toggle-btn.collapsed:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); -} - -.token-toggle-btn:active { - transform: scale(1.02); -} - -/* 箭头样式 - 移除浮动动画 */ -.token-toggle-btn span { - transition: all 0.3s ease; - display: inline-block; -} - -/* 移除动画效果 */ -/* .token-toggle-btn:not(.collapsed) span { - animation: arrowBounceUp 2s ease-in-out infinite; -} - -.token-toggle-btn.collapsed span { - animation: arrowBounceDown 2s ease-in-out infinite; -} */ - -/* 保留动画定义,但不使用 */ -@keyframes arrowBounceUp { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -@keyframes arrowBounceDown { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(3px); } -} - -/* 响应式调整 */ -@media (max-width: 768px) { - .token-stats { - gap: 16px; - } - - .token-item { - min-width: 60px; - } - - .token-value { - font-size: 15px; - } - - .token-value.current { - font-size: 17px; - } - - .token-label { - font-size: 10px; - } - - .token-toggle-btn { - width: 32px; - height: 32px; - font-size: 12px; - right: 16px; - } -} -/* Markdown列表样式 - 修复偏左问题 */ -.text-content ul, -.text-content ol { - margin-left: 24px; /* 增加左边距 */ - padding-left: 0; - margin-bottom: 12px; -} - -.text-content ul { - list-style-type: disc; /* 实心圆点 */ -} - -.text-content ul ul { - list-style-type: circle; /* 空心圆点 */ - margin-top: 6px; -} - -.text-content ol { - list-style-type: decimal; /* 数字列表 */ -} - -.text-content li { - margin-bottom: 6px; - line-height: 1.6; -} - -/* 搜索结果展示 */ -.search-meta { - font-size: 14px; - color: var(--claude-text-secondary); - line-height: 1.6; - margin-bottom: 12px; - word-break: break-word; -} - -.search-result-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.search-result-item { - padding: 10px 12px; - border: 1px solid rgba(118, 103, 84, 0.16); - border-radius: 8px; - background: rgba(255, 255, 255, 0.65); - word-break: break-word; -} - -.search-result-title { - font-weight: 600; - margin-bottom: 6px; - color: var(--claude-text); -} - -.search-result-url a { - color: var(--claude-accent-strong); - text-decoration: none; - word-break: break-all; -} - -.search-result-url a:hover { - text-decoration: underline; -} - -.search-empty { - font-size: 13px; - color: var(--claude-text-secondary); - font-style: italic; -} diff --git a/sub_agent/static/backup_20251026_183122/terminal.html b/sub_agent/static/backup_20251026_183122/terminal.html deleted file mode 100644 index f0a31f0..0000000 --- a/sub_agent/static/backup_20251026_183122/terminal.html +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - AI Terminal Monitor - 实时终端查看器 - - - - - - - - -
-

- 🖥️ AI Terminal Monitor - - 连接中... -

-
- - -
-
- - 等待终端会话... -
-
- - -
- -
-
-
- 无活动会话 - - -
-
-
-
-
-
-
-
-
- - - -
- - -
-
-
- - 延迟: 0ms -
-
- 📡 - 0 KB/s -
-
- 🕐 - -
-
-
- AI Agent Terminal Monitor v1.0 -
-
- - -
- - - - - - - - - - diff --git a/sub_agent/static/backup_20251026_184346/app.js b/sub_agent/static/backup_20251026_184346/app.js deleted file mode 100644 index 528554e..0000000 --- a/sub_agent/static/backup_20251026_184346/app.js +++ /dev/null @@ -1,2854 +0,0 @@ -// static/app-enhanced.js - 修复版,正确实现Token实时更新 - -const SOCKET_IO_CDN_SOURCES = [ - 'https://cdn.socket.io/4.7.5/socket.io.min.js', - 'https://cdn.jsdelivr.net/npm/socket.io-client@4.7.5/dist/socket.io.min.js', - 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js' -]; - -function injectScriptSequentially(urls, onSuccess, onFailure) { - let index = 0; - const tryLoad = () => { - if (index >= urls.length) { - onFailure(); - return; - } - const url = urls[index]; - const script = document.createElement('script'); - script.src = url; - script.async = false; - script.onload = () => { - if (typeof io !== 'undefined') { - console.log(`Socket.IO 已从 ${url} 加载`); - onSuccess(); - } else { - index += 1; - tryLoad(); - } - }; - script.onerror = () => { - console.warn(`无法从 ${url} 加载 Socket.IO,尝试下一个源`); - index += 1; - tryLoad(); - }; - document.head.appendChild(script); - }; - tryLoad(); -} - -async function ensureSocketIOLoaded() { - if (typeof io !== 'undefined') { - return true; - } - return await new Promise((resolve) => { - injectScriptSequentially( - SOCKET_IO_CDN_SOURCES, - () => resolve(true), - () => resolve(false) - ); - }); -} - -async function bootstrapApp() { - // 检查必要的库是否加载 - if (typeof Vue === 'undefined') { - console.error('错误:Vue.js 未加载'); - document.body.innerHTML = '

Vue.js 加载失败,请刷新页面

'; - return; - } - - if (typeof io === 'undefined') { - const loaded = await ensureSocketIOLoaded(); - if (!loaded || typeof io === 'undefined') { - console.error('错误:Socket.IO 未加载'); - document.body.innerHTML = '

Socket.IO 加载失败,请检查网络后刷新页面

'; - return; - } - } - - console.log('所有依赖加载成功,初始化Vue应用...'); - - const { createApp } = Vue; - - const app = createApp({ - data() { - return { - // 连接状态 - isConnected: false, - socket: null, - - // 系统信息 - projectPath: '', - thinkingMode: '未知', - - // 消息相关 - messages: [], - inputMessage: '', - - // 当前消息索引 - currentMessageIndex: -1, - streamingMessage: false, - - // 停止功能状态 - stopRequested: false, - - // 路由相关 - initialRouteResolved: false, - - // 文件相关 - fileTree: [], - focusedFiles: {}, - expandedFolders: {}, - - // 展开状态管理 - expandedBlocks: new Set(), - - // 滚动控制 - userScrolling: false, - autoScrollEnabled: true, - - // 面板宽度控制 - leftWidth: 280, - rightWidth: 420, - rightCollapsed: true, - isResizing: false, - resizingPanel: null, - minPanelWidth: 200, - maxPanelWidth: 600, - - // 工具状态跟踪 - preparingTools: new Map(), - activeTools: new Map(), - toolActionIndex: new Map(), - toolActionIndex: new Map(), - - // ========================================== - // 对话管理相关状态 - // ========================================== - - // 对话历史侧边栏 - sidebarCollapsed: true, // 默认收起对话侧边栏 - showTodoList: false, - conversations: [], - conversationsLoading: false, - hasMoreConversations: false, - loadingMoreConversations: false, - currentConversationId: null, - currentConversationTitle: '当前对话', - - // 搜索功能 - searchQuery: '', - searchTimer: null, - - // 分页 - conversationsOffset: 0, - conversationsLimit: 20, - - // ========================================== - // Token统计相关状态(修复版) - // ========================================== - - // 当前上下文Token(动态计算,包含完整prompt) - currentContextTokens: 0, - - // 累计Token统计(从对话文件和WebSocket获取) - currentConversationTokens: { - // 累计统计字段 - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - - }, - // Token面板折叠状态 - tokenPanelCollapsed: false, - - // 对话压缩状态 - compressing: false, - - // 设置菜单状态 - settingsOpen: false, - - // 工具控制菜单 - toolMenuOpen: false, - toolSettings: [], - toolSettingsLoading: false, - - // 文件上传状态 - uploading: false, - - // TODO 列表 - todoList: null, - todoEmoji: '🗒️', - fileEmoji: '📁', - todoDoneEmoji: '☑️', - todoPendingEmoji: '⬜️', - toolCategoryEmojis: { - network: '🌐', - file_edit: '📝', - read_focus: '🔍', - terminal_realtime: '🖥️', - terminal_command: '⌨️', - memory: '🧠', - todo: '🗒️' - }, - - // 右键菜单相关 - contextMenu: { - visible: false, - x: 0, - y: 0, - node: null - }, - onDocumentClick: null, - onWindowScroll: null, - onKeydownListener: null - } - }, - - async mounted() { - console.log('Vue应用已挂载'); - await this.bootstrapRoute(); - this.initSocket(); - this.initScrollListener(); - - // 延迟加载初始数据 - setTimeout(() => { - this.loadInitialData(); - }, 500); - - document.addEventListener('click', this.handleClickOutsideSettings); - document.addEventListener('click', this.handleClickOutsideToolMenu); - window.addEventListener('popstate', this.handlePopState); - - this.onDocumentClick = (event) => { - if (!this.contextMenu.visible) { - return; - } - if (event.target && event.target.closest && event.target.closest('.context-menu')) { - return; - } - this.hideContextMenu(); - }; - - this.onWindowScroll = () => { - if (this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - this.onKeydownListener = (event) => { - if (event.key === 'Escape' && this.contextMenu.visible) { - this.hideContextMenu(); - } - }; - - document.addEventListener('click', this.onDocumentClick); - window.addEventListener('scroll', this.onWindowScroll, true); - document.addEventListener('keydown', this.onKeydownListener); - }, - - beforeUnmount() { - document.removeEventListener('click', this.handleClickOutsideSettings); - document.removeEventListener('click', this.handleClickOutsideToolMenu); - window.removeEventListener('popstate', this.handlePopState); - if (this.onDocumentClick) { - document.removeEventListener('click', this.onDocumentClick); - this.onDocumentClick = null; - } - if (this.onWindowScroll) { - window.removeEventListener('scroll', this.onWindowScroll, true); - this.onWindowScroll = null; - } - if (this.onKeydownListener) { - document.removeEventListener('keydown', this.onKeydownListener); - this.onKeydownListener = null; - } - }, - - methods: { - async bootstrapRoute() { - const path = window.location.pathname.replace(/^\/+/, ''); - if (!path || path === 'new') { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.initialRouteResolved = true; - return; - } - - const convId = path.startsWith('conv_') ? path : `conv_${path}`; - try { - const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' }); - const result = await resp.json(); - if (result.success) { - this.currentConversationId = convId; - this.currentConversationTitle = result.title || '对话'; - history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`); - } else { - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } - } catch (error) { - console.warn('初始化路由失败:', error); - history.replaceState({}, '', '/new'); - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - } finally { - this.initialRouteResolved = true; - } - }, - - handlePopState(event) { - const state = event.state || {}; - const convId = state.conversationId; - if (!convId) { - this.currentConversationId = null; - this.currentConversationTitle = '新对话'; - this.messages = []; - this.resetAllStates(); - this.resetTokenStatistics(); - return; - } - this.loadConversation(convId); - }, - - stripConversationPrefix(conversationId) { - if (!conversationId) return ''; - return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId; - }, - - showContextMenu(payload) { - if (!payload || !payload.node) { - return; - } - const { node, event } = payload; - console.log('context menu', node.path, node.type); - if (event && typeof event.preventDefault === 'function') { - event.preventDefault(); - } - if (event && typeof event.stopPropagation === 'function') { - event.stopPropagation(); - } - if (!node.path && node.path !== '') { - this.hideContextMenu(); - return; - } - if (node.type !== 'file' && node.type !== 'folder') { - this.hideContextMenu(); - return; - } - - this.hideContextMenu(); - - let x = (event && event.clientX) || 0; - let y = (event && event.clientY) || 0; - const menuWidth = 200; - const menuHeight = 50; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - if (x + menuWidth > viewportWidth) { - x = viewportWidth - menuWidth - 8; - } - if (y + menuHeight > viewportHeight) { - y = viewportHeight - menuHeight - 8; - } - - this.contextMenu.visible = true; - this.contextMenu.x = Math.max(8, x); - this.contextMenu.y = Math.max(8, y); - this.contextMenu.node = node; - }, - - hideContextMenu() { - if (!this.contextMenu.visible) { - return; - } - this.contextMenu.visible = false; - this.contextMenu.node = null; - }, - - async downloadFile(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/file?path=${encodeURIComponent(path)}`; - const name = path.split('/').pop() || 'file'; - await this.downloadResource(url, name); - }, - - async downloadFolder(path) { - if (!path) { - this.hideContextMenu(); - return; - } - const url = `/api/download/folder?path=${encodeURIComponent(path)}`; - const segments = path.split('/').filter(Boolean); - const folderName = segments.length ? segments.pop() : 'folder'; - await this.downloadResource(url, `${folderName}.zip`); - }, - - async downloadResource(url, filename) { - try { - const response = await fetch(url); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - message = errorData.error || errorData.message || message; - } catch (err) { - message = await response.text(); - } - alert(`下载失败: ${message}`); - return; - } - - const blob = await response.blob(); - const downloadName = filename || 'download'; - const link = document.createElement('a'); - const href = URL.createObjectURL(blob); - link.href = href; - link.download = downloadName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(href); - } catch (error) { - console.error('下载失败:', error); - alert(`下载失败: ${error.message || error}`); - } finally { - this.hideContextMenu(); - } - }, - - initScrollListener() { - const messagesArea = this.$refs.messagesArea; - if (!messagesArea) { - console.warn('消息区域未找到'); - return; - } - - let isProgrammaticScroll = false; - const bottomThreshold = 12; - - this._setScrollingFlag = (flag) => { - isProgrammaticScroll = !!flag; - }; - - messagesArea.addEventListener('scroll', () => { - if (isProgrammaticScroll) { - return; - } - - const scrollTop = messagesArea.scrollTop; - const scrollHeight = messagesArea.scrollHeight; - const clientHeight = messagesArea.clientHeight; - const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; - - if (isAtBottom) { - this.userScrolling = false; - this.autoScrollEnabled = true; - } else { - this.userScrolling = true; - this.autoScrollEnabled = false; - } - }); - }, - - initSocket() { - try { - console.log('初始化WebSocket连接...'); - - const usePollingOnly = window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1'; - - this.socket = io('/', usePollingOnly ? { - transports: ['polling'], - upgrade: false - } : { - transports: ['websocket', 'polling'] - }); - - // 连接事件 - this.socket.on('connect', () => { - this.isConnected = true; - console.log('WebSocket已连接'); - // 连接时重置所有状态 - this.resetAllStates(); - }); - - this.socket.on('disconnect', () => { - this.isConnected = false; - console.log('WebSocket已断开'); - // 断线时也重置状态,防止状态混乱 - this.resetAllStates(); - }); - - this.socket.on('connect_error', (error) => { - console.error('WebSocket连接错误:', error.message); - }); - - // ========================================== - // Token统计WebSocket事件处理(修复版) - // ========================================== - - this.socket.on('token_update', (data) => { - console.log('收到token更新事件:', data); - - // 只处理当前对话的token更新 - if (data.conversation_id === this.currentConversationId) { - // 更新累计统计(使用后端提供的准确字段名) - this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; - - console.log(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); - - // 同时更新当前上下文Token(关键修复) - this.updateCurrentContextTokens(); - - this.$forceUpdate(); - } - }); - - this.socket.on('todo_updated', (data) => { - console.log('收到todo更新事件:', data); - if (data && data.conversation_id) { - this.currentConversationId = data.conversation_id; - } - this.todoList = data && data.todo_list ? data.todo_list : null; - }); - - // 系统就绪 - this.socket.on('system_ready', (data) => { - this.projectPath = data.project_path || ''; - this.thinkingMode = data.thinking_mode || '未知'; - console.log('系统就绪:', data); - - // 系统就绪后立即加载对话列表 - this.loadConversationsList(); - }); - - this.socket.on('tool_settings_updated', (data) => { - console.log('收到工具设置更新:', data); - if (data && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } - }); - - // ========================================== - // 对话管理相关Socket事件 - // ========================================== - - // 监听对话变更事件 - this.socket.on('conversation_changed', (data) => { - console.log('对话已切换:', data); - this.currentConversationId = data.conversation_id; - this.currentConversationTitle = data.title || ''; - - if (data.cleared) { - // 对话被清空 - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - // 重置Token统计 - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.loadConversationsList(); - this.fetchTodoList(); - }); - - this.socket.on('conversation_resolved', (data) => { - if (!data || !data.conversation_id) { - return; - } - const convId = data.conversation_id; - this.currentConversationId = convId; - if (data.title) { - this.currentConversationTitle = data.title; - } - const pathFragment = this.stripConversationPrefix(convId); - const currentPath = window.location.pathname.replace(/^\/+/, ''); - if (data.created) { - history.pushState({ conversationId: convId }, '', `/${pathFragment}`); - } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); - } - }); - - // 监听对话加载事件 - this.socket.on('conversation_loaded', (data) => { - console.log('对话已加载:', data); - if (data.clear_ui) { - // 清理当前UI状态,准备显示历史内容 - this.resetAllStates(); - } - - // 延迟获取并显示历史对话内容 - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 延迟获取Token统计(累计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - this.fetchTodoList(); - }, 500); - }); - - // 监听对话列表更新事件 - this.socket.on('conversation_list_update', (data) => { - console.log('对话列表已更新:', data); - // 刷新对话列表 - this.loadConversationsList(); - }); - - // 监听状态更新事件 - this.socket.on('status_update', (status) => { - // 更新系统状态信息 - if (status.conversation && status.conversation.current_id) { - this.currentConversationId = status.conversation.current_id; - } - }); - - // AI消息开始 - this.socket.on('ai_message_start', () => { - console.log('AI消息开始'); - this.cleanupStaleToolActions(); - const newMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(newMessage); - this.currentMessageIndex = this.messages.length - 1; - this.streamingMessage = true; - this.stopRequested = false; - this.autoScrollEnabled = true; - this.scrollToBottom(); - }); - - // 思考流开始 - this.socket.on('thinking_start', () => { - console.log('思考开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking = ''; - msg.currentStreamingType = 'thinking'; - - const action = { - id: Date.now() + Math.random(), - type: 'thinking', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - - const blockId = `${this.currentMessageIndex}-thinking-${msg.actions.length - 1}`; - this.expandedBlocks.add(blockId); - this.$forceUpdate(); - } - }); - - // 思考内容块 - this.socket.on('thinking_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingThinking += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 思考结束 - this.socket.on('thinking_end', (data) => { - console.log('思考结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'thinking') { - lastAction.streaming = false; - lastAction.content = data.full_content; - } - msg.streamingThinking = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 文本流开始 - this.socket.on('text_start', () => { - console.log('文本开始'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText = ''; - msg.currentStreamingType = 'text'; - - const action = { - id: Date.now() + Math.random(), - type: 'text', - content: '', - streaming: true, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - } - }); - - // 文本内容块 - this.socket.on('text_chunk', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - msg.streamingText += data.content; - - const lastAction = msg.actions[msg.actions.length - 1]; - if (lastAction && lastAction.type === 'text') { - lastAction.content += data.content; - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - - // 实时渲染LaTeX - this.renderLatexInRealtime(); - } - }); - - // 文本结束 - this.socket.on('text_end', (data) => { - console.log('文本结束'); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找当前流式文本的action - for (let i = msg.actions.length - 1; i >= 0; i--) { - const action = msg.actions[i]; - if (action.type === 'text' && action.streaming) { - action.streaming = false; - action.content = data.full_content; - console.log('文本action已更新为完成状态'); - break; - } - } - - msg.streamingText = ''; - msg.currentStreamingType = null; - this.$forceUpdate(); - } - }); - - // 工具提示事件(可选) - this.socket.on('tool_hint', (data) => { - console.log('工具提示:', data.name); - // 可以在这里添加提示UI - }); - - // 工具准备中事件 - 实时显示 - this.socket.on('tool_preparing', (data) => { - console.log('工具准备中:', data.name); - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - const action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'preparing', - result: null, - message: data.message || `准备调用 ${data.name}...` - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.preparingTools.set(data.id, action); - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 工具状态更新事件 - 实时显示详细状态 - this.socket.on('tool_status', (data) => { - console.log('工具状态:', data); - const target = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (target) { - target.tool.statusDetail = data.detail; - target.tool.statusType = data.status; - this.$forceUpdate(); - return; - } - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - - // 查找对应的工具action并更新状态 - for (let action of msg.actions) { - if (action.type === 'tool' && action.tool.name === data.tool) { - action.tool.statusDetail = data.detail; - action.tool.statusType = data.status; - this.$forceUpdate(); - break; - } - } - } - }); - - // 工具开始(从准备转为执行) - this.socket.on('tool_start', (data) => { - console.log('工具开始执行:', data.name); - let action = null; - if (data.preparing_id && this.preparingTools.has(data.preparing_id)) { - action = this.preparingTools.get(data.preparing_id); - this.preparingTools.delete(data.preparing_id); - } else { - action = this.findToolAction(data.id, data.preparing_id, data.execution_id); - } - if (!action) { - const msg = this.ensureAssistantMessage(); - if (!msg) { - return; - } - action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - status: 'running', - result: null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - } - action.tool.status = 'running'; - action.tool.arguments = data.arguments; - action.tool.message = null; - action.tool.id = data.id; - action.tool.executionId = data.id; - this.registerToolAction(action, data.id); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - }); - - // 更新action(工具完成) - this.socket.on('update_action', (data) => { - console.log('更新action:', data.id, 'status:', data.status); - let targetAction = this.findToolAction(data.id, data.preparing_id, data.execution_id); - if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) { - targetAction = this.preparingTools.get(data.preparing_id); - } - if (!targetAction) { - outer: for (const message of this.messages) { - if (!message.actions) continue; - for (const action of message.actions) { - if (action.type !== 'tool') continue; - const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; - const matchByToolId = action.tool.id === data.id; - const matchByPreparingId = action.id === data.preparing_id; - if (matchByExecution || matchByToolId || matchByPreparingId) { - targetAction = action; - break outer; - } - } - } - } - if (targetAction) { - if (data.status) { - targetAction.tool.status = data.status; - } - if (data.result !== undefined) { - targetAction.tool.result = data.result; - } - if (data.message !== undefined) { - targetAction.tool.message = data.message; - } - if (data.awaiting_content) { - targetAction.tool.awaiting_content = true; - } else if (data.status === 'completed') { - targetAction.tool.awaiting_content = false; - } - if (!targetAction.tool.executionId && (data.execution_id || data.id)) { - targetAction.tool.executionId = data.execution_id || data.id; - } - this.registerToolAction(targetAction, data.execution_id || data.id); - if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { - this.unregisterToolAction(targetAction); - if (data.id) { - this.preparingTools.delete(data.id); - } - if (data.preparing_id) { - this.preparingTools.delete(data.preparing_id); - } - } - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - - // 关键修复:每个工具完成后都更新当前上下文Token - if (data.status === 'completed') { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - this.socket.on('append_payload', (data) => { - console.log('收到append_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: data.path || '未知文件', - forced: !!data.forced, - success: data.success === undefined ? true : !!data.success, - lines: data.lines ?? null, - bytes: data.bytes ?? null - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - this.socket.on('modify_payload', (data) => { - console.log('收到modify_payload事件:', data); - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: data.path || '未知文件', - total: data.total ?? null, - completed: data.completed || [], - failed: data.failed || [], - forced: !!data.forced - }, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } - }); - - // 停止请求确认 - this.socket.on('stop_requested', (data) => { - console.log('停止请求已接收:', data.message); - // 可以显示提示信息 - }); - - // 任务停止 - this.socket.on('task_stopped', (data) => { - console.log('任务已停止:', data.message); - this.resetAllStates(); - }); - - // 任务完成(重点:更新Token统计) - this.socket.on('task_complete', (data) => { - console.log('任务完成', data); - this.resetAllStates(); - - // 任务完成后立即更新Token统计(关键修复) - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - this.fetchConversationTokenStatistics(); - } - }); - - // 聚焦文件更新 - this.socket.on('focused_files_update', (data) => { - this.focusedFiles = data || {}; - // 聚焦文件变化时更新当前上下文Token(关键修复) - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 文件树更新 - this.socket.on('file_tree_update', (data) => { - this.updateFileTree(data); - // 文件树变化时也可能影响上下文 - if (this.currentConversationId) { - setTimeout(() => { - this.updateCurrentContextTokens(); - }, 500); - } - }); - - // 系统消息 - this.socket.on('system_message', (data) => { - if (this.currentMessageIndex >= 0) { - const msg = this.messages[this.currentMessageIndex]; - const action = { - id: `system-${Date.now()}-${Math.random()}`, - type: 'system', - content: data.content, - timestamp: Date.now() - }; - msg.actions.push(action); - this.$forceUpdate(); - this.conditionalScrollToBottom(); - } else { - this.addSystemMessage(data.content); - } - }); - - // 错误处理 - this.socket.on('error', (data) => { - this.addSystemMessage(`错误: ${data.message}`); - // 仅标记当前流结束,避免状态错乱 - this.streamingMessage = false; - this.stopRequested = false; - }); - - // 命令结果 - this.socket.on('command_result', (data) => { - if (data.command === 'clear' && data.success) { - this.messages = []; - this.currentMessageIndex = -1; - this.expandedBlocks.clear(); - // 清除对话时重置Token统计 - this.resetTokenStatistics(); - } else if (data.command === 'status' && data.success) { - this.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); - } else if (!data.success) { - this.addSystemMessage(`命令失败: ${data.message}`); - } - }); - - } catch (error) { - console.error('Socket初始化失败:', error); - } - }, - - registerToolAction(action, executionId = null) { - if (!action || action.type !== 'tool') { - return; - } - const keys = new Set(); - if (action.id) { - keys.add(action.id); - } - if (action.tool && action.tool.id) { - keys.add(action.tool.id); - } - if (executionId) { - keys.add(executionId); - } - if (action.tool && action.tool.executionId) { - keys.add(action.tool.executionId); - } - keys.forEach(key => { - if (!key) { - return; - } - this.toolActionIndex.set(key, action); - }); - }, - - unregisterToolAction(action) { - if (!action || action.type !== 'tool') { - return; - } - const keysToRemove = []; - for (const [key, stored] of this.toolActionIndex.entries()) { - if (stored === action) { - keysToRemove.push(key); - } - } - keysToRemove.forEach(key => this.toolActionIndex.delete(key)); - }, - - findToolAction(id, preparingId, executionId) { - if (!this.toolActionIndex) { - return null; - } - const candidates = [executionId, id, preparingId]; - for (const key of candidates) { - if (key && this.toolActionIndex.has(key)) { - return this.toolActionIndex.get(key); - } - } - return null; - }, - - cleanupStaleToolActions() { - this.messages.forEach(msg => { - if (!msg.actions) { - return; - } - msg.actions.forEach(action => { - if (action.type !== 'tool' || !action.tool) { - return; - } - if (['running', 'preparing'].includes(action.tool.status)) { - action.tool.status = 'stale'; - action.tool.message = action.tool.message || '已被新的响应中断'; - this.unregisterToolAction(action); - } - }); - }); - this.preparingTools.clear(); - this.toolActionIndex.clear(); - }, - - ensureAssistantMessage() { - if (this.currentMessageIndex >= 0) { - return this.messages[this.currentMessageIndex]; - } - const message = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - this.messages.push(message); - this.currentMessageIndex = this.messages.length - 1; - return message; - }, - - // 完整重置所有状态 - resetAllStates() { - console.log('重置所有前端状态'); - this.hideContextMenu(); - - // 重置消息和流状态 - this.streamingMessage = false; - this.currentMessageIndex = -1; - this.stopRequested = false; - - // 清理工具状态 - this.preparingTools.clear(); - this.activeTools.clear(); - this.toolActionIndex.clear(); - - // ✨ 新增:将所有未完成的工具标记为已完成 - this.messages.forEach(msg => { - if (msg.role === 'assistant' && msg.actions) { - msg.actions.forEach(action => { - if (action.type === 'tool' && - (action.tool.status === 'preparing' || action.tool.status === 'running')) { - action.tool.status = 'completed'; - } - }); - } - }); - - // 重置滚动状态 - this.userScrolling = false; - this.autoScrollEnabled = true; - - // 清理Markdown缓存 - if (this.markdownCache) { - this.markdownCache.clear(); - } - - // 强制更新视图 - this.$forceUpdate(); - - this.settingsOpen = false; - this.toolMenuOpen = false; - this.toolSettingsLoading = false; - this.toolSettings = []; - - console.log('前端状态重置完成'); - }, - - // 重置Token统计 - resetTokenStatistics() { - this.currentContextTokens = 0; - this.currentConversationTokens = { - cumulative_input_tokens: 0, - cumulative_output_tokens: 0, - cumulative_total_tokens: 0 - }; - }, - - async loadInitialData() { - try { - console.log('加载初始数据...'); - - const filesResponse = await fetch('/api/files'); - const filesData = await filesResponse.json(); - this.updateFileTree(filesData); - - const focusedResponse = await fetch('/api/focused'); - const focusedData = await focusedResponse.json(); - this.focusedFiles = focusedData || {}; - - await this.fetchTodoList(); - - const statusResponse = await fetch('/api/status'); - const statusData = await statusResponse.json(); - this.projectPath = statusData.project_path || ''; - this.thinkingMode = statusData.thinking_mode || '未知'; - - // 获取当前对话信息 - const statusConversationId = statusData.conversation && statusData.conversation.current_id; - if (statusConversationId) { - if (!this.currentConversationId) { - this.currentConversationId = statusConversationId; - } - // 如果有当前对话,尝试获取标题和Token统计 - try { - const convResponse = await fetch(`/api/conversations/current`); - const convData = await convResponse.json(); - if (convData.success && convData.data) { - this.currentConversationTitle = convData.data.title; - } - await this.fetchAndDisplayHistory(); - // 获取当前对话的Token统计 - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - } catch (e) { - console.warn('获取当前对话标题失败:', e); - } - } - - await this.loadToolSettings(true); - - console.log('初始数据加载完成'); - } catch (error) { - console.error('加载初始数据失败:', error); - } - }, - - async refreshFileTree() { - try { - const response = await fetch('/api/files'); - const data = await response.json(); - this.updateFileTree(data); - } catch (error) { - console.error('刷新文件树失败:', error); - } - }, - - // ========================================== - // Token统计相关方法(完全修复版) - // ========================================== - - async updateCurrentContextTokens() { - // 获取当前上下文Token数(动态计算,包含完整prompt构建) - if (!this.currentConversationId) { - this.currentContextTokens = 0; - return; - } - - try { - console.log(`正在更新当前上下文Token: ${this.currentConversationId}`); - - // 关键修复:使用正确的动态API,包含文件结构+记忆+聚焦文件+终端内容+工具定义 - const response = await fetch(`/api/conversations/${this.currentConversationId}/tokens`); - const data = await response.json(); - - if (data.success && data.data) { - this.currentContextTokens = data.data.total_tokens || 0; - console.log(`当前上下文Token更新: ${this.currentContextTokens}`); - this.$forceUpdate(); - } else { - console.warn('获取当前上下文Token失败:', data.error); - this.currentContextTokens = 0; - } - } catch (error) { - console.warn('获取当前上下文Token异常:', error); - this.currentContextTokens = 0; - } - }, - - async fetchConversationTokenStatistics() { - // 获取对话累计Token统计(加载对话时、任务完成后调用) - if (!this.currentConversationId) { - this.resetTokenStatistics(); - return; - } - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/token-statistics`); - const data = await response.json(); - - if (data.success && data.data) { - // 更新累计统计 - this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0; - this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0; - this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0; - - console.log(`累计Token统计: 输入=${data.data.total_input_tokens}, 输出=${data.data.total_output_tokens}, 总计=${data.data.total_tokens}`); - this.$forceUpdate(); - } else { - console.warn('获取Token统计失败:', data.error); - // 保持当前统计,不重置 - } - } catch (error) { - console.warn('获取Token统计异常:', error); - // 保持当前统计,不重置 - } - }, - - // Token面板折叠/展开切换 - toggleTokenPanel() { - this.tokenPanelCollapsed = !this.tokenPanelCollapsed; - }, - - // ========================================== - // 对话管理核心功能 - // ========================================== - - async loadConversationsList() { - this.conversationsLoading = true; - try { - const response = await fetch(`/api/conversations?limit=${this.conversationsLimit}&offset=${this.conversationsOffset}`); - const data = await response.json(); - - if (data.success) { - if (this.conversationsOffset === 0) { - this.conversations = data.data.conversations; - } else { - this.conversations.push(...data.data.conversations); - } - this.hasMoreConversations = data.data.has_more; - console.log(`已加载 ${this.conversations.length} 个对话`); - - if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { - const latestConversation = this.conversations[0]; - if (latestConversation && latestConversation.id) { - await this.loadConversation(latestConversation.id); - } - } - } else { - console.error('加载对话列表失败:', data.error); - } - } catch (error) { - console.error('加载对话列表异常:', error); - } finally { - this.conversationsLoading = false; - } - }, - - async loadMoreConversations() { - if (this.loadingMoreConversations || !this.hasMoreConversations) return; - - this.loadingMoreConversations = true; - this.conversationsOffset += this.conversationsLimit; - await this.loadConversationsList(); - this.loadingMoreConversations = false; - }, - - async loadConversation(conversationId) { - console.log('加载对话:', conversationId); - - if (conversationId === this.currentConversationId) { - console.log('已是当前对话,跳过加载'); - return; - } - - try { - // 1. 调用加载API - const response = await fetch(`/api/conversations/${conversationId}/load`, { - method: 'PUT' - }); - const result = await response.json(); - - if (result.success) { - console.log('对话加载API成功:', result); - - // 2. 更新当前对话信息 - this.currentConversationId = conversationId; - this.currentConversationTitle = result.title; - history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`); - - // 3. 重置UI状态 - this.resetAllStates(); - - // 4. 延迟获取并显示历史对话内容(关键功能) - setTimeout(() => { - this.fetchAndDisplayHistory(); - }, 300); - - // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) - setTimeout(() => { - this.fetchConversationTokenStatistics(); - this.updateCurrentContextTokens(); - }, 500); - - } else { - console.error('对话加载失败:', result.message); - alert(`加载对话失败: ${result.message}`); - } - } catch (error) { - console.error('加载对话异常:', error); - alert(`加载对话异常: ${error.message}`); - } - }, - - // ========================================== - // 关键功能:获取并显示历史对话内容 - // ========================================== - async fetchAndDisplayHistory() { - console.log('开始获取历史对话内容...'); - - if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { - console.log('没有当前对话ID,跳过历史加载'); - return; - } - - try { - // 使用专门的API获取对话消息历史 - const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); - - if (!messagesResponse.ok) { - console.warn('无法获取消息历史,尝试备用方法'); - // 备用方案:通过状态API获取 - const statusResponse = await fetch('/api/status'); - const status = await statusResponse.json(); - console.log('系统状态:', status); - - // 如果状态中有对话历史字段 - if (status.conversation_history && Array.isArray(status.conversation_history)) { - this.renderHistoryMessages(status.conversation_history); - return; - } - - console.log('备用方案也无法获取历史消息'); - return; - } - - const messagesData = await messagesResponse.json(); - console.log('获取到消息数据:', messagesData); - - if (messagesData.success && messagesData.data && messagesData.data.messages) { - const messages = messagesData.data.messages; - console.log(`发现 ${messages.length} 条历史消息`); - - if (messages.length > 0) { - // 清空当前显示的消息 - this.messages = []; - - // 渲染历史消息 - 这是关键功能 - this.renderHistoryMessages(messages); - - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - - console.log('历史对话内容显示完成'); - } else { - console.log('对话存在但没有历史消息'); - this.messages = []; - } - } else { - console.log('消息数据格式不正确:', messagesData); - this.messages = []; - } - - } catch (error) { - console.error('获取历史对话失败:', error); - console.log('尝试不显示错误弹窗,仅在控制台记录'); - // 不显示alert,避免打断用户体验 - this.messages = []; - } - }, - - // ========================================== - // 关键功能:渲染历史消息 - // ========================================== - renderHistoryMessages(historyMessages) { - console.log('开始渲染历史消息...', historyMessages); - console.log('历史消息数量:', historyMessages.length); - - if (!Array.isArray(historyMessages)) { - console.error('历史消息不是数组格式'); - return; - } - - let currentAssistantMessage = null; - - historyMessages.forEach((message, index) => { - console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); - - if (message.role === 'user') { - // 用户消息 - 先结束之前的assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - this.messages.push({ - role: 'user', - content: message.content || '' - }); - console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); - - } else if (message.role === 'assistant') { - // AI消息 - 如果没有当前assistant消息,创建一个 - if (!currentAssistantMessage) { - currentAssistantMessage = { - role: 'assistant', - actions: [], - streamingThinking: '', - streamingText: '', - currentStreamingType: null - }; - } - - // 处理思考内容 - 支持多种格式 - const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; - - let allThinkingContent = ''; - for (const pattern of thinkPatterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - allThinkingContent += match[1].trim() + '\n'; - } - } - - if (allThinkingContent) { - currentAssistantMessage.actions.push({ - id: `history-think-${Date.now()}-${Math.random()}`, - type: 'thinking', - content: allThinkingContent.trim(), - streaming: false, - timestamp: Date.now() - }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); - } - - // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - - let textContent = content - .replace(/[\s\S]*?<\/think>/g, '') - .replace(/[\s\S]*?<\/thinking>/g, '') - .trim(); - - if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: appendPayloadMeta.path || '未知文件', - forced: !!appendPayloadMeta.forced, - success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, - lines: appendPayloadMeta.lines ?? null, - bytes: appendPayloadMeta.bytes ?? null - }, - timestamp: Date.now() - }); - console.log('添加append占位信息:', appendPayloadMeta.path); - } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: modifyPayloadMeta.path || '未知文件', - total: modifyPayloadMeta.total_blocks ?? null, - completed: modifyPayloadMeta.completed || [], - failed: modifyPayloadMeta.failed || [], - forced: !!modifyPayloadMeta.forced, - details: modifyPayloadMeta.details || [] - }, - timestamp: Date.now() - }); - console.log('添加modify占位信息:', modifyPayloadMeta.path); - } - - if (textContent && !appendPayloadMeta && !modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-text-${Date.now()}-${Math.random()}`, - type: 'text', - content: textContent, - streaming: false, - timestamp: Date.now() - }); - console.log('添加文本内容:', textContent.substring(0, 50) + '...'); - } - - // 处理工具调用 - if (message.tool_calls && Array.isArray(message.tool_calls)) { - message.tool_calls.forEach((toolCall, tcIndex) => { - let arguments_obj = {}; - try { - arguments_obj = typeof toolCall.function.arguments === 'string' - ? JSON.parse(toolCall.function.arguments || '{}') - : (toolCall.function.arguments || {}); - } catch (e) { - console.warn('解析工具参数失败:', e); - arguments_obj = {}; - } - - currentAssistantMessage.actions.push({ - id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, - type: 'tool', - tool: { - id: toolCall.id, - name: toolCall.function.name, - arguments: arguments_obj, - status: 'preparing', - result: null - }, - timestamp: Date.now() - }); - console.log('添加工具调用:', toolCall.function.name); - }); - } - - } else if (message.role === 'tool') { - // 工具结果 - 更新当前assistant消息中对应的工具 - if (currentAssistantMessage) { - // 查找对应的工具action - 使用更灵活的匹配 - let toolAction = null; - - // 优先按tool_call_id匹配 - if (message.tool_call_id) { - toolAction = currentAssistantMessage.actions.find(action => - action.type === 'tool' && - action.tool.id === message.tool_call_id - ); - } - - // 如果找不到,按name匹配最后一个同名工具 - if (!toolAction && message.name) { - const sameNameTools = currentAssistantMessage.actions.filter(action => - action.type === 'tool' && - action.tool.name === message.name - ); - toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 - } - - if (toolAction) { - // 解析工具结果 - let result; - try { - // 尝试解析为JSON - result = JSON.parse(message.content); - } catch (e) { - // 如果不是JSON,就作为纯文本 - result = { - output: message.content, - success: true - }; - } - - toolAction.tool.status = 'completed'; - toolAction.tool.result = result; - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } - console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - if (message.name === 'append_to_file' && result && typeof result === 'object') { - const appendSummary = { - path: result.path || '未知文件', - success: result.success !== false, - summary: result.message || (result.success === false ? '追加失败' : '追加完成'), - lines: result.lines || 0, - bytes: result.bytes || 0, - forced: !!result.forced - }; - currentAssistantMessage.actions.push({ - id: `history-append-${Date.now()}-${Math.random()}`, - type: 'append', - append: appendSummary, - timestamp: Date.now() - }); - } - } else { - console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); - } - } - - } else { - // 其他类型消息(如system)- 先结束当前assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - currentAssistantMessage = null; - } - - console.log('处理其他类型消息:', message.role); - this.messages.push({ - role: message.role, - content: message.content || '' - }); - } - }); - - // 处理最后一个assistant消息 - if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { - this.messages.push(currentAssistantMessage); - } - - console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); - - // 强制更新视图 - this.$forceUpdate(); - - // 确保滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); - }, - - async createNewConversation() { - console.log('创建新对话...'); - - try { - const response = await fetch('/api/conversations', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thinking_mode: this.thinkingMode !== '快速模式' - }) - }); - - const result = await response.json(); - - if (result.success) { - console.log('新对话创建成功:', result.conversation_id); - - // 清空当前消息 - this.messages = []; - this.currentConversationId = result.conversation_id; - this.currentConversationTitle = '新对话'; - history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); - - // 重置Token统计 - this.resetTokenStatistics(); - - // 重置状态 - this.resetAllStates(); - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('创建对话失败:', result.message); - alert(`创建对话失败: ${result.message}`); - } - } catch (error) { - console.error('创建对话异常:', error); - alert(`创建对话异常: ${error.message}`); - } - }, - - async deleteConversation(conversationId) { - if (!confirm('确定要删除这个对话吗?删除后无法恢复。')) { - return; - } - - console.log('删除对话:', conversationId); - - try { - const response = await fetch(`/api/conversations/${conversationId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - console.log('对话删除成功'); - - // 如果删除的是当前对话,清空界面 - if (conversationId === this.currentConversationId) { - this.messages = []; - this.currentConversationId = null; - this.currentConversationTitle = ''; - this.resetAllStates(); - this.resetTokenStatistics(); - history.replaceState({}, '', '/new'); - } - - // 刷新对话列表 - this.conversationsOffset = 0; - await this.loadConversationsList(); - - } else { - console.error('删除对话失败:', result.message); - alert(`删除对话失败: ${result.message}`); - } - } catch (error) { - console.error('删除对话异常:', error); - alert(`删除对话异常: ${error.message}`); - } - }, - - async duplicateConversation(conversationId) { - console.log('复制对话:', conversationId); - try { - const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.duplicate_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - - this.conversationsOffset = 0; - await this.loadConversationsList(); - } else { - const message = result.message || result.error || '复制失败'; - alert(`复制失败: ${message}`); - } - } catch (error) { - console.error('复制对话异常:', error); - alert(`复制对话异常: ${error.message}`); - } - }, - - searchConversations() { - // 简单的搜索功能,实际实现可以调用搜索API - if (this.searchTimer) { - clearTimeout(this.searchTimer); - } - - this.searchTimer = setTimeout(() => { - if (this.searchQuery.trim()) { - console.log('搜索对话:', this.searchQuery); - // TODO: 实现搜索API调用 - // this.searchConversationsAPI(this.searchQuery); - } else { - // 清空搜索,重新加载全部对话 - this.conversationsOffset = 0; - this.loadConversationsList(); - } - }, 300); - }, - - toggleSidebar() { - this.sidebarCollapsed = !this.sidebarCollapsed; - }, - - formatTime(timeString) { - if (!timeString) return ''; - - const date = new Date(timeString); - const now = new Date(); - const diffMs = now - date; - const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - const diffDays = Math.floor(diffHours / 24); - - if (diffHours < 1) { - return '刚刚'; - } else if (diffHours < 24) { - return `${diffHours}小时前`; - } else if (diffDays < 7) { - return `${diffDays}天前`; - } else { - return date.toLocaleDateString('zh-CN', { - month: 'short', - day: 'numeric' - }); - } - }, - - // ========================================== - // 原有功能保持不变 - // ========================================== - - updateFileTree(structure) { - const treeDictionary = structure && structure.tree ? structure.tree : {}; - - const buildNodes = (treeMap) => { - if (!treeMap) { - return []; - } - - const entries = Object.keys(treeMap).map((name) => { - const node = treeMap[name] || {}; - if (node.type === 'folder') { - return { - type: 'folder', - name, - path: node.path || name, - children: buildNodes(node.children) - }; - } - return { - type: 'file', - name, - path: node.path || name, - annotation: node.annotation || '' - }; - }); - - entries.sort((a, b) => { - if (a.type !== b.type) { - return a.type === 'folder' ? -1 : 1; - } - return a.name.localeCompare(b.name, 'zh-CN'); - }); - - return entries; - }; - - const nodes = buildNodes(treeDictionary); - - const expanded = { ...this.expandedFolders }; - const validFolderPaths = new Set(); - - const ensureExpansion = (list, depth = 0) => { - list.forEach((item) => { - if (item.type === 'folder') { - validFolderPaths.add(item.path); - if (expanded[item.path] === undefined) { - expanded[item.path] = false; - } - ensureExpansion(item.children || [], depth + 1); - } - }); - }; - - ensureExpansion(nodes); - - Object.keys(expanded).forEach((path) => { - if (!validFolderPaths.has(path)) { - delete expanded[path]; - } - }); - - this.expandedFolders = expanded; - this.fileTree = nodes; - }, - - toggleFolder(path) { - this.hideContextMenu(); - if (!path) { - return; - } - const current = !!this.expandedFolders[path]; - this.expandedFolders = { - ...this.expandedFolders, - [path]: !current - }; - }, - - toggleTodoPanel() { - this.showTodoList = !this.showTodoList; - }, - - formatTaskStatus(task) { - if (!task) { - return ''; - } - return task.status === 'done' - ? `${this.todoDoneEmoji} 完成` - : `${this.todoPendingEmoji} 未完成`; - }, - - toolCategoryEmoji(categoryId) { - return this.toolCategoryEmojis[categoryId] || '⚙️'; - }, - - async fetchTodoList() { - try { - const response = await fetch('/api/todo-list'); - const data = await response.json(); - if (data.success) { - this.todoList = data.data || null; - } - } catch (error) { - console.error('获取待办列表失败:', error); - } - }, - - triggerFileUpload() { - if (this.uploading) { - return; - } - const input = this.$refs.fileUploadInput; - if (input) { - input.click(); - } - }, - - handleFileSelected(event) { - const fileInput = event?.target; - if (!fileInput || !fileInput.files || fileInput.files.length === 0) { - return; - } - const [file] = fileInput.files; - this.uploadSelectedFile(file); - }, - - resetFileInput() { - const input = this.$refs.fileUploadInput; - if (input) { - input.value = ''; - } - }, - - async uploadSelectedFile(file) { - if (!file || this.uploading) { - this.resetFileInput(); - return; - } - - this.uploading = true; - - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/upload', { - method: 'POST', - body: formData - }); - - let result = {}; - try { - result = await response.json(); - } catch (parseError) { - throw new Error('服务器响应无法解析'); - } - - if (!response.ok || !result.success) { - const message = result.error || result.message || '上传失败'; - throw new Error(message); - } - - await this.refreshFileTree(); - alert(`上传成功:${result.path || file.name}`); - } catch (error) { - console.error('文件上传失败:', error); - alert(`文件上传失败:${error.message}`); - } finally { - this.uploading = false; - this.resetFileInput(); - } - }, - - handleSendOrStop() { - if (this.streamingMessage) { - this.stopTask(); - } else { - this.sendMessage(); - } - }, - - sendMessage() { - if (this.streamingMessage || !this.isConnected) { - return; - } - - if (!this.inputMessage.trim()) { - return; - } - - const message = this.inputMessage; - - if (message.startsWith('/')) { - this.socket.emit('send_command', { command: message }); - this.inputMessage = ''; - this.settingsOpen = false; - return; - } - - this.messages.push({ - role: 'user', - content: message - }); - - this.currentMessageIndex = -1; - this.socket.emit('send_message', { message: message, conversation_id: this.currentConversationId }); - this.inputMessage = ''; - this.autoScrollEnabled = true; - this.scrollToBottom(); - this.settingsOpen = false; - - // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) - setTimeout(() => { - if (this.currentConversationId) { - this.updateCurrentContextTokens(); - } - }, 1000); - }, - - // 新增:停止任务方法 - stopTask() { - if (this.streamingMessage && !this.stopRequested) { - this.socket.emit('stop_task'); - this.stopRequested = true; - console.log('发送停止请求'); - } - this.settingsOpen = false; - }, - - clearChat() { - if (confirm('确定要清除所有对话记录吗?')) { - this.socket.emit('send_command', { command: '/clear' }); - } - this.settingsOpen = false; - }, - - async compressConversation() { - if (!this.currentConversationId) { - alert('当前没有可压缩的对话。'); - return; - } - - if (this.compressing) { - return; - } - - const confirmed = confirm('确定要压缩当前对话记录吗?压缩后会生成新的对话副本。'); - if (!confirmed) { - return; - } - - this.settingsOpen = false; - this.compressing = true; - - try { - const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, { - method: 'POST' - }); - - const result = await response.json(); - - if (response.ok && result.success) { - const newId = result.compressed_conversation_id; - if (newId) { - this.currentConversationId = newId; - } - console.log('对话压缩完成:', result); - } else { - const message = result.message || result.error || '压缩失败'; - alert(`压缩失败: ${message}`); - } - } catch (error) { - console.error('压缩对话异常:', error); - alert(`压缩对话异常: ${error.message}`); - } finally { - this.compressing = false; - } - }, - - toggleToolMenu() { - if (!this.isConnected) { - return; - } - const nextState = !this.toolMenuOpen; - this.toolMenuOpen = nextState; - if (nextState) { - this.settingsOpen = false; - this.loadToolSettings(); - } - }, - - handleClickOutsideToolMenu(event) { - if (!this.toolMenuOpen) { - return; - } - const dropdown = this.$refs.toolDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.toolMenuOpen = false; - } - }, - - applyToolSettingsSnapshot(categories) { - if (!Array.isArray(categories)) { - return; - } - this.toolSettings = categories.map((item) => ({ - id: item.id, - label: item.label || item.id, - enabled: !!item.enabled, - tools: Array.isArray(item.tools) ? item.tools : [] - })); - this.toolSettingsLoading = false; - }, - - async loadToolSettings(force = false) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - if (!force && this.toolSettings.length > 0) { - return; - } - this.toolSettingsLoading = true; - try { - const response = await fetch('/api/tool-settings'); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('获取工具设置失败:', data); - this.toolSettingsLoading = false; - } - } catch (error) { - console.error('获取工具设置异常:', error); - this.toolSettingsLoading = false; - } - }, - - async updateToolCategory(categoryId, enabled) { - if (!this.isConnected) { - return; - } - if (this.toolSettingsLoading) { - return; - } - const previousSnapshot = this.toolSettings.map((item) => ({ ...item })); - this.toolSettings = this.toolSettings.map((item) => { - if (item.id === categoryId) { - return { ...item, enabled }; - } - return item; - }); - try { - const response = await fetch('/api/tool-settings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - category: categoryId, - enabled - }) - }); - const data = await response.json(); - if (response.ok && data.success && Array.isArray(data.categories)) { - this.applyToolSettingsSnapshot(data.categories); - } else { - console.warn('更新工具设置失败:', data); - this.toolSettings = previousSnapshot; - } - } catch (error) { - console.error('更新工具设置异常:', error); - this.toolSettings = previousSnapshot; - } - this.toolSettingsLoading = false; - }, - - toggleSettings() { - if (!this.isConnected) { - return; - } - this.settingsOpen = !this.settingsOpen; - if (this.settingsOpen) { - this.toolMenuOpen = false; - } - }, - - toggleFocusPanel() { - this.rightCollapsed = !this.rightCollapsed; - if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - this.settingsOpen = false; - }, - - handleClickOutsideSettings(event) { - if (!this.settingsOpen) { - return; - } - const dropdown = this.$refs.settingsDropdown; - if (dropdown && !dropdown.contains(event.target)) { - this.settingsOpen = false; - } - }, - - addSystemMessage(content) { - this.messages.push({ - role: 'system', - content: content - }); - this.conditionalScrollToBottom(); - }, - - toggleBlock(id) { - if (this.expandedBlocks.has(id)) { - this.expandedBlocks.delete(id); - } else { - this.expandedBlocks.add(id); - } - this.$forceUpdate(); - }, - - // 修复:工具相关方法 - 接收tool对象而不是name - getToolIcon(tool) { - const toolName = typeof tool === 'string' ? tool : tool.name; - const icons = { - 'create_file': '📄', - 'sleep': '⏱️', - 'read_file': '📖', - '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': '❗' - }; - return icons[toolName] || '⚙️'; - }, - - getToolAnimationClass(tool) { - // 根据工具状态返回不同的动画类 - if (tool.status === 'hinted') { - return 'hint-animation pulse-slow'; - } else if (tool.status === 'preparing') { - return 'preparing-animation'; - } else if (tool.status === 'running') { - const animations = { - 'create_file': 'file-animation', - 'read_file': 'read-animation', - 'delete_file': 'file-animation', - 'rename_file': 'file-animation', - 'modify_file': 'file-animation', - 'append_to_file': 'file-animation', - 'create_folder': 'file-animation', - 'focus_file': 'focus-animation', - 'unfocus_file': 'focus-animation', - 'web_search': 'search-animation', - 'extract_webpage': 'search-animation', - 'save_webpage': 'file-animation', - 'run_python': 'code-animation', - 'run_command': 'terminal-animation', - 'update_memory': 'memory-animation', - 'sleep': 'wait-animation', - 'terminal_session': 'terminal-animation', - 'terminal_input': 'terminal-animation', - 'terminal_snapshot': 'terminal-animation', - 'terminal_reset': 'terminal-animation', - 'todo_create': 'file-animation', - 'todo_update_task': 'file-animation', - 'todo_finish': 'file-animation', - 'todo_finish_confirm': 'file-animation' - }; - return animations[tool.name] || 'default-animation'; - } - return ''; - }, - - // 修复:获取工具状态文本 - getToolStatusText(tool) { - // 优先使用自定义消息 - if (tool.message) { - return tool.message; - } - - if (tool.status === 'hinted') { - return `可能需要 ${tool.name}...`; - } else if (tool.status === 'preparing') { - return `准备调用 ${tool.name}...`; - } else if (tool.status === 'running') { - const texts = { - 'create_file': '正在创建文件...', - 'read_file': '正在读取文件...', - 'sleep': '正在等待...', - 'delete_file': '正在删除文件...', - 'rename_file': '正在重命名文件...', - 'modify_file': '正在修改文件...', - 'append_to_file': '正在追加文件...', - 'create_folder': '正在创建文件夹...', - 'focus_file': '正在聚焦文件...', - 'unfocus_file': '正在取消聚焦...', - 'web_search': '正在搜索网络...', - 'extract_webpage': '正在提取网页...', - 'save_webpage': '正在保存网页...', - 'run_python': '正在执行Python代码...', - 'run_command': '正在执行命令...', - 'update_memory': '正在更新记忆...', - 'terminal_session': '正在管理终端会话...', - 'terminal_input': '正在发送终端输入...', - 'terminal_snapshot': '正在获取终端快照...', - 'terminal_reset': '正在重置终端...' - }; - return texts[tool.name] || '正在执行...'; - } else if (tool.status === 'completed') { - // 修复:完成状态的文本 - const texts = { - 'create_file': '文件创建成功', - 'read_file': '文件读取完成', - 'delete_file': '文件删除成功', - 'sleep': '等待完成', - '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': '终端已重置' - }; - return texts[tool.name] || '执行完成'; - } else { - // 其他状态 - return `${tool.name} - ${tool.status}`; - } - }, - - getToolDescription(tool) { - // 如果有状态详情,优先显示 - if (tool.statusDetail) { - return tool.statusDetail; - } - - if (tool.result && typeof tool.result === 'object') { - if (tool.result.path) { - return tool.result.path.split('/').pop(); - } - } - - if (tool.arguments) { - if (tool.arguments.path) { - return tool.arguments.path.split('/').pop(); - } - if (tool.arguments.target_path) { - return tool.arguments.target_path.split('/').pop(); - } - if (tool.arguments.query) { - return `"${tool.arguments.query}"`; - } - if (tool.arguments.command) { - return tool.arguments.command; - } - if (tool.arguments.seconds) { - return `${tool.arguments.seconds} 秒`; - } - } - return ''; - }, - - formatSearchTopic(filters) { - const mapping = { - 'general': '通用', - 'news': '新闻', - 'finance': '金融' - }; - const topic = (filters && filters.topic) ? String(filters.topic).toLowerCase() : 'general'; - return mapping[topic] || '通用'; - }, - - formatSearchTime(filters) { - if (!filters) { - return '未限定时间'; - } - if (filters.time_range) { - const mapping = { - 'day': '过去24小时', - 'week': '过去7天', - 'month': '过去30天', - 'year': '过去365天' - }; - return mapping[filters.time_range] || `相对范围:${filters.time_range}`; - } - if (typeof filters.days === 'number') { - return `过去${filters.days}天`; - } - if (filters.start_date && filters.end_date) { - return `${filters.start_date} 至 ${filters.end_date}`; - } - return '未限定时间'; - }, - - renderMarkdown(text, isStreaming = false) { - if (!text) return ''; - - if (typeof marked === 'undefined') { - return text; - } - - marked.setOptions({ - breaks: true, - gfm: true, - sanitize: false - }); - - if (!isStreaming) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - - const cacheKey = `${text.length}_${text.substring(0, 100)}`; - - if (this.markdownCache.has(cacheKey)) { - return this.markdownCache.get(cacheKey); - } - } - - let html = marked.parse(text); - html = this.wrapCodeBlocks(html, isStreaming); - - if (!isStreaming && text.length < 10000) { - if (!this.markdownCache) { - this.markdownCache = new Map(); - } - this.markdownCache.set(`${text.length}_${text.substring(0, 100)}`, html); - if (this.markdownCache.size > 20) { - const firstKey = this.markdownCache.keys().next().value; - this.markdownCache.delete(firstKey); - } - } - - // 只在非流式状态处理(流式状态由renderLatexInRealtime处理) - if (!isStreaming) { - setTimeout(() => { - // 代码高亮 - if (typeof Prism !== 'undefined') { - const codeBlocks = document.querySelectorAll('.code-block-wrapper pre code:not([data-highlighted])'); - codeBlocks.forEach(block => { - try { - Prism.highlightElement(block); - block.setAttribute('data-highlighted', 'true'); - } catch (e) { - console.warn('代码高亮失败:', e); - } - }); - } - - // LaTeX最终渲染 - if (typeof renderMathInElement !== 'undefined') { - const elements = document.querySelectorAll('.text-output .text-content:not(.streaming-text)'); - elements.forEach(element => { - if (element.hasAttribute('data-math-rendered')) return; - - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - element.setAttribute('data-math-rendered', 'true'); - } catch (e) { - console.warn('LaTeX渲染失败:', e); - } - }); - } - }, 100); - } - - return html; - }, - // 实时LaTeX渲染(用于流式输出) - renderLatexInRealtime() { - if (typeof renderMathInElement === 'undefined') { - return; - } - - // 使用requestAnimationFrame优化性能 - if (this._latexRenderTimer) { - cancelAnimationFrame(this._latexRenderTimer); - } - - this._latexRenderTimer = requestAnimationFrame(() => { - const elements = document.querySelectorAll('.text-output .streaming-text'); - elements.forEach(element => { - try { - renderMathInElement(element, { - delimiters: [ - {left: '$$', right: '$$', display: true}, - {left: '$', right: '$', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '\\(', right: '\\)', display: false} - ], - throwOnError: false, - trust: true - }); - } catch (e) { - // 忽略错误,继续渲染 - } - }); - }); - }, - // 用字符串替换包装代码块 - // 用字符串替换包装代码块 - 添加streaming参数 - wrapCodeBlocks(html, isStreaming = false) { - // 如果是流式输出,不包装代码块,保持原样 - if (isStreaming) { - return html; - } - - let counter = 0; - - // 匹配
...
- return html.replace(/
]*)>([\s\S]*?)<\/code><\/pre>/g, (match, attributes, content) => {
-                    // 提取语言
-                    const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
-                    const language = langMatch ? langMatch[1] : 'text';
-                    
-                    // 生成唯一ID
-                    const blockId = `code-${Date.now()}-${counter++}`;
-                    
-                    // 转义引号用于data属性
-                    const escapedContent = content
-                        .replace(/&/g, '&')
-                        .replace(//g, '>')
-                        .replace(/"/g, '"');
-                    
-                    // 构建新的HTML,保持code元素原样
-                    return `
-            
-
- ${language} - -
-
${content}
-
`; - }); - }, - - getLanguageClass(path) { - const ext = path.split('.').pop().toLowerCase(); - const langMap = { - 'py': 'language-python', - 'js': 'language-javascript', - 'html': 'language-html', - 'css': 'language-css', - 'json': 'language-json', - 'md': 'language-markdown', - 'txt': 'language-plain' - }; - return langMap[ext] || 'language-plain'; - }, - - scrollToBottom() { - setTimeout(() => { - const messagesArea = this.$refs.messagesArea; - if (messagesArea) { - // 标记为程序触发的滚动 - if (this._setScrollingFlag) { - this._setScrollingFlag(true); - } - - messagesArea.scrollTop = messagesArea.scrollHeight; - - // 滚动完成后重置标记 - setTimeout(() => { - if (this._setScrollingFlag) { - this._setScrollingFlag(false); - } - }, 100); - } - }, 50); - }, - - conditionalScrollToBottom() { - // 严格检查:只在明确允许时才滚动 - if (this.autoScrollEnabled === true && this.userScrolling === false) { - this.scrollToBottom(); - } - }, - - toggleScrollLock() { - const currentlyLocked = this.autoScrollEnabled && !this.userScrolling; - if (currentlyLocked) { - this.autoScrollEnabled = false; - this.userScrolling = true; - } else { - this.autoScrollEnabled = true; - this.userScrolling = false; - this.scrollToBottom(); - } - }, - - // 面板调整方法 - startResize(panel, event) { - this.isResizing = true; - this.resizingPanel = panel; - if (panel === 'right' && this.rightCollapsed) { - this.rightCollapsed = false; - if (this.rightWidth < this.minPanelWidth) { - this.rightWidth = this.minPanelWidth; - } - } - document.addEventListener('mousemove', this.handleResize); - document.addEventListener('mouseup', this.stopResize); - document.body.style.userSelect = 'none'; - document.body.style.cursor = 'col-resize'; - event.preventDefault(); - }, - - handleResize(event) { - if (!this.isResizing) return; - - const containerWidth = document.querySelector('.main-container').offsetWidth; - - if (this.resizingPanel === 'left') { - let newWidth = event.clientX - (this.sidebarCollapsed ? 60 : 300); - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.leftWidth = newWidth; - } else if (this.resizingPanel === 'right') { - let newWidth = containerWidth - event.clientX; - newWidth = Math.max(this.minPanelWidth, Math.min(newWidth, this.maxPanelWidth)); - this.rightWidth = newWidth; - } else if (this.resizingPanel === 'conversation') { - // 对话侧边栏宽度调整 - let newWidth = event.clientX; - newWidth = Math.max(200, Math.min(newWidth, 400)); - // 这里可以动态调整对话侧边栏宽度,暂时不实现 - } - }, - - stopResize() { - this.isResizing = false; - this.resizingPanel = null; - document.removeEventListener('mousemove', this.handleResize); - document.removeEventListener('mouseup', this.stopResize); - document.body.style.userSelect = ''; - document.body.style.cursor = ''; - }, - - // 格式化token显示(修复NaN问题) - formatTokenCount(tokens) { - // 确保tokens是数字,防止NaN - const num = Number(tokens) || 0; - if (num < 1000) { - return num.toString(); - } else if (num < 1000000) { - return (num / 1000).toFixed(1) + 'K'; - } else { - return (num / 1000000).toFixed(1) + 'M'; - } - } - } - }); - - app.component('file-node', { - name: 'FileNode', - props: { - node: { - type: Object, - required: true - }, - level: { - type: Number, - default: 0 - }, - expandedFolders: { - type: Object, - required: true - } - }, - emits: ['toggle-folder', 'context-menu'], - computed: { - isExpanded() { - if (this.node.type !== 'folder') { - return false; - } - const value = this.expandedFolders[this.node.path]; - return value === undefined ? true : value; - }, - folderPadding() { - return { - paddingLeft: `${12 + this.level * 16}px` - }; - }, - filePadding() { - return { - paddingLeft: `${40 + this.level * 16}px` - }; - } - }, - methods: { - toggle() { - if (this.node.type === 'folder') { - this.$emit('toggle-folder', this.node.path); - } - } - }, - template: ` -
-
- -
- -
-
-
- 📄 - {{ node.name }} - {{ node.annotation }} -
-
- ` - }); - - app.mount('#app'); - console.log('Vue应用初始化完成'); -} - -window.addEventListener('load', () => { - bootstrapApp(); -}); diff --git a/sub_agent/static/backup_20251026_184346/claude-colors-simple.html b/sub_agent/static/backup_20251026_184346/claude-colors-simple.html deleted file mode 100644 index aebd82d..0000000 --- a/sub_agent/static/backup_20251026_184346/claude-colors-simple.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - Claude颜色展示 - 简化版 - - - -
-

Claude聊天界面颜色

- -

核心颜色

-
-
-
-
背景颜色
-
#eeece2
-
-
- -
-
-
-
字体颜色
-
#3d3929
-
-
- -
-
-
-
品牌主色
-
#da7756
-
-
- -
-
-
-
按钮颜色
-
#bd5d3a
-
-
- -

字体样式

-
- 字体栈:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif -

- 斜体样式展示 | 粗体样式展示 -
- -

界面元素

- - -

对话界面模拟

-
-
你好,Claude!今天天气怎么样?
-
你好!我无法获取实时天气信息,但你可以通过天气应用查看当地天气。
-
- -
- 这种配色方案创造出温暖、专业的对话环境,背景是米白色(#eeece2),文字是深棕色(#3d3929),给人舒适友好的感觉。 -
-
- - diff --git a/sub_agent/static/backup_20251026_184346/debug.html b/sub_agent/static/backup_20251026_184346/debug.html deleted file mode 100644 index 0a945c9..0000000 --- a/sub_agent/static/backup_20251026_184346/debug.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - AI Agent 调试监控 - - - - - -
-

🔧 AI Agent 调试监控

- - -
-

连接状态

-
Socket连接: {{ isConnected ? '已连接' : '未连接' }}
-
当前消息索引: {{ currentMessageIndex }}
-
消息总数: {{ messages.length }}
-
- - -
-

控制面板

- - - - -
- - -
-

WebSocket事件流 (最新 {{ events.length }} 条)

-
-
- {{ event.time }} - {{ event.type }} - : {{ JSON.stringify(event.data).slice(0, 200) }} -
-
-
- - -
-

当前消息Actions状态 (消息 #{{ currentMessageIndex }})

-
-
-
Action #{{ idx }}: {{ action.type }}
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
- 工具: {{ action.tool.name }}
- 状态: {{ action.tool.status }}
- ID: {{ action.tool.id }}
- 有结果: {{ !!action.tool.result }} -
-
- Streaming: {{ action.streaming }}
- Content长度: {{ action.content ? action.content.length : 0 }} -
-
-
-
- - -
-

最新消息原始数据

-
{{ JSON.stringify(messages[messages.length - 1], null, 2) }}
-
-
- - - - \ No newline at end of file diff --git a/sub_agent/static/backup_20251026_184346/index.html b/sub_agent/static/backup_20251026_184346/index.html deleted file mode 100644 index 22bac8f..0000000 --- a/sub_agent/static/backup_20251026_184346/index.html +++ /dev/null @@ -1,591 +0,0 @@ - - - - - - AI Agent System - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-

正在连接服务器...

-

如果长时间无响应,请刷新页面

-
- - - -
- - -
-
- - - - - diff --git a/sub_agent/static/backup_20251026_184346/login.html b/sub_agent/static/backup_20251026_184346/login.html deleted file mode 100644 index cff0db6..0000000 --- a/sub_agent/static/backup_20251026_184346/login.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 登录 - AI Agent - - - - - - - diff --git a/sub_agent/static/backup_20251026_184346/register.html b/sub_agent/static/backup_20251026_184346/register.html deleted file mode 100644 index 0943626..0000000 --- a/sub_agent/static/backup_20251026_184346/register.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - 注册 - AI Agent - - - -
-

创建账号

-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - - diff --git a/sub_agent/static/backup_20251026_184346/style.css b/sub_agent/static/backup_20251026_184346/style.css deleted file mode 100644 index 8049abf..0000000 --- a/sub_agent/static/backup_20251026_184346/style.css +++ /dev/null @@ -1,2049 +0,0 @@ -/* static/style-enhanced.css - 增强版,包含对话历史管理功能 */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --claude-bg: #eeece2; - --claude-panel: rgba(255, 255, 255, 0.82); - --claude-sidebar: rgba(255, 255, 255, 0.68); - --claude-border: rgba(118, 103, 84, 0.25); - --claude-text: #3d3929; - --claude-text-secondary: #7f7766; - --claude-muted: rgba(121, 109, 94, 0.4); - --claude-accent: #da7756; - --claude-accent-strong: #bd5d3a; - --claude-highlight: rgba(218, 119, 86, 0.14); - --claude-button-hover: #c76541; - --claude-button-active: #a95331; - --claude-shadow: 0 14px 36px rgba(61, 57, 41, 0.12); - --claude-success: #76b086; - --claude-warning: #d99845; -} - -body { - font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - background: var(--claude-bg); - color: var(--claude-text); - height: 100vh; - overflow: hidden; - -webkit-font-smoothing: antialiased; -} - -/* 顶部状态栏 */ -.header { - background: var(--claude-panel); - backdrop-filter: blur(24px); - border-bottom: 1px solid var(--claude-border); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 56px; - z-index: 100; - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.05); -} - -.header-left { - display: flex; - align-items: center; - gap: 20px; -} - -.logo { - font-size: 18px; - font-weight: 600; - color: var(--claude-text); - letter-spacing: 0.02em; -} - -.project-path { - color: var(--claude-text-secondary); - font-size: 14px; -} - -.header-right { - display: flex; - align-items: center; - gap: 20px; -} - -.thinking-mode { - background: var(--claude-accent); - color: #fff8f2; - padding: 5px 14px; - border-radius: 980px; - font-size: 13px; - font-weight: 500; - letter-spacing: 0.04em; -} - -.connection-status { - font-size: 13px; - color: var(--claude-text-secondary); - display: flex; - align-items: center; - gap: 6px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--claude-muted); - transition: all 0.3s ease; -} - -.status-dot.active { - background: var(--claude-success); - box-shadow: 0 0 8px rgba(118, 176, 134, 0.45); -} - -/* 主容器 */ -.main-container { - display: flex; - height: calc(100vh - 56px); - background: var(--claude-bg); - position: relative; - align-items: stretch; -} - -/* ========================================= */ -/* 新增:对话历史侧边栏样式 */ -/* ========================================= */ - -.conversation-sidebar { - width: 280px; - background: var(--claude-sidebar); - border-right: none; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 50; - backdrop-filter: blur(12px); - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; - border-bottom: 1px solid var(--claude-border); -} - -.conversation-collapsed-spacer { - flex: 1 1 auto; - background: transparent; -} - -.conversation-sidebar.collapsed { - width: 50px; - overflow: hidden; - height: calc(100vh - 56px) !important; - min-height: calc(100vh - 56px) !important; -} - -.conversation-sidebar.collapsed .conversation-header { - justify-content: center; -} - -.conversation-sidebar.collapsed .conversation-header .toggle-sidebar-btn { - margin-left: 0; -} - -.conversation-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(118, 103, 84, 0.12); - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - position: sticky; - top: 0; - z-index: 60; - min-height: 68px; -} - -.new-conversation-btn { - flex: 1; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border: 1px solid rgba(189, 93, 58, 0.4); - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 6px; - box-shadow: 0 6px 14px rgba(189, 93, 58, 0.18); -} - -.new-conversation-btn:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); - transform: translateY(-1px); -} - -.btn-icon { - font-size: 16px; - font-weight: bold; -} - -.btn-text { - white-space: nowrap; -} - -.toggle-sidebar-btn { - background: rgba(255, 255, 255, 0.7); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.18); - padding: 6px 8px; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; - min-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.toggle-sidebar-btn:hover { - background: rgba(255, 255, 255, 0.9); -} - -.conversation-search { - padding: 12px; - background: transparent; - border-bottom: 1px solid var(--claude-border); -} - -.search-input { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--claude-border); - border-radius: 6px; - font-size: 13px; - background: rgba(255, 255, 255, 0.55); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.search-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.85); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.18); -} - -.conversation-list { - flex: 1; - overflow-y: auto; - padding: 8px 0; - background: transparent; -} - -.loading-conversations, -.no-conversations { - text-align: center; - color: var(--claude-text-secondary); - padding: 30px 15px; - font-size: 13px; -} - -.conversation-item { - padding: 12px 16px; - margin: 2px 8px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid transparent; - position: relative; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.04); -} - -.conversation-item:hover { - background: rgba(255, 255, 255, 0.85); - border-color: rgba(218, 119, 86, 0.35); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.conversation-item.active { - background: rgba(218, 119, 86, 0.18); - border-color: var(--claude-accent); - box-shadow: 0 10px 28px rgba(189, 93, 58, 0.18); -} - -.conversation-title { - font-size: 14px; - font-weight: 500; - color: var(--claude-text); - margin-bottom: 6px; - word-wrap: break-word; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.3; -} - -.conversation-meta { - font-size: 11px; - color: var(--claude-text-secondary); - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2px; -} - -.conversation-time { - flex-shrink: 0; -} - -.conversation-counts { - text-align: right; - white-space: nowrap; - font-size: 10px; -} - -.conversation-actions { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); - opacity: 0; - transition: opacity 0.2s ease; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; -} - -.conversation-item:hover .conversation-actions { - opacity: 1; -} - -.conversation-action-btn { - border: none; - border-radius: 4px; - font-size: 13px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - color: white; -} - -.conversation-action-btn.copy-btn { - background: var(--claude-accent); -} - -.conversation-action-btn.copy-btn:hover { - background: var(--claude-button-hover); - transform: translateY(-1px); -} - -.conversation-action-btn.delete-btn { - background: #d85a42; -} - -.conversation-action-btn.delete-btn:hover { - background: #bf422b; - transform: translateY(-1px); -} - -.load-more { - padding: 12px 16px; - text-align: center; -} - -.load-more-btn { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent); - border: 1px solid rgba(218, 119, 86, 0.35); - padding: 6px 14px; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; -} - -.load-more-btn:hover:not(:disabled) { - background: var(--claude-accent); - color: #fffdf8; -} - -.load-more-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* 当前对话信息栏 */ -.current-conversation-info { - background: var(--claude-panel); - backdrop-filter: blur(18px); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -.conversation-title-display { - font-weight: 500; - color: var(--claude-text); -} - -.conversation-message-count { - font-size: 12px; -} - -/* 拖拽手柄 */ -.resize-handle { - width: 4px; - background: var(--claude-sidebar); - cursor: col-resize; - position: relative; - transition: background 0.2s; - flex-shrink: 0; -} - -.resize-handle:hover { - background: rgba(218, 119, 86, 0.22); -} - -/* 侧边栏 */ -.sidebar { - background: rgba(255, 255, 255, 0.75); - overflow-y: auto; - flex-shrink: 0; - border-left: none; -} - -.sidebar-header { - padding: 23px; - border-bottom: 1px solid var(--claude-border); - position: sticky; - top: 0; - background: rgba(255, 255, 255, 0.85); - z-index: 10; - backdrop-filter: blur(16px); - display: flex; - align-items: center; - gap: 10px; -} - -.sidebar-header h3 { - font-size: 15px; - font-weight: 600; - color: var(--claude-text); - margin: 0; -} - -.sidebar-view-toggle { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(118, 103, 84, 0.3); - background: rgba(255, 255, 255, 0.85); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.2s ease; - color: var(--claude-text); -} - -.sidebar-view-toggle:hover { - background: rgba(255, 255, 255, 0.95); -} - -.sidebar.right-sidebar.collapsed { - width: 0 !important; - min-width: 0 !important; - border-left: none; - overflow: hidden; -} - -.sidebar.right-sidebar.collapsed .sidebar-header, -.sidebar.right-sidebar.collapsed .focused-files { - display: none; -} - -/* 文件树 */ -.file-tree { - padding: 12px 0 20px; - color: var(--claude-text); -} - -.todo-panel { - padding: 16px 20px 24px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.todo-empty { - font-size: 14px; - color: var(--claude-text-secondary); - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.8); - border: 1px dashed var(--claude-border); - text-align: center; -} - -.todo-task { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.18); - border-radius: 10px; - font-size: 13px; - color: var(--claude-text); - background: rgba(255, 255, 255, 0.9); -} - -.todo-task.done { - background: rgba(92, 190, 125, 0.08); - border-color: rgba(92, 190, 125, 0.3); -} - -.todo-task-title { - flex: 1; -} - -.todo-task-status { - font-weight: 600; - font-size: 12px; - margin-left: 12px; -} - -.todo-instruction { - font-size: 12px; - color: var(--claude-text-secondary); - margin-top: 8px; -} - -.file-node-wrapper { - font-size: 14px; - color: var(--claude-text); - font-family: inherit; -} - -.folder-header { - width: 100%; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s ease, transform 0.2s ease; - text-align: left; - font-family: inherit; -} - -.folder-header:hover { - background: rgba(218, 119, 86, 0.12); - transform: translateX(2px); -} - -.folder-arrow { - width: 12px; - text-align: center; - color: var(--claude-text-secondary); - font-size: 12px; -} - -.folder-icon, -.file-icon { - width: 18px; - text-align: center; -} - -.folder-name, -.file-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: inherit; -} - - -.folder-children { - margin-left: 14px; - padding-left: 6px; - border-left: 1px dashed rgba(0, 0, 0, 0.08); -} - -.folder-children .file-node-wrapper { - margin-left: 0; -} - -.file-node.file-leaf { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border-radius: 8px; - font-family: inherit; -} - -.file-node.file-leaf:hover { - background: rgba(218, 119, 86, 0.1); -} - -.file-node .annotation { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -.context-menu { - position: fixed; - background: #ffffff; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.15); - z-index: 3000; - min-width: 180px; - padding: 6px 0; - backdrop-filter: blur(8px); -} - -.context-menu button { - width: 100%; - padding: 8px 18px; - background: transparent; - border: none; - text-align: left; - font-size: 13px; - color: #1f2933; - cursor: pointer; - transition: background 0.15s ease; -} - -.context-menu button:hover { - background: rgba(59, 130, 246, 0.12); -} - -.context-menu button:disabled { - color: #9ca3af; - cursor: not-allowed; - background: transparent; -} - -/* 聊天容器 */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - background: rgba(255, 255, 255, 0.78); - min-width: 0; - min-height: 0; - position: relative; - backdrop-filter: blur(6px); -} - -/* 消息区域 */ -.messages-area { - flex: 1; - overflow-y: auto; - padding: 24px; - min-height: 0; -} - -.scroll-lock-toggle { - position: absolute; - right: 28px; - bottom: 200px; - z-index: 25; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.scroll-lock-btn { - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.92); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); -} - -.scroll-lock-btn:hover { - transform: translateY(-2px); - box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); -} - -.scroll-lock-toggle.locked .scroll-lock-btn { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); -} - -.scroll-lock-btn svg { - width: 18px; - height: 18px; - stroke: var(--claude-text); - stroke-width: 1.8; - fill: none; - transition: stroke 0.2s ease; -} - -.scroll-lock-toggle.locked .scroll-lock-btn svg { - stroke: var(--claude-accent); -} - -/* 滚动条 */ -.messages-area::-webkit-scrollbar, -.sidebar::-webkit-scrollbar, -.conversation-list::-webkit-scrollbar { - width: 8px; -} - -.messages-area::-webkit-scrollbar-track, -.sidebar::-webkit-scrollbar-track, -.conversation-list::-webkit-scrollbar-track { - background: transparent; -} - -.messages-area::-webkit-scrollbar-thumb, -.sidebar::-webkit-scrollbar-thumb, -.conversation-list::-webkit-scrollbar-thumb { - background: rgba(121, 109, 94, 0.4); - border-radius: 8px; -} - -/* 消息块 */ -.message-block { - margin-bottom: 24px; -} - -/* 用户消息 */ -.user-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 8px; - letter-spacing: 0.02em; -} - -.user-message .message-text { - background: rgba(255, 255, 255, 0.85); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - white-space: pre-wrap; -} - -/* AI消息 */ -.assistant-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 12px; - letter-spacing: 0.02em; -} - -.assistant-message .message-text { - background: rgba(218, 119, 86, 0.12); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - border-left: 4px solid var(--claude-accent); - white-space: pre-wrap; -} - -/* Action项入场动画 */ -@keyframes slideInFade { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes quickFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.action-item { - animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 0ms; -} - -/* 流式内容:立即显示 */ -.action-item.streaming-content, -.action-item.immediate-show { - animation: quickFadeIn 0.2s ease-out both; - animation-delay: 0ms !important; -} - -/* 已完成的工具:统一延迟看动画 */ -.action-item.completed-tool { - animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 100ms; -} - -/* 文本输出 - 无缩进,与思考块对齐 */ -.text-output { - margin: 16px 0; - color: var(--claude-text); - font-size: 15px; - line-height: 1.7; -} - -.text-output .text-content { - padding: 0 20px 0 15px; /* 左边56px与思考块对齐 */ -} - -.text-output .text-content p { - margin-bottom: 12px; -} - -.text-output .text-content p:last-child { - margin-bottom: 0; -} - -.append-block { - margin: 12px 0; - padding: 12px 16px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); - display: flex; - flex-direction: column; - gap: 8px; -} - -.append-block.append-error { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); - box-shadow: inset 0 0 0 1px rgba(216, 90, 66, 0.08); -} - -.append-header { - display: flex; - align-items: center; - gap: 10px; - font-weight: 600; - color: var(--claude-text); - font-size: 15px; -} - -.append-block.append-error .append-header { - color: #b0432a; -} - -.append-icon { - font-size: 18px; -} - -.append-summary { - flex: 1; -} - -.append-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-meta-item { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.append-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder { - margin: 12px 0; -} - -.modify-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.modify-placeholder-content .modify-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder-content .modify-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-placeholder { - margin: 12px 0; -} - - -.append-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.append-placeholder-content .append-warning { - margin-top: 4px; -} - -.append-placeholder.append-error .append-placeholder-content { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); -} -/* Markdown表格样式 */ -.text-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; -} - -.text-content table th, -.text-content table td { - border: 1px solid #e0e0e0; - padding: 10px 14px; - text-align: left; -} - -.text-content table th { - background: rgba(218, 119, 86, 0.08); - font-weight: 600; - color: var(--claude-text); -} - -.text-content table tr:hover { - background: #fafafa; -} - -/* 代码块包装器 - 防止跳动 */ -.code-block-wrapper { - border: 2px solid rgba(118, 103, 84, 0.25); - border-radius: 12px; - overflow: hidden; - margin: 16px 0; - background: rgba(255, 255, 255, 0.78); - min-height: 80px; /* 添加最小高度防止跳动 */ - contain: layout; /* CSS containment 优化渲染 */ -} - -/* 代码块头部 */ -.code-block-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(118, 103, 84, 0.25); -} - -.code-language { - color: var(--claude-text-secondary); - font-size: 13px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-weight: 500; -} - -.copy-code-btn { - background: transparent; - color: var(--claude-text-secondary); - border: 1px solid rgba(121, 109, 94, 0.35); - padding: 6px 10px; - border-radius: 6px; - font-size: 16px; - cursor: pointer; - transition: all 0.2s ease; - line-height: 1; -} - -.copy-code-btn:hover { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent-strong); -} - -.copy-code-btn.copied { - background: var(--claude-success); - border-color: var(--claude-success); - color: #f6fff8; -} - -/* 代码块内容区 */ -.code-block-wrapper pre { - background: #ffffff !important; - padding: 16px !important; - margin: 0 !important; - border-radius: 0 !important; - border: none !important; -} - -.code-block-wrapper pre code { - background: transparent !important; - padding: 0 !important; - color: #000000; -} -/* 流式文本 */ -.streaming-text { - display: block; -} - -.cursor-blink { - animation: blink 1s steps(1) infinite; - color: var(--claude-accent); - font-weight: normal; -} - -/* 思考内容样式 - 保留换行 */ -.thinking-content { - white-space: pre-wrap; /* 保留换行和空格 */ - word-wrap: break-word; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.6; - color: var(--claude-text-secondary); -} - -/* 可折叠块 */ -.collapsible-block { - background: rgba(255, 255, 255, 0.78); - border-radius: 12px; - margin-bottom: 12px; - overflow: hidden; - border: 1px solid var(--claude-border); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.collapsible-block:hover { - box-shadow: 0 14px 28px rgba(61, 57, 41, 0.1); -} - -.collapsible-header { - padding: 14px 20px; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - user-select: none; - background: rgba(255, 255, 255, 0.72); - transition: background-color 0.2s ease; - position: relative; -} - -.collapsible-header:hover { - background: rgba(218, 119, 86, 0.07); -} - -/* 箭头 */ -.arrow { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: var(--claude-text-secondary); -} - -.arrow::before { - content: '›'; - font-size: 18px; -} - -.collapsible-block.expanded .arrow { - transform: rotate(90deg); -} - -/* 状态图标 */ -.status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; -} - -/* 内容区域 */ -.collapsible-content { - max-height: 0; - overflow: hidden; - opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.collapsible-block.expanded .collapsible-content { - max-height: 600px; - overflow-y: auto; - opacity: 1; -} - -.content-inner { - padding: 20px 20px 20px 56px; - color: var(--claude-text-secondary); - font-size: 14px; - line-height: 1.6; -} - -/* 工具动画 */ -@keyframes brain-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.15); } -} - -.thinking-icon { - animation: brain-pulse 1.5s ease-in-out infinite; -} - -@keyframes file-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -.file-animation { - animation: file-bounce 1.5s ease-in-out infinite; -} - -@keyframes scan-effect { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -.read-animation { - animation: scan-effect 1.5s ease-in-out infinite; -} - -@keyframes search-rotate { - 0% { transform: rotate(-10deg); } - 50% { transform: rotate(10deg); } - 100% { transform: rotate(-10deg); } -} - -.search-animation { - animation: search-rotate 1s ease-in-out infinite; -} - -@keyframes code-breathe { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -.code-animation { - animation: code-breathe 2s ease-in-out infinite; -} - -.terminal-animation::after { - content: '_'; - animation: blink 1s steps(1) infinite; - margin-left: 2px; -} - -@keyframes memory-fade { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.95); } -} - -.memory-animation { - animation: memory-fade 2s ease-in-out infinite; -} - -@keyframes focus-glow { - 0%, 100% { filter: brightness(1); } - 50% { filter: brightness(1.3); } -} - -.focus-animation { - animation: focus-glow 1.5s ease-in-out infinite; -} - -/* 进度条 */ -.progress-indicator { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--claude-accent); - animation: progress 2s ease-in-out infinite; - opacity: 0; - transition: opacity 0.3s ease; -} - -.collapsible-block.processing .progress-indicator { - opacity: 1; -} - -@keyframes progress { - 0% { width: 0%; left: 0%; } - 50% { width: 40%; left: 30%; } - 100% { width: 0%; left: 100%; } -} - -/* 状态文字 */ -.status-text { - font-size: 14px; - color: var(--claude-text); - font-weight: 500; -} - -.processing .status-text { - color: var(--claude-text-secondary); -} - -/* 完成勾号 */ -.checkmark { - color: var(--claude-success); - font-weight: 600; - font-size: 16px; -} - -/* 工具描述 */ -.tool-desc { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -/* 输入区域 */ -.token-wrapper { - flex-shrink: 0; -} - -.input-area { - background: rgba(255, 255, 255, 0.82); - border-top: 1px solid var(--claude-border); - padding: 20px; - backdrop-filter: blur(12px); - flex-shrink: 0; -} - -.input-wrapper { - display: flex; - flex-direction: column; - gap: 12px; -} - -.message-input { - width: 100%; - padding: 14px 16px; - border: 1px solid var(--claude-border); - border-radius: 12px; - font-size: 15px; - resize: none; - font-family: inherit; - background: rgba(255, 255, 255, 0.75); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.message-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.2); -} - -.input-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - flex-wrap: wrap; -} - -/* 按钮 */ -.btn { - padding: 10px 24px; - border: none; - border-radius: 980px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - letter-spacing: 0.03em; -} - -.send-btn { - background: var(--claude-accent); - color: #fffdf8; - box-shadow: 0 12px 28px rgba(189, 93, 58, 0.25); -} - -.send-btn:hover:not(:disabled) { - background: var(--claude-button-hover); - transform: scale(1.02); - box-shadow: 0 14px 32px rgba(189, 93, 58, 0.3); -} - -.send-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.stop-btn { - background: #d85a42; - color: #fffaf5; -} - -.stop-btn:hover { - background: #bf422b; -} - -.settings-dropdown { - position: relative; - display: flex; - align-items: center; -} - -.tool-dropdown { - position: relative; - display: flex; - align-items: center; - margin-right: auto; -} - -.upload-control { - display: flex; - align-items: center; -} - -.file-input-hidden { - display: none; -} - -.upload-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.upload-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.upload-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.tool-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.tool-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.tool-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.settings-btn { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.settings-btn:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.95); -} - -.settings-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* 适应折叠屏/矮屏幕,保证输入区完整可见 */ -@media (max-height: 900px) { - .messages-area { - padding: 16px 18px; - } - .token-wrapper { - margin-bottom: 8px; - } - .input-area { - padding: 14px; - } - .message-input { - padding: 12px 14px; - min-height: 120px; - } - .btn { - padding: 8px 18px; - } -} - -.settings-menu { - position: absolute; - right: 0; - bottom: calc(100% + 12px); - background: rgba(255, 255, 255, 0.96); - border: 1px solid var(--claude-border); - border-radius: 12px; - box-shadow: var(--claude-shadow); - 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: rgba(255, 255, 255, 0.96) 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: 320px; - max-width: 380px; - padding: 14px 18px; -} - -.settings-menu.tool-menu::before { - left: 32px; - right: auto; -} - -.tool-menu .tool-menu-status, -.tool-menu .tool-menu-empty { - font-size: 13px; - color: rgba(61, 57, 41, 0.78); - text-align: left; -} - -.tool-menu .tool-menu-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.tool-menu .tool-category-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 10px; - background: rgba(255, 255, 255, 0.88); - font-size: 13px; -} - -.tool-menu .tool-category-item.disabled { - opacity: 0.55; -} - -.tool-menu .tool-category-label { - flex: 1; - white-space: nowrap; - font-size: 13px; - font-weight: 500; - color: var(--claude-text); - display: inline-flex; - align-items: center; - gap: 6px; -} - -.tool-category-icon { - font-size: 16px; -} - -.tool-menu .tool-category-toggle { - width: auto !important; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 18px; - text-align: center; - white-space: nowrap; -} - -.menu-btn { - width: 100%; - padding: 8px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 8px; - font-size: 13px; - font-weight: 500; - text-align: left; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - cursor: pointer; - transition: all 0.2s ease; -} - -.menu-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); - transform: translateY(-1px); -} - -.menu-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.menu-btn.compress-entry { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-success); -} - -.menu-btn.compress-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.menu-btn.clear-entry { - background: rgba(255, 255, 255, 0.78); - color: #bf422b; -} - -.menu-btn.clear-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.settings-menu-enter-active, -.settings-menu-leave-active { - transition: opacity 0.18s ease, transform 0.18s ease; -} - -.settings-menu-enter-from, -.settings-menu-leave-to { - opacity: 0; - transform: translateY(6px); -} - -/* 聚焦文件 */ -.focused-files { - padding: 16px; -} - -.no-files { - text-align: center; - color: var(--claude-text-secondary); - padding: 60px 20px; - font-size: 14px; -} - -.file-tabs { - display: flex; - flex-direction: column; - gap: 12px; -} - -.file-tab { - border: 1px solid var(--claude-border); - border-radius: 12px; - overflow: hidden; - background: rgba(255, 255, 255, 0.75); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.tab-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-bottom: 1px solid var(--claude-border); -} - -.file-name { - font-weight: 500; - color: var(--claude-text); -} - -.file-size { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.file-content { - max-height: 320px; - overflow-y: auto; - background: #1e1e1e; -} - -.file-content pre { - margin: 0; - padding: 16px; -} - -.file-content code { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - color: #aed581; -} - -/* 系统消息 */ -.system-message { - text-align: center; - color: var(--claude-text-secondary); - font-size: 13px; - margin: 20px 0; - padding: 10px; - background: rgba(255, 255, 255, 0.7); - border-radius: 12px; - border: 1px solid var(--claude-border); -} - -/* Markdown样式 */ -.text-content h1, -.text-content h2, -.text-content h3 { - margin-top: 20px; - margin-bottom: 12px; - font-weight: 600; - color: var(--claude-text); -} - -.text-content p { - margin-bottom: 12px; -} - -.text-content pre { - background: rgba(255, 255, 255, 0.78); - padding: 16px; - border-radius: 12px; - overflow-x: auto; - margin: 16px 0; - border: 1px solid var(--claude-border); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.text-content code { - background: rgba(218, 119, 86, 0.1); - padding: 2px 6px; - border-radius: 4px; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--claude-accent-strong); -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} - - -/* ========================================= */ -/* 响应式设计 */ -/* ========================================= */ - -@media (max-width: 1200px) { - .conversation-sidebar { - width: 260px; - } - - .left-sidebar, - .right-sidebar { - width: 300px !important; - } -} - -@media (max-width: 768px) { - .conversation-sidebar { - position: absolute; - left: 0; - top: 0; - height: 100%; - z-index: 1000; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); - transform: translateX(-100%); - } - - .conversation-sidebar:not(.collapsed) { - transform: translateX(0); - } - - .left-sidebar, - .right-sidebar { - display: none; - } - - .resize-handle { - display: none; - } -} -.conversation-stats { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--claude-text-secondary); -} - -.token-count { - color: var(--claude-accent); - font-weight: 500; -} -/* ========================================= */ -/* Token 统计面板样式(无缝一体版)*/ -/* ========================================= */ - -/* Token区域包装器 */ -.token-wrapper { - position: relative; - z-index: 5; - margin-bottom: 0; -} - -/* 当前对话信息栏 - 移除底部边框 */ -.current-conversation-info { - position: relative; - z-index: 10; - background: var(--claude-panel); - backdrop-filter: blur(18px); - border-bottom: none; /* 移除边框,让它和下面的面板融为一体 */ - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - color: var(--claude-text-secondary); - border-radius: 0; /* 顶部保持直角 */ - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.06); -} - -/* Token面板 - 与标题栏完全一体,底部圆角 */ -.token-display-panel { - background: var(--claude-panel); - backdrop-filter: blur(18px); - border: none; - border-radius: 0 0 16px 16px; - box-shadow: 0 8px 18px rgba(189, 93, 58, 0.12); - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); - width: 100%; - margin: 0; - padding: 0; -} - -/* 展开状态 */ -.token-display-panel:not(.collapsed) { - height: 80px; - opacity: 1; -} - -/* 收起状态 */ -.token-display-panel.collapsed { - height: 0; - opacity: 0; - border: none; - box-shadow: none; -} - -.token-panel-content { - padding: 16px 24px; - height: 100%; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.token-display-panel.collapsed .token-panel-content { - opacity: 0; - pointer-events: none; -} - -.token-stats { - display: flex; - gap: 32px; - align-items: center; - justify-content: center; - font-size: 13px; - height: 100%; -} - -.token-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 4px; - min-width: 80px; -} - -.token-label { - color: var(--claude-text-secondary); - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.token-value { - color: var(--claude-text); - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; -} - -.token-value.current { - color: var(--claude-accent); - font-size: 20px; -} -.token-value.input { color: var(--claude-success); } -.token-value.output { color: var(--claude-warning); } - -.token-separator { - width: 1px; - height: 35px; - background: linear-gradient(to bottom, - transparent, - rgba(218, 119, 86, 0.25) 20%, - rgba(218, 119, 86, 0.25) 80%, - transparent - ); - margin: 0 8px; -} - -/* 切换按钮 - 独立定位 */ -.token-toggle-btn { - position: absolute; - right: 24px; - bottom: -18px; /* 相对于wrapper底部 */ - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(218, 119, 86, 0.3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 15; - font-size: 14px; - font-weight: bold; -} - -/* 展开状态 */ -.token-toggle-btn:not(.collapsed) { - background: linear-gradient(135deg, #ffffff 0%, rgba(255, 248, 242, 0.9) 100%); - color: var(--claude-accent); - box-shadow: 0 3px 10px rgba(189, 93, 58, 0.18); -} - -/* 收起状态 - 在标题栏下方露出一半 */ -.token-toggle-btn.collapsed { - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border-color: rgba(255, 248, 242, 0.55); - box-shadow: 0 3px 11px rgba(189, 93, 58, 0.22); -} - -.token-toggle-btn:hover { - transform: scale(1.05); - box-shadow: 0 5px 16px rgba(189, 93, 58, 0.26); -} - -.token-toggle-btn.collapsed:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); -} - -.token-toggle-btn:active { - transform: scale(1.02); -} - -/* 箭头样式 - 移除浮动动画 */ -.token-toggle-btn span { - transition: all 0.3s ease; - display: inline-block; -} - -/* 移除动画效果 */ -/* .token-toggle-btn:not(.collapsed) span { - animation: arrowBounceUp 2s ease-in-out infinite; -} - -.token-toggle-btn.collapsed span { - animation: arrowBounceDown 2s ease-in-out infinite; -} */ - -/* 保留动画定义,但不使用 */ -@keyframes arrowBounceUp { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -@keyframes arrowBounceDown { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(3px); } -} - -/* 响应式调整 */ -@media (max-width: 768px) { - .token-stats { - gap: 16px; - } - - .token-item { - min-width: 60px; - } - - .token-value { - font-size: 15px; - } - - .token-value.current { - font-size: 17px; - } - - .token-label { - font-size: 10px; - } - - .token-toggle-btn { - width: 32px; - height: 32px; - font-size: 12px; - right: 16px; - } -} -/* Markdown列表样式 - 修复偏左问题 */ -.text-content ul, -.text-content ol { - margin-left: 24px; /* 增加左边距 */ - padding-left: 0; - margin-bottom: 12px; -} - -.text-content ul { - list-style-type: disc; /* 实心圆点 */ -} - -.text-content ul ul { - list-style-type: circle; /* 空心圆点 */ - margin-top: 6px; -} - -.text-content ol { - list-style-type: decimal; /* 数字列表 */ -} - -.text-content li { - margin-bottom: 6px; - line-height: 1.6; -} - -/* 搜索结果展示 */ -.search-meta { - font-size: 14px; - color: var(--claude-text-secondary); - line-height: 1.6; - margin-bottom: 12px; - word-break: break-word; -} - -.search-result-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.search-result-item { - padding: 10px 12px; - border: 1px solid rgba(118, 103, 84, 0.16); - border-radius: 8px; - background: rgba(255, 255, 255, 0.65); - word-break: break-word; -} - -.search-result-title { - font-weight: 600; - margin-bottom: 6px; - color: var(--claude-text); -} - -.search-result-url a { - color: var(--claude-accent-strong); - text-decoration: none; - word-break: break-all; -} - -.search-result-url a:hover { - text-decoration: underline; -} - -.search-empty { - font-size: 13px; - color: var(--claude-text-secondary); - font-style: italic; -} diff --git a/sub_agent/static/backup_20251026_184346/terminal.html b/sub_agent/static/backup_20251026_184346/terminal.html deleted file mode 100644 index f0a31f0..0000000 --- a/sub_agent/static/backup_20251026_184346/terminal.html +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - AI Terminal Monitor - 实时终端查看器 - - - - - - - - -
-

- 🖥️ AI Terminal Monitor - - 连接中... -

-
- - -
-
- - 等待终端会话... -
-
- - -
- -
-
-
- 无活动会话 - - -
-
-
-
-
-
-
-
-
- - - -
- - -
-
-
- - 延迟: 0ms -
-
- 📡 - 0 KB/s -
-
- 🕐 - -
-
-
- AI Agent Terminal Monitor v1.0 -
-
- - -
- - - - - - - - - - diff --git a/sub_agent/web_server.py b/sub_agent/web_server.py index 118531b..7f71aea 100644 --- a/sub_agent/web_server.py +++ b/sub_agent/web_server.py @@ -46,6 +46,9 @@ from config import ( AGENT_VERSION, SUB_AGENT_MAX_ACTIVE, SUB_AGENT_DEFAULT_TIMEOUT, + SUB_AGENT_STATE_FILE, + DATA_DIR, + SUB_AGENT_TASKS_BASE_DIR, ) from modules.user_manager import UserManager, UserWorkspace from modules.gui_file_manager import GuiFileManager @@ -173,28 +176,207 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte return f"{header}\n{body}" +def _format_relative_path(path: Optional[str], workspace: Optional[str]) -> str: + """将绝对路径转换为相对 workspace 的表示,默认返回原始路径。""" + if not path: + return "" + try: + target = Path(path).resolve() + if workspace: + base = Path(workspace).resolve() + rel = target.relative_to(base) + rel_text = rel.as_posix() + if not rel_text or rel_text == ".": + return "." + return f"./{rel_text}" + except Exception: + pass + return Path(path).as_posix() + + def build_sub_agent_instruction(meta: Dict[str, Any]) -> str: """根据任务元数据构建初始指令消息。""" + workspace_dir = meta.get("workspace_dir") + references_dir = meta.get("references_dir") + deliverables_dir = meta.get("deliverables_dir") + workspace_label = _format_relative_path(workspace_dir, workspace_dir) or "." + references_label = _format_relative_path(references_dir, workspace_dir) or "references/" + deliverables_label = _format_relative_path(deliverables_dir, workspace_dir) or "deliverables/" + lines = [ f"子智能体任务 - 代号 {meta.get('agent_id')} (task_id={meta.get('task_id')})", "", meta.get("summary", ""), meta.get("task", ""), "", - "工作目录:", - f"- workspace: {meta.get('workspace_dir')}", - f"- references: {meta.get('references_dir')}", - f"- deliverables: {meta.get('deliverables_dir')}", + "工作区与目录说明:", + f"- workspace: {workspace_label}(唯一可写根目录,所有操作只能在此路径内进行)", + f"- references: {references_label}(系统已自动创建,仅供查阅,不得修改原文件)", + f"- deliverables: {deliverables_label}(系统已自动创建,用于存放交付文件与 result.md)", "", - "请立即分析任务并开始执行。必要时可向主智能体请求澄清。", + "交付要求:", + "- 所有成果放入 deliverables/ 下,保持清晰的目录结构;", + "- deliverables/ 必须包含 result.md,说明完成情况、交付列表、遗留风险和下一步建议;", + "- references/ 只读,如需引用请复制到 workspace/ 再处理;", + "- 子智能体无法与主智能体实时沟通,如信息不足请在 result.md 中说明。", + "- 最后必须调用 finish_sub_agent,并在 reason 中概括完成情况;若暂未完成,也要说明阻塞原因并继续执行直至可交付。", + "", + "提示:references/ 与 deliverables/ 目录均已自动创建,如需额外子目录请在 workspace/ 内自行管理。", + "", + "请立即分析任务、规划步骤并开始执行,过程中记录关键操作,确保交付即可直接被主智能体使用。", ] return "\n".join(lines) +def build_finish_tool_reminder(meta: Dict[str, Any]) -> str: + """构建提醒子智能体调用结束工具的提示语。""" + workspace_dir = meta.get("workspace_dir") + deliverables_dir = meta.get("deliverables_dir") + deliverables_label = _format_relative_path(deliverables_dir, workspace_dir) or "deliverables" + if deliverables_label in {"", "."}: + result_path = "./result.md" + else: + normalized = deliverables_label.rstrip("/") + result_path = f"{normalized}/result.md" + return ( + f"⚠️ 检测到你已停止输出,但尚未调用 finish_sub_agent。请确认 {deliverables_label} 内的交付文件与 {result_path} 已准备完毕," + "再调用 finish_sub_agent(reason=...) 正式结束任务;如果任务仍未完成,请继续完成剩余步骤并在完成后立即调用 finish_sub_agent。" + ) + + def get_active_sub_agent_count() -> int: return len([task for task in sub_agent_tasks.values() if task.get("status") in {"pending", "running"}]) +def find_sub_agent_conversation_file(conv_id: str) -> Optional[Path]: + """在已知目录中搜索子智能体对话文件。""" + possible_dirs = [] + tasks_root = Path(SUB_AGENT_TASKS_BASE_DIR).expanduser().resolve() + if tasks_root.exists(): + possible_dirs.append(tasks_root) + data_root = Path(DATA_DIR).expanduser().resolve() + if data_root.exists(): + possible_dirs.append(data_root) + users_root = Path("users").resolve() + if users_root.exists(): + possible_dirs.append(users_root) + + for base in possible_dirs: + try: + matches = list(base.rglob(f"{conv_id}.json")) + except Exception: + matches = [] + for match in matches: + try: + if match.name == f"{conv_id}.json": + return match + except Exception: + continue + return None + + +def build_workspace_tree(root_path: str, max_depth: int = 5) -> Dict[str, Any]: + """构建工作目录的文件树,用于子智能体只读视图。""" + root = Path(root_path).expanduser().resolve() + if not root.exists(): + return { + "path": str(root), + "tree": {}, + "folders": [], + "files": [], + "total_files": 0, + "total_size": 0 + } + + structure = { + "path": str(root), + "tree": {}, + "folders": [], + "files": [], + "total_files": 0, + "total_size": 0 + } + + def scan_directory(path: Path, tree: Dict[str, Any], depth: int = 0): + if depth > max_depth: + return + try: + entries = sorted( + [p for p in path.iterdir() if not p.name.startswith('.')], + key=lambda p: (not p.is_dir(), p.name.lower()) + ) + except PermissionError: + return + + for entry in entries: + relative_path = str(entry.relative_to(root)) + if entry.is_dir(): + structure["folders"].append({ + "name": entry.name, + "path": relative_path + }) + tree[entry.name] = { + "type": "folder", + "path": relative_path, + "children": {} + } + scan_directory(entry, tree[entry.name]["children"], depth + 1) + else: + try: + size = entry.stat().st_size + modified = datetime.fromtimestamp(entry.stat().st_mtime).isoformat() + except OSError: + size = 0 + modified = "" + structure["files"].append({ + "name": entry.name, + "path": relative_path, + "size": size, + "modified": modified + }) + structure["total_files"] += 1 + structure["total_size"] += size + tree[entry.name] = { + "type": "file", + "path": relative_path, + "size": size, + "modified": modified + } + + scan_directory(root, structure["tree"]) + return structure + + +def _load_state_data() -> Dict[str, Any]: + state_file = Path(SUB_AGENT_STATE_FILE).expanduser().resolve() + if not state_file.exists(): + return {} + try: + with state_file.open('r', encoding='utf-8') as f: + return json.load(f) + except Exception: + return {} + + +def load_persisted_task(task_id: str) -> Optional[Dict[str, Any]]: + data = _load_state_data() + tasks = data.get("tasks") or {} + return tasks.get(task_id) + + +def iter_persisted_tasks(): + data = _load_state_data() + tasks = data.get("tasks") or {} + return list(tasks.values()) + + +def get_task_record(task_id: str) -> Optional[Dict[str, Any]]: + task = sub_agent_tasks.get(task_id) + if task: + return task + return load_persisted_task(task_id) + + def broadcast_sub_agent_event(task_id: str, event_type: str, payload: Optional[Dict[str, Any]] = None): room = f"sub_agent_{task_id}" data = {"task_id": task_id} @@ -231,6 +413,23 @@ def _normalize_conversation_id(value: Optional[str]) -> Optional[str]: return value if value.startswith("conv_") else f"conv_{value}" +def _extract_parent_conversation(info: Optional[Dict[str, Any]]) -> Optional[str]: + """尝试从任务记录中提取父对话ID。""" + if not info: + return None + service_payload = info.get("service_payload") or {} + candidates = [ + info.get("parent_conversation_id"), + info.get("conversation_id"), + service_payload.get("parent_conversation_id"), + ] + for candidate in candidates: + normalized = _normalize_conversation_id(candidate) + if normalized: + return normalized + return None + + def _inject_sub_agent_script(html: str, task_id: str, parent_conv: Optional[str], sub_conv: Optional[str]) -> str: script = ( "