myagent-ai 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/web/api_server.py +117 -14
- package/web/ui/chat/chat.css +20 -3
- package/web/ui/chat/chat_main.js +13 -5
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -153,7 +153,7 @@ class ApiServer:
|
|
|
153
153
|
# 消息队列(用于存放待执行的消息,key: session_id)
|
|
154
154
|
self._msg_queues: Dict[str, List[Dict]] = {}
|
|
155
155
|
# 任务列表内存存储(exec 模式,替代 task.md)
|
|
156
|
-
self._task_list_store: dict[str, list] = {} #
|
|
156
|
+
self._task_list_store: dict[str, list] = {} # session_id -> [{text, status}] (per-session, not per-agent)
|
|
157
157
|
self._setup_routes()
|
|
158
158
|
self._runner: Optional[web.AppRunner] = None
|
|
159
159
|
|
|
@@ -264,9 +264,13 @@ class ApiServer:
|
|
|
264
264
|
# ── 会话管理 ──
|
|
265
265
|
r.add_get("/api/sessions", self.handle_list_sessions)
|
|
266
266
|
r.add_get("/api/sessions/{sid}/messages", self.handle_get_messages)
|
|
267
|
+
r.add_get("/api/session/messages", self.handle_get_messages_query) # query param version (supports / in sid)
|
|
267
268
|
r.add_delete("/api/sessions/{sid}", self.handle_delete_session)
|
|
269
|
+
r.add_delete("/api/session", self.handle_delete_session_query) # query param version
|
|
268
270
|
r.add_delete("/api/sessions/{sid}/messages", self.handle_clear_session_messages)
|
|
271
|
+
r.add_delete("/api/session/messages", self.handle_clear_session_messages_query) # query param version
|
|
269
272
|
r.add_put("/api/sessions/{sid}/rename", self.handle_rename_session)
|
|
273
|
+
r.add_put("/api/session/rename", self.handle_rename_session_query) # query param version
|
|
270
274
|
r.add_get("/api/memory/stats", self.handle_memory_stats)
|
|
271
275
|
r.add_get("/api/memory/search", self.handle_memory_search)
|
|
272
276
|
r.add_get("/api/memory/long-term", self.handle_list_long_term)
|
|
@@ -491,7 +495,7 @@ class ApiServer:
|
|
|
491
495
|
clean_message, agent_system_prompt = self._build_agent_chat_context(agent_path, agent_cfg, message)
|
|
492
496
|
|
|
493
497
|
# ── 执行模式: 将任务规划上下文注入到 system_prompt(而非用户消息)──
|
|
494
|
-
task_plan_context = self._build_task_plan_context(agent_path, chat_mode, message)
|
|
498
|
+
task_plan_context = self._build_task_plan_context(agent_path, chat_mode, message, session_id=session_id)
|
|
495
499
|
if task_plan_context:
|
|
496
500
|
agent_system_prompt += "\n\n## 任务规划\n" + task_plan_context
|
|
497
501
|
|
|
@@ -518,7 +522,7 @@ class ApiServer:
|
|
|
518
522
|
try:
|
|
519
523
|
tl = self._extract_task_list_json(response)
|
|
520
524
|
if tl is not None:
|
|
521
|
-
self._task_list_store[
|
|
525
|
+
self._task_list_store[session_id] = tl
|
|
522
526
|
except Exception as tp_err:
|
|
523
527
|
logger.warning(f"任务列表更新失败: {tp_err}")
|
|
524
528
|
|
|
@@ -589,7 +593,7 @@ class ApiServer:
|
|
|
589
593
|
model_chain = self._build_model_chain(agent_cfg, agent_path)
|
|
590
594
|
|
|
591
595
|
# Build context with task plan injection into system_prompt (NOT user message)
|
|
592
|
-
task_plan_context = self._build_task_plan_context(agent_path, chat_mode, message)
|
|
596
|
+
task_plan_context = self._build_task_plan_context(agent_path, chat_mode, message, session_id=session_id)
|
|
593
597
|
clean_message, agent_system_prompt = self._build_agent_chat_context(agent_path, agent_cfg, message)
|
|
594
598
|
if task_plan_context:
|
|
595
599
|
agent_system_prompt += "\n\n## 任务规划\n" + task_plan_context
|
|
@@ -627,7 +631,7 @@ class ApiServer:
|
|
|
627
631
|
try:
|
|
628
632
|
tl = self._extract_task_list_json(full_response)
|
|
629
633
|
if tl is not None:
|
|
630
|
-
self._task_list_store[
|
|
634
|
+
self._task_list_store[session_id] = tl
|
|
631
635
|
await response.write(("data: " + json.dumps({"type": "task_list_update", "tasks": tl}) + "\n\n").encode())
|
|
632
636
|
except Exception:
|
|
633
637
|
pass
|
|
@@ -678,7 +682,26 @@ class ApiServer:
|
|
|
678
682
|
|
|
679
683
|
except Exception as e:
|
|
680
684
|
logger.error(f"Stream chat error: {e}", exc_info=True)
|
|
681
|
-
|
|
685
|
+
# ── 兜底保存:流式输出被中断时,确保已推送的内容不丢失 ──
|
|
686
|
+
# 场景:用户在流式输出过程中刷新页面,SSE 连接断开,_stream_process_message
|
|
687
|
+
# 中的保存逻辑未执行,导致 user 消息在但 assistant 消息丢失
|
|
688
|
+
try:
|
|
689
|
+
if self.core.memory and session_id:
|
|
690
|
+
# 检查最后一条消息是否为 user(说明 assistant 回复未保存)
|
|
691
|
+
conv = self.core.memory.get_conversation(session_id, limit=5)
|
|
692
|
+
if conv and conv[-1].role == "user":
|
|
693
|
+
logger.info(f"[{session_id}] 流式中断兜底保存:补充一条中断提示")
|
|
694
|
+
self.core.memory.add_short_term(
|
|
695
|
+
session_id=session_id,
|
|
696
|
+
role="assistant",
|
|
697
|
+
content="⚠️ [连接中断] 上次回复因页面刷新被中断,部分内容可能丢失。请重新发送指令继续。",
|
|
698
|
+
)
|
|
699
|
+
except Exception as save_err:
|
|
700
|
+
logger.warning(f"兜底保存失败: {save_err}")
|
|
701
|
+
try:
|
|
702
|
+
await response.write(("data: " + json.dumps({"type": "error", "error": str(e)}) + "\n\n").encode())
|
|
703
|
+
except Exception:
|
|
704
|
+
pass
|
|
682
705
|
finally:
|
|
683
706
|
# Release execution lock
|
|
684
707
|
if needs_lock_check and self._execution_lock["locked_by"] == agent_path:
|
|
@@ -740,7 +763,7 @@ class ApiServer:
|
|
|
740
763
|
|
|
741
764
|
return response
|
|
742
765
|
|
|
743
|
-
def _build_task_plan_context(self, agent_path: str, chat_mode: str, original_message: str) -> str:
|
|
766
|
+
def _build_task_plan_context(self, agent_path: str, chat_mode: str, original_message: str, session_id: str = "") -> str:
|
|
744
767
|
"""构建任务规划上下文(仅 exec 模式,注入到 system_prompt 中)"""
|
|
745
768
|
if chat_mode != "exec":
|
|
746
769
|
return ""
|
|
@@ -757,10 +780,12 @@ class ApiServer:
|
|
|
757
780
|
" - 执行完一个操作后停下来,等待结果反馈后再决定下一步\n"
|
|
758
781
|
" - 不要一次性执行多个操作\n"
|
|
759
782
|
"3. **回复格式**:先写纯文本分析/总结 → 再写 ```tasklist``` 更新进度 → 最后写 ```action``` 执行操作(如有)\n"
|
|
783
|
+
"4. **任务完成**:当所有步骤都标记为 done 时,用 ```action``` 输出 {\"type\": \"final_answer\", \"content\": \"...\"} 结束任务。\n"
|
|
760
784
|
)
|
|
761
785
|
|
|
762
|
-
#
|
|
763
|
-
|
|
786
|
+
# 从内存读取当前任务列表(按 session 隔离)
|
|
787
|
+
store_key = session_id or agent_path
|
|
788
|
+
tasks = self._task_list_store.get(store_key, [])
|
|
764
789
|
if not tasks:
|
|
765
790
|
return base_instruction + "\n## 当前状态\n暂无任务计划。请先分析用户需求,拆分为具体步骤,然后用 ```tasklist``` 输出计划。"
|
|
766
791
|
|
|
@@ -768,6 +793,10 @@ class ApiServer:
|
|
|
768
793
|
done = [f" - ✅ {t['text']}" for t in tasks if t.get("status") == "done"]
|
|
769
794
|
running = [f" - 🔄 {t['text']}" for t in tasks if t.get("status") == "running"]
|
|
770
795
|
|
|
796
|
+
# 如果所有任务都已完成,不再注入任务上下文,让 LLM 自然回复
|
|
797
|
+
if not pending and not running:
|
|
798
|
+
return ""
|
|
799
|
+
|
|
771
800
|
context = base_instruction + "\n## 当前任务进度\n"
|
|
772
801
|
if done:
|
|
773
802
|
context += "已完成:\n" + "\n".join(done) + "\n"
|
|
@@ -780,6 +809,7 @@ class ApiServer:
|
|
|
780
809
|
"1. 用纯文本简要分析当前进展\n"
|
|
781
810
|
"2. 用 ```tasklist``` 更新任务进度(标记已完成的步骤为 done,标记当前步骤为 running)\n"
|
|
782
811
|
"3. 用 ```action``` 执行下一个待执行步骤(每次只执行一个操作)\n"
|
|
812
|
+
"4. 如果所有步骤已完成,用 {\"type\": \"final_answer\", \"content\": \"总结\"} 结束\n"
|
|
783
813
|
)
|
|
784
814
|
return context
|
|
785
815
|
|
|
@@ -1823,6 +1853,15 @@ class ApiServer:
|
|
|
1823
1853
|
entries = self.core.memory.get_conversation(sid, limit=100)
|
|
1824
1854
|
return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at} for e in entries])
|
|
1825
1855
|
|
|
1856
|
+
async def handle_get_messages_query(self, request):
|
|
1857
|
+
"""GET /api/session/messages?sid=... - 通过 query 参数获取会话消息(支持 session_id 中包含 /)"""
|
|
1858
|
+
sid = request.query.get("sid", "")
|
|
1859
|
+
if not sid:
|
|
1860
|
+
return web.json_response([])
|
|
1861
|
+
if not self.core.memory: return web.json_response([])
|
|
1862
|
+
entries = self.core.memory.get_conversation(sid, limit=100)
|
|
1863
|
+
return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at} for e in entries])
|
|
1864
|
+
|
|
1826
1865
|
async def handle_delete_session(self, request):
|
|
1827
1866
|
"""DELETE /api/sessions/{sid} - 彻底删除会话(所有记忆)"""
|
|
1828
1867
|
sid = request.match_info["sid"]
|
|
@@ -1832,6 +1871,17 @@ class ApiServer:
|
|
|
1832
1871
|
logger.info(f"会话 {sid} 已删除,共清除 {count} 条记忆")
|
|
1833
1872
|
return web.json_response({"ok": True, "deleted": True})
|
|
1834
1873
|
|
|
1874
|
+
async def handle_delete_session_query(self, request):
|
|
1875
|
+
"""DELETE /api/session?sid=... - 通过 query 参数删除会话(支持 session_id 中包含 /)"""
|
|
1876
|
+
sid = request.query.get("sid", "")
|
|
1877
|
+
if not sid:
|
|
1878
|
+
return web.json_response({"ok": False, "error": "missing sid"}, status=400)
|
|
1879
|
+
logger.info(f"删除会话: {sid}")
|
|
1880
|
+
if self.core.memory:
|
|
1881
|
+
count = self.core.memory.delete_session(sid)
|
|
1882
|
+
logger.info(f"会话 {sid} 已删除,共清除 {count} 条记忆")
|
|
1883
|
+
return web.json_response({"ok": True, "deleted": True})
|
|
1884
|
+
|
|
1835
1885
|
async def handle_clear_session_messages(self, request):
|
|
1836
1886
|
"""DELETE /api/sessions/{sid}/messages - 仅清空会话对话历史(保留会话本身)"""
|
|
1837
1887
|
sid = request.match_info["sid"]
|
|
@@ -1841,6 +1891,17 @@ class ApiServer:
|
|
|
1841
1891
|
logger.info(f"会话 {sid} 消息已清空,共清除 {count} 条消息")
|
|
1842
1892
|
return web.json_response({"ok": True, "cleared": True})
|
|
1843
1893
|
|
|
1894
|
+
async def handle_clear_session_messages_query(self, request):
|
|
1895
|
+
"""DELETE /api/session/messages?sid=... - 通过 query 参数清空会话消息"""
|
|
1896
|
+
sid = request.query.get("sid", "")
|
|
1897
|
+
if not sid:
|
|
1898
|
+
return web.json_response({"ok": False, "error": "missing sid"}, status=400)
|
|
1899
|
+
logger.info(f"清空会话消息: {sid}")
|
|
1900
|
+
if self.core.memory:
|
|
1901
|
+
count = self.core.memory.clear_conversation(sid)
|
|
1902
|
+
logger.info(f"会话 {sid} 消息已清空,共清除 {count} 条消息")
|
|
1903
|
+
return web.json_response({"ok": True, "cleared": True})
|
|
1904
|
+
|
|
1844
1905
|
async def handle_rename_session(self, request):
|
|
1845
1906
|
"""PUT /api/sessions/{sid}/rename - 重命名会话"""
|
|
1846
1907
|
sid = request.match_info["sid"]
|
|
@@ -1857,6 +1918,24 @@ class ApiServer:
|
|
|
1857
1918
|
self.core.memory.rename_session(sid, new_name)
|
|
1858
1919
|
return web.json_response({"ok": True, "name": new_name})
|
|
1859
1920
|
|
|
1921
|
+
async def handle_rename_session_query(self, request):
|
|
1922
|
+
"""PUT /api/session/rename?sid=... - 通过 query 参数重命名会话"""
|
|
1923
|
+
sid = request.query.get("sid", "")
|
|
1924
|
+
if not sid:
|
|
1925
|
+
return web.json_response({"ok": False, "error": "missing sid"}, status=400)
|
|
1926
|
+
try:
|
|
1927
|
+
body = await request.json()
|
|
1928
|
+
except Exception:
|
|
1929
|
+
body = {}
|
|
1930
|
+
new_name = (body.get("name") or "").strip()
|
|
1931
|
+
if not new_name:
|
|
1932
|
+
return web.json_response({"ok": False, "error": "名称不能为空"}, status=400)
|
|
1933
|
+
if len(new_name) > 100:
|
|
1934
|
+
return web.json_response({"ok": False, "error": "名称不能超过100个字符"}, status=400)
|
|
1935
|
+
if self.core.memory:
|
|
1936
|
+
self.core.memory.rename_session(sid, new_name)
|
|
1937
|
+
return web.json_response({"ok": True, "name": new_name})
|
|
1938
|
+
|
|
1860
1939
|
# --- Memory ---
|
|
1861
1940
|
async def handle_memory_stats(self, request):
|
|
1862
1941
|
return web.json_response(self.core.memory.get_stats() if self.core.memory else {})
|
|
@@ -2625,7 +2704,7 @@ class ApiServer:
|
|
|
2625
2704
|
iteration = 0
|
|
2626
2705
|
# 追踪连续无 action 迭代次数,防止无限重新提示
|
|
2627
2706
|
_consecutive_no_action = 0
|
|
2628
|
-
_MAX_NO_ACTION_RETRIES =
|
|
2707
|
+
_MAX_NO_ACTION_RETRIES = 2 # 限制重试次数,避免重复循环规划
|
|
2629
2708
|
# ── 追踪所有流式推送的纯文本(用于刷新后恢复) ──
|
|
2630
2709
|
_all_streamed_text_parts = [] # 每轮迭代推送的纯文本片段
|
|
2631
2710
|
|
|
@@ -2635,7 +2714,7 @@ class ApiServer:
|
|
|
2635
2714
|
# ── 执行模式:每轮迭代前刷新任务进度上下文 ──
|
|
2636
2715
|
# 这样 LLM 在每次调用时都能看到最新的任务列表状态
|
|
2637
2716
|
if chat_mode == "exec":
|
|
2638
|
-
fresh_task_context = self._build_task_plan_context(agent_path, chat_mode, user_message)
|
|
2717
|
+
fresh_task_context = self._build_task_plan_context(agent_path, chat_mode, user_message, session_id=session_id)
|
|
2639
2718
|
if fresh_task_context:
|
|
2640
2719
|
# 替换掉之前的任务规划上下文(保留非任务规划部分)
|
|
2641
2720
|
base_prompt = context.metadata.get("agent_override_prompt", "")
|
|
@@ -2828,8 +2907,8 @@ class ApiServer:
|
|
|
2828
2907
|
if chat_mode == "exec":
|
|
2829
2908
|
task_list = self._extract_task_list_json(content)
|
|
2830
2909
|
if task_list is not None:
|
|
2831
|
-
#
|
|
2832
|
-
self._task_list_store[
|
|
2910
|
+
# 保存到内存(按 session 隔离,避免跨会话任务泄漏)
|
|
2911
|
+
self._task_list_store[session_id] = task_list
|
|
2833
2912
|
await _write_sse({"type": "task_list_update", "tasks": task_list})
|
|
2834
2913
|
|
|
2835
2914
|
# ── Check for tool calls (OpenAI function calling) ──
|
|
@@ -2938,6 +3017,29 @@ class ApiServer:
|
|
|
2938
3017
|
|
|
2939
3018
|
result_summary = agent._summarize_action_results(results)
|
|
2940
3019
|
|
|
3020
|
+
# ── 自动更新任务状态:将 running 任务标记为 done ──
|
|
3021
|
+
# 修复:执行成功后不依赖 LLM 下次输出才更新状态,避免重复执行同一任务
|
|
3022
|
+
if chat_mode == "exec":
|
|
3023
|
+
current_tasks = list(self._task_list_store.get(session_id, []))
|
|
3024
|
+
updated = False
|
|
3025
|
+
if results and results[0].get("success", False):
|
|
3026
|
+
# 找到第一个 running 的任务,标记为 done
|
|
3027
|
+
for t in current_tasks:
|
|
3028
|
+
if t.get("status") == "running":
|
|
3029
|
+
t["status"] = "done"
|
|
3030
|
+
updated = True
|
|
3031
|
+
break
|
|
3032
|
+
# 如果没有 running 的,找第一个 pending 的标记为 done(兼容 LLM 未设置 running 的情况)
|
|
3033
|
+
if not updated:
|
|
3034
|
+
for t in current_tasks:
|
|
3035
|
+
if t.get("status") == "pending":
|
|
3036
|
+
t["status"] = "done"
|
|
3037
|
+
updated = True
|
|
3038
|
+
break
|
|
3039
|
+
if updated:
|
|
3040
|
+
self._task_list_store[session_id] = current_tasks
|
|
3041
|
+
await _write_sse({"type": "task_list_update", "tasks": current_tasks})
|
|
3042
|
+
|
|
2941
3043
|
# Handle timeout diagnostics (same as _process_inner)
|
|
2942
3044
|
has_timeout = any(r.get("timed_out") for r in results)
|
|
2943
3045
|
timeout_detail = ""
|
|
@@ -3018,7 +3120,7 @@ class ApiServer:
|
|
|
3018
3120
|
# 导致循环在此处 break,任务未完成就停止。
|
|
3019
3121
|
# 修复:检查任务列表中是否还有未完成步骤,如果有则重新提示 LLM 继续。
|
|
3020
3122
|
if chat_mode == "exec":
|
|
3021
|
-
current_tasks = self._task_list_store.get(
|
|
3123
|
+
current_tasks = self._task_list_store.get(session_id, [])
|
|
3022
3124
|
pending_count = sum(
|
|
3023
3125
|
1 for t in current_tasks
|
|
3024
3126
|
if t.get("status") in ("pending", "running", "blocked")
|
|
@@ -3073,6 +3175,7 @@ class ApiServer:
|
|
|
3073
3175
|
|
|
3074
3176
|
# Save assistant response to memory
|
|
3075
3177
|
# ── 优先使用流式累积文本(包含所有迭代的纯文本),回退到 final_response ──
|
|
3178
|
+
# 注意:即使客户端中途断开(刷新页面),也要保存已有内容
|
|
3076
3179
|
saved_response = final_response
|
|
3077
3180
|
if not saved_response and _all_streamed_text_parts:
|
|
3078
3181
|
saved_response = "\n\n".join(p for p in _all_streamed_text_parts if p.strip())
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -1712,13 +1712,23 @@ input,textarea,select{font:inherit}
|
|
|
1712
1712
|
.agent-panel{
|
|
1713
1713
|
position:fixed;
|
|
1714
1714
|
right:0;top:0;
|
|
1715
|
-
width:85vw;max-width:340px;
|
|
1715
|
+
width:85vw!important;max-width:340px;min-width:260px!important;
|
|
1716
1716
|
height:100vh;
|
|
1717
1717
|
z-index:50;
|
|
1718
1718
|
transform:translateX(100%);
|
|
1719
1719
|
transition:transform .3s cubic-bezier(.4,0,.2,1);
|
|
1720
1720
|
box-shadow:none;
|
|
1721
1721
|
}
|
|
1722
|
+
/* Override desktop collapsed state on mobile */
|
|
1723
|
+
.agent-panel.collapsed{
|
|
1724
|
+
width:85vw!important;min-width:260px!important;
|
|
1725
|
+
}
|
|
1726
|
+
.agent-panel.collapsed .agent-panel-header h3,
|
|
1727
|
+
.agent-panel.collapsed .agent-list,
|
|
1728
|
+
.agent-panel.collapsed .agent-create-btn,
|
|
1729
|
+
.agent-panel.collapsed .agent-collapse-btn span,
|
|
1730
|
+
.agent-panel.collapsed .agent-panel-footer{display:flex!important}
|
|
1731
|
+
.agent-panel.collapsed .agent-collapse-btn span{display:inline!important}
|
|
1722
1732
|
.agent-panel.mobile-open{
|
|
1723
1733
|
transform:translateX(0);
|
|
1724
1734
|
box-shadow:-4px 0 24px rgba(0,0,0,.15);
|
|
@@ -1753,8 +1763,15 @@ input,textarea,select{font:inherit}
|
|
|
1753
1763
|
.messages-inner{
|
|
1754
1764
|
max-width:100%;
|
|
1755
1765
|
}
|
|
1756
|
-
.message-
|
|
1757
|
-
|
|
1766
|
+
.message-row{gap:6px}
|
|
1767
|
+
.message-avatar{
|
|
1768
|
+
width:28px;height:28px;font-size:12px;
|
|
1769
|
+
}
|
|
1770
|
+
.message-row.assistant .message-bubble{
|
|
1771
|
+
max-width:100%;
|
|
1772
|
+
}
|
|
1773
|
+
.message-row.user .message-bubble{
|
|
1774
|
+
max-width:80%;
|
|
1758
1775
|
}
|
|
1759
1776
|
.message-bubble pre{
|
|
1760
1777
|
font-size:12px;
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -162,6 +162,7 @@ const state = {
|
|
|
162
162
|
abortController: null,
|
|
163
163
|
execTimerInterval: null, // polling interval ID for execution progress
|
|
164
164
|
systemStatus: null,
|
|
165
|
+
_sessionLoadSeq: 0, // 防止 selectSession 并发竞态的序号
|
|
165
166
|
// ── Execution mode escalation ──
|
|
166
167
|
escalated: false, // 当前会话是否临时提权到 local
|
|
167
168
|
execLockInfo: null, // 全局锁信息 {locked, locked_by, locked_at}
|
|
@@ -1103,6 +1104,8 @@ async function selectAgent(agentPath) {
|
|
|
1103
1104
|
StatePersistence.save('activeAgent', agentPath);
|
|
1104
1105
|
state.activeSessionId = null;
|
|
1105
1106
|
state.messages = [];
|
|
1107
|
+
// 递增序号,使任何进行中的 selectSession 请求失效
|
|
1108
|
+
state._sessionLoadSeq++;
|
|
1106
1109
|
var parts = agentPath.split('/');
|
|
1107
1110
|
var cumPath = '';
|
|
1108
1111
|
for (var i = 0; i < parts.length; i++) {
|
|
@@ -1528,7 +1531,7 @@ async function clearSessionFromMenu(id) {
|
|
|
1528
1531
|
if (!id || id === '__new__') return;
|
|
1529
1532
|
if (!confirm('确定清空此对话的消息?会话本身不会被删除。')) return;
|
|
1530
1533
|
try {
|
|
1531
|
-
const resp = await fetch(`/api/
|
|
1534
|
+
const resp = await fetch(`/api/session/messages?sid=${encodeURIComponent(id)}`, {
|
|
1532
1535
|
method: 'DELETE',
|
|
1533
1536
|
headers: { 'Content-Type': 'application/json' }
|
|
1534
1537
|
});
|
|
@@ -1631,6 +1634,8 @@ async function selectSession(id) {
|
|
|
1631
1634
|
document.getElementById('stopBtn').style.display = 'none';
|
|
1632
1635
|
|
|
1633
1636
|
state.activeSessionId = id;
|
|
1637
|
+
// 递增序号,用于检测并发竞态
|
|
1638
|
+
const mySeq = ++state._sessionLoadSeq;
|
|
1634
1639
|
const session = state.sessions.find(s => s.id === id);
|
|
1635
1640
|
document.getElementById('headerTitle').textContent = session ? session.name : formatSessionName(id);
|
|
1636
1641
|
document.getElementById('welcomeCard').style.display = 'none';
|
|
@@ -1638,13 +1643,16 @@ async function selectSession(id) {
|
|
|
1638
1643
|
|
|
1639
1644
|
// Load messages
|
|
1640
1645
|
try {
|
|
1641
|
-
const data = await api(`/api/
|
|
1646
|
+
const data = await api(`/api/session/messages?sid=${encodeURIComponent(id)}`);
|
|
1647
|
+
// 竞态保护:如果在此请求期间又切换了其他会话,丢弃过期结果
|
|
1648
|
+
if (mySeq !== state._sessionLoadSeq) return;
|
|
1642
1649
|
state.messages = (data || []).map(m => ({
|
|
1643
1650
|
role: m.role,
|
|
1644
1651
|
content: m.content,
|
|
1645
1652
|
time: m.time || '',
|
|
1646
1653
|
}));
|
|
1647
1654
|
} catch (e) {
|
|
1655
|
+
if (mySeq !== state._sessionLoadSeq) return;
|
|
1648
1656
|
state.messages = [];
|
|
1649
1657
|
toast('加载消息失败', 'error');
|
|
1650
1658
|
}
|
|
@@ -1656,7 +1664,7 @@ async function selectSession(id) {
|
|
|
1656
1664
|
async function deleteSession(id) {
|
|
1657
1665
|
if (!id || id === '__new__') return;
|
|
1658
1666
|
try {
|
|
1659
|
-
const resp = await fetch(`/api/
|
|
1667
|
+
const resp = await fetch(`/api/session?sid=${encodeURIComponent(id)}`, {
|
|
1660
1668
|
method: 'DELETE',
|
|
1661
1669
|
headers: { 'Content-Type': 'application/json' }
|
|
1662
1670
|
});
|
|
@@ -1721,7 +1729,7 @@ async function finishRenameSession(id, newName) {
|
|
|
1721
1729
|
return;
|
|
1722
1730
|
}
|
|
1723
1731
|
try {
|
|
1724
|
-
await api(`/api/
|
|
1732
|
+
await api(`/api/session/rename?sid=${encodeURIComponent(id)}`, {
|
|
1725
1733
|
method: 'PUT',
|
|
1726
1734
|
body: JSON.stringify({ name: newName }),
|
|
1727
1735
|
});
|
|
@@ -1753,7 +1761,7 @@ async function clearCurrentChat() {
|
|
|
1753
1761
|
}
|
|
1754
1762
|
if (!confirm('确定清空当前对话消息?会话本身不会被删除。')) return;
|
|
1755
1763
|
try {
|
|
1756
|
-
const resp = await fetch(`/api/
|
|
1764
|
+
const resp = await fetch(`/api/session/messages?sid=${encodeURIComponent(state.activeSessionId)}`, {
|
|
1757
1765
|
method: 'DELETE',
|
|
1758
1766
|
headers: { 'Content-Type': 'application/json' }
|
|
1759
1767
|
});
|