tagword-codeagent 1.1.6__py3-none-any.whl
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.
- codeagent/__init__.py +10 -0
- codeagent/__main__.py +13 -0
- codeagent/cli/__init__.py +13 -0
- codeagent/cli/entrypoint.py +161 -0
- codeagent/cli/main.py +979 -0
- codeagent/core/__init__.py +2 -0
- codeagent/core/attachments.py +342 -0
- codeagent/core/audio_models.py +50 -0
- codeagent/core/bootstrap.py +178 -0
- codeagent/core/default_presets.py +159 -0
- codeagent/core/env.py +106 -0
- codeagent/core/folder_picker.py +151 -0
- codeagent/core/image_understanding.py +90 -0
- codeagent/core/models.py +6 -0
- codeagent/core/paths.py +238 -0
- codeagent/core/pricing.py +107 -0
- codeagent/core/process_ports.py +258 -0
- codeagent/core/seed_bridge.py +21 -0
- codeagent/core/settings.py +98 -0
- codeagent/core/speech_synth.py +330 -0
- codeagent/core/team_config.py +247 -0
- codeagent/core/team_manager.py +245 -0
- codeagent/core/token_counter.py +41 -0
- codeagent/core/tts_voices.py +103 -0
- codeagent/core/usage_billing.py +72 -0
- codeagent/core/vision_models.py +93 -0
- codeagent/memory/diary.py +144 -0
- codeagent/persona_defaults/README.md +12 -0
- codeagent/persona_defaults/agent.md +352 -0
- codeagent/persona_defaults/identity.md +29 -0
- codeagent/persona_defaults/memory.md +4 -0
- codeagent/persona_defaults/skills.md +325 -0
- codeagent/persona_defaults/soul.md +38 -0
- codeagent/persona_defaults/tools.md +41 -0
- codeagent/persona_defaults/user.md +18 -0
- codeagent/runtime/__init__.py +11 -0
- codeagent/runtime/agent_context.py +6 -0
- codeagent/runtime/agent_runtime.py +21 -0
- codeagent/runtime/compact_prompt.py +9 -0
- codeagent/runtime/compact_state.py +40 -0
- codeagent/runtime/llm_worker.py +132 -0
- codeagent/runtime/orchestrator.py +161 -0
- codeagent/runtime/prompt_enrichment.py +324 -0
- codeagent/runtime/session_manager.py +30 -0
- codeagent/runtime/task_split.py +38 -0
- codeagent/runtime/turn_loop.py +13 -0
- codeagent/runtime/worker.py +48 -0
- codeagent/server/__init__.py +505 -0
- codeagent/server/app_factory.py +1292 -0
- codeagent/server/attachment_api.py +261 -0
- codeagent/server/hub.py +175 -0
- codeagent/server/icon.png +0 -0
- codeagent/server/self_healing.py +143 -0
- codeagent/server/team_engine.py +243 -0
- codeagent/server/tts_api.py +51 -0
- codeagent/server/webui_api_app.py +3180 -0
- codeagent/skills/__init__.py +2 -0
- codeagent/skills/select.py +144 -0
- codeagent/tools/__init__.py +6 -0
- codeagent/tools/agent_tools.py +10 -0
- codeagent/web/__init__.py +1 -0
- codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
- codeagent/web/auth_impl.py +326 -0
- codeagent/web/static/00-marked.min.js +6 -0
- codeagent/web/static/00-mermaid.min.js +2314 -0
- codeagent/web/static/00-purify.min.js +3 -0
- codeagent/web/static/00-storage.js +83 -0
- codeagent/web/static/00-utils.js +51 -0
- codeagent/web/static/01-theme.css +96 -0
- codeagent/web/static/01a-scroll.js +86 -0
- codeagent/web/static/01b-think.js +62 -0
- codeagent/web/static/01c-session-identity.js +81 -0
- codeagent/web/static/01c-session-thinking.js +61 -0
- codeagent/web/static/01c-session-tree-menus.js +349 -0
- codeagent/web/static/01c-session-tree.js +531 -0
- codeagent/web/static/01c-session-utilities.js +83 -0
- codeagent/web/static/01c-vision-model.js +132 -0
- codeagent/web/static/01d-image-gen-model.js +84 -0
- codeagent/web/static/01e-audio-model.js +97 -0
- codeagent/web/static/01f-compose-ui.js +88 -0
- codeagent/web/static/01g-tts-settings.js +128 -0
- codeagent/web/static/01h-music-model.js +85 -0
- codeagent/web/static/01i-model-stack.js +291 -0
- codeagent/web/static/01j-video-gen-model.js +85 -0
- codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
- codeagent/web/static/01k-model-stack-state.js +209 -0
- codeagent/web/static/01q-inflight.js +195 -0
- codeagent/web/static/02-reset.css +22 -0
- codeagent/web/static/02-session-ui.js +201 -0
- codeagent/web/static/03-layout-p1.css +2 -0
- codeagent/web/static/03-layout-p2.css +2 -0
- codeagent/web/static/03a-links-stream.js +88 -0
- codeagent/web/static/03b-stream-text.js +119 -0
- codeagent/web/static/03c-render-bubble.js +520 -0
- codeagent/web/static/03d-agent-bubble.js +126 -0
- codeagent/web/static/03e-tool-trace.js +182 -0
- codeagent/web/static/03f-timeline.js +54 -0
- codeagent/web/static/03g-bubble-tts.js +292 -0
- codeagent/web/static/04-components-p1.css +1 -0
- codeagent/web/static/04-components-p2b.css +1 -0
- codeagent/web/static/04-components-p3.css +2 -0
- codeagent/web/static/04-components-p4.css +1 -0
- codeagent/web/static/04-components-p5.css +1 -0
- codeagent/web/static/04-components-p6.css +58 -0
- codeagent/web/static/04-components-p7.css +4 -0
- codeagent/web/static/04-components-p8.css +1 -0
- codeagent/web/static/04-components-p9.css +1 -0
- codeagent/web/static/04-ws-connect.js +351 -0
- codeagent/web/static/04-ws-helpers.js +116 -0
- codeagent/web/static/05-chat-p1.css +1058 -0
- codeagent/web/static/05-chat-p2.css +1 -0
- codeagent/web/static/05-chat-p3.css +295 -0
- codeagent/web/static/05a-session-history.js +176 -0
- codeagent/web/static/05b-session-list.js +114 -0
- codeagent/web/static/05c-dialog.js +123 -0
- codeagent/web/static/05d-project-create.js +172 -0
- codeagent/web/static/05e-project-remote.js +143 -0
- codeagent/web/static/06-chat.js +552 -0
- codeagent/web/static/06-utilities.css +181 -0
- codeagent/web/static/06b-attachments.js +304 -0
- codeagent/web/static/06c-camera.js +488 -0
- codeagent/web/static/07-activity-bar.css +127 -0
- codeagent/web/static/09-git-p1.css +1 -0
- codeagent/web/static/09-git-p2.css +1 -0
- codeagent/web/static/09a-init-bind.js +110 -0
- codeagent/web/static/09b-panel-toggle.js +80 -0
- codeagent/web/static/09c-api-refresh.js +100 -0
- codeagent/web/static/09d-status-log.js +80 -0
- codeagent/web/static/09e-commit.js +80 -0
- codeagent/web/static/09f-remote.js +93 -0
- codeagent/web/static/11a-cron-parse.js +117 -0
- codeagent/web/static/11b-cron-panel.js +100 -0
- codeagent/web/static/11c-cron-card.js +133 -0
- codeagent/web/static/11d-cron-form.js +105 -0
- codeagent/web/static/11e-env-config.js +168 -0
- codeagent/web/static/11f-env-llm-a-state.js +136 -0
- codeagent/web/static/11f-env-llm-b-form.js +409 -0
- codeagent/web/static/11f-env-llm-c-board.js +322 -0
- codeagent/web/static/11g-env-paths-git.js +973 -0
- codeagent/web/static/11h-env-mcp-a-state.js +161 -0
- codeagent/web/static/11h-env-mcp-b-form.js +229 -0
- codeagent/web/static/11h-env-mcp-c-board.js +565 -0
- codeagent/web/static/12-plugins.js +127 -0
- codeagent/web/static/13a-skills.js +156 -0
- codeagent/web/static/13a-todo-utils.js +140 -0
- codeagent/web/static/13b-todo-fetch.js +106 -0
- codeagent/web/static/13b-tools.js +127 -0
- codeagent/web/static/13c-todo-crud.js +92 -0
- codeagent/web/static/14-activity-bar.js +177 -0
- codeagent/web/static/15-plans.css +194 -0
- codeagent/web/static/15-plans.js +219 -0
- codeagent/web/static/16-mobile.css +612 -0
- codeagent/web/static/16-mobile.js +178 -0
- codeagent/web/static/17-preset-ux.css +287 -0
- codeagent/web/static/18-agent-mgr.css +616 -0
- codeagent/web/static/18-agent-mgr.js +204 -0
- codeagent/web/static/24-health-dashboard.js +68 -0
- codeagent/web/static/99-brand-polish.css +152 -0
- codeagent/web/static/body.html +824 -0
- codeagent/web/static/setup/setup_form.html +171 -0
- codeagent/web/static/setup/setup_head.html +100 -0
- codeagent/web/static/setup/setup_tail.html +264 -0
- codeagent/web/static/setup/setup_test.html +38 -0
- codeagent/web/web_login.html +265 -0
- codeagent/web/webui.html +35 -0
- tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
- tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
- tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
- tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
- tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
- tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/* ================================================================
|
|
2
|
+
* 01c-session-tree.js
|
|
3
|
+
* 项目树(侧栏 tree)核心渲染:
|
|
4
|
+
* - 状态: treeExpanded / treeProjectActiveId / treeProjectsCache / treeSessionsGenByPid
|
|
5
|
+
* - 工具: treePid / treeIsExpanded / bumpSessionsGen / sessionsFetchStale / findProjDiv
|
|
6
|
+
* - 高亮: syncTreeProjectActiveHighlight / syncTreeSessionActiveHighlight
|
|
7
|
+
* - 列表: fillSessionsList / loadSessionsIntoContainer / refreshSessionsUnderProject
|
|
8
|
+
* / appendProjectTreeNode / refreshProjects / fetchSessionsForProject
|
|
9
|
+
* - 节点: createProjectNode / applyProjectExpandStateToDom / toggleTreeProjectExpanded
|
|
10
|
+
* / handleProjectRowClick / getTreeProjectPath
|
|
11
|
+
*
|
|
12
|
+
* 右键菜单/对话框/重命名/删除在 01c-session-tree-menus.js。
|
|
13
|
+
* 按字母序:'01c-session-tree-menus.js' 中字符 '-' (0x2D) < '01c-session-tree.js'
|
|
14
|
+
* 字符 '.' (0x2E),所以 menus 文件实际在 tree 文件**之前**加载;
|
|
15
|
+
* function hoisting 保证两边可互相引用。
|
|
16
|
+
*
|
|
17
|
+
* 依赖 01c-session-identity.js 的:
|
|
18
|
+
* agentId / projectId / sessionId / saveProjectIdForAgent / loadSessionIdForAgent
|
|
19
|
+
* 依赖全局 DOM:log, projectTree, btnNewSession
|
|
20
|
+
* 依赖全局函数:reconnectWsForSession, refreshSessionList, loadSessionHistoryIntoLog
|
|
21
|
+
* ================================================================ */
|
|
22
|
+
|
|
23
|
+
var treeExpanded = {};
|
|
24
|
+
var treeProjectActiveId = '';
|
|
25
|
+
var treeProjectRenderGen = 0;
|
|
26
|
+
/** 最近一次拉取的项目列表;展开/折叠等仅本地重绘时复用,避免重复 GET /api/ui/projects */
|
|
27
|
+
var treeProjectsCache = null;
|
|
28
|
+
/** 每个项目下列表请求的代数,丢弃过期回调(不等同于整棵树重建) */
|
|
29
|
+
var treeSessionsGenByPid = Object.create(null);
|
|
30
|
+
|
|
31
|
+
function treePid(id) {
|
|
32
|
+
return String(id == null ? '' : id);
|
|
33
|
+
}
|
|
34
|
+
function treeIsExpanded(id) {
|
|
35
|
+
return treeExpanded[treePid(id)] === true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function bumpSessionsGen(pid) {
|
|
39
|
+
var k = treePid(pid);
|
|
40
|
+
treeSessionsGenByPid[k] = (treeSessionsGenByPid[k] || 0) + 1;
|
|
41
|
+
return treeSessionsGenByPid[k];
|
|
42
|
+
}
|
|
43
|
+
function sessionsFetchStale(pid, gen) {
|
|
44
|
+
return treeSessionsGenByPid[treePid(pid)] !== gen;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findProjDiv(pid) {
|
|
48
|
+
var k = treePid(pid);
|
|
49
|
+
var sel = '.tree-project[data-project-id="' + k.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]';
|
|
50
|
+
try {
|
|
51
|
+
if (typeof CSS !== 'undefined' && CSS.escape) {
|
|
52
|
+
sel = '.tree-project[data-project-id="' + CSS.escape(k) + '"]';
|
|
53
|
+
}
|
|
54
|
+
} catch (_) {}
|
|
55
|
+
return document.querySelector(sel);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function syncTreeProjectActiveHighlight() {
|
|
59
|
+
document.querySelectorAll('.tree-project').forEach(function(projDiv) {
|
|
60
|
+
var pid = treePid(projDiv.dataset.projectId);
|
|
61
|
+
var row = projDiv.querySelector('.tree-project__row');
|
|
62
|
+
if (!row) return;
|
|
63
|
+
var isActive = pid === treePid(projectId) || pid === treePid(treeProjectActiveId);
|
|
64
|
+
row.classList.toggle('tree-project__row--active', isActive);
|
|
65
|
+
row.querySelectorAll('.tree-project__active-bar').forEach(function(b) { b.remove(); });
|
|
66
|
+
if (isActive) {
|
|
67
|
+
var bar = document.createElement('div');
|
|
68
|
+
bar.className = 'tree-project__active-bar';
|
|
69
|
+
row.appendChild(bar);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function syncTreeSessionActiveHighlight() {
|
|
75
|
+
document.querySelectorAll('.tree-session__row').forEach(function(sRow) {
|
|
76
|
+
var sid = sRow.getAttribute('data-session-id');
|
|
77
|
+
if (!sid) return;
|
|
78
|
+
sRow.classList.toggle('tree-session__row--active', sid === sessionId);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function fillSessionsList(childrenEl, pid, sessions) {
|
|
83
|
+
var k = treePid(pid);
|
|
84
|
+
childrenEl.innerHTML = '';
|
|
85
|
+
if (!sessions || sessions.length === 0) {
|
|
86
|
+
var empty = document.createElement('div');
|
|
87
|
+
empty.className = 'tree-session__empty';
|
|
88
|
+
empty.textContent = '暂无会话';
|
|
89
|
+
childrenEl.appendChild(empty);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
sessions.forEach(function(s) {
|
|
93
|
+
var sDiv = document.createElement('div');
|
|
94
|
+
sDiv.className = 'tree-session';
|
|
95
|
+
sDiv.setAttribute('data-session-id', s.session_id);
|
|
96
|
+
// 检查运行态
|
|
97
|
+
if (typeof chatInflightBySid !== 'undefined' && (chatInflightBySid[s.session_id] || 0) > 0) {
|
|
98
|
+
sDiv.classList.add('is-running');
|
|
99
|
+
}
|
|
100
|
+
var sRow = document.createElement('div');
|
|
101
|
+
sRow.className = 'tree-session__row' + (s.session_id === sessionId ? ' tree-session__row--active' : '');
|
|
102
|
+
if (s.unread) sRow.classList.add('tree-session__row--unread');
|
|
103
|
+
sRow.setAttribute('data-session-id', s.session_id);
|
|
104
|
+
|
|
105
|
+
var ind = document.createElement('span');
|
|
106
|
+
ind.className = 'tree-session__indicator';
|
|
107
|
+
sRow.appendChild(ind);
|
|
108
|
+
|
|
109
|
+
var sTitle = document.createElement('span');
|
|
110
|
+
sTitle.className = 'tree-session__title';
|
|
111
|
+
sTitle.textContent = s.display_title || s.session_id || '会话';
|
|
112
|
+
sRow.appendChild(sTitle);
|
|
113
|
+
|
|
114
|
+
var sMenu = document.createElement('button');
|
|
115
|
+
sMenu.type = 'button'; sMenu.className = 'tree-session__menu';
|
|
116
|
+
sMenu.textContent = '⋮';
|
|
117
|
+
sMenu.addEventListener('click', function(e) {
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
showSessionContextMenu(e, s.session_id, k);
|
|
120
|
+
});
|
|
121
|
+
sRow.appendChild(sMenu);
|
|
122
|
+
|
|
123
|
+
sRow.addEventListener('click', function() {
|
|
124
|
+
if (s.session_id === sessionId) return;
|
|
125
|
+
if (typeof saveMsgDraft === 'function') saveMsgDraft();
|
|
126
|
+
if (k !== treePid(projectId)) {
|
|
127
|
+
projectId = k; treeProjectActiveId = k; window.dispatchEvent(new CustomEvent('project-changed', {detail: {projectId: k}}));
|
|
128
|
+
saveProjectIdForAgent(agentId, projectId);
|
|
129
|
+
}
|
|
130
|
+
sessionId = s.session_id;
|
|
131
|
+
saveSessionIdForAgent(agentId, projectId, sessionId);
|
|
132
|
+
window.dispatchEvent(new CustomEvent('session-changed', {detail: {sessionId: sessionId, projectId: projectId}}));
|
|
133
|
+
updateComposerButtons();
|
|
134
|
+
if (typeof restoreMsgDraft === 'function') restoreMsgDraft(sessionId);
|
|
135
|
+
log.innerHTML = '';
|
|
136
|
+
try { reconnectWsForSession(); } catch (_) {}
|
|
137
|
+
loadSessionHistoryIntoLog(true);
|
|
138
|
+
syncTreeProjectActiveHighlight();
|
|
139
|
+
syncTreeSessionActiveHighlight();
|
|
140
|
+
// 切换会话后同步 completed 状态(清除当前会话的绿色 + 全量重算)
|
|
141
|
+
if (typeof clearSessionCompleted === 'function') clearSessionCompleted(s.session_id);
|
|
142
|
+
if (typeof applyAllSessionCompletedStates === 'function') applyAllSessionCompletedStates();
|
|
143
|
+
});
|
|
144
|
+
sDiv.appendChild(sRow);
|
|
145
|
+
childrenEl.appendChild(sDiv);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function loadSessionsIntoContainer(childrenEl, pid) {
|
|
151
|
+
var k = treePid(pid);
|
|
152
|
+
var sg = bumpSessionsGen(k);
|
|
153
|
+
fetchSessionsForProject(k).then(function(sessions) {
|
|
154
|
+
if (sessionsFetchStale(k, sg)) return;
|
|
155
|
+
if (!treeIsExpanded(k)) return;
|
|
156
|
+
if (!childrenEl.isConnected) return;
|
|
157
|
+
// 当前会话属于此项目但尚未落盘(新创建)→ 加占位条目
|
|
158
|
+
if (treePid(pid) === treePid(projectId) && sessionId &&
|
|
159
|
+
!sessions.some(function(s) { return s.session_id === sessionId; })) {
|
|
160
|
+
sessions = [{ session_id: sessionId, message_count: 0, display_title: '新对话', channel: 'Web 聊天', preview: '' }].concat(sessions);
|
|
161
|
+
}
|
|
162
|
+
fillSessionsList(childrenEl, k, sessions);
|
|
163
|
+
syncTreeSessionActiveHighlight();
|
|
164
|
+
// 异步加载完成后重新应用搜索过滤
|
|
165
|
+
var inp = document.getElementById('treeSearchInput');
|
|
166
|
+
if (inp && inp.value.trim()) _treeSearchFilter(inp.value);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function applyProjectExpandStateToDom(pid) {
|
|
171
|
+
var k = treePid(pid);
|
|
172
|
+
var projDiv = findProjDiv(k);
|
|
173
|
+
if (!projDiv) return;
|
|
174
|
+
var expanded = treeIsExpanded(k);
|
|
175
|
+
var toggle = projDiv.querySelector('.tree-project__toggle');
|
|
176
|
+
if (toggle) {
|
|
177
|
+
toggle.classList.toggle('tree-project__toggle--expanded', expanded);
|
|
178
|
+
toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
179
|
+
}
|
|
180
|
+
var existing = projDiv.querySelector(':scope > .tree-sessions');
|
|
181
|
+
var inlineNew = projDiv.querySelector(':scope > .tree-session__new');
|
|
182
|
+
|
|
183
|
+
if (expanded) {
|
|
184
|
+
if (inlineNew) inlineNew.remove();
|
|
185
|
+
if (!existing) {
|
|
186
|
+
var children = document.createElement('div');
|
|
187
|
+
children.className = 'tree-sessions';
|
|
188
|
+
children.dataset.projectId = k;
|
|
189
|
+
projDiv.appendChild(children);
|
|
190
|
+
loadSessionsIntoContainer(children, k);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
if (existing) existing.remove();
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function toggleTreeProjectExpanded(pid) {
|
|
199
|
+
var k = treePid(pid);
|
|
200
|
+
treeExpanded[k] = !treeIsExpanded(k);
|
|
201
|
+
applyProjectExpandStateToDom(k);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function handleProjectRowClick(pid) {
|
|
205
|
+
var k = treePid(pid);
|
|
206
|
+
if (k === treePid(projectId)) {
|
|
207
|
+
toggleTreeProjectExpanded(k);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
treeProjectActiveId = k;
|
|
211
|
+
projectId = k;
|
|
212
|
+
if (typeof saveMsgDraft === 'function') saveMsgDraft();
|
|
213
|
+
window.dispatchEvent(new CustomEvent('project-changed', {detail: {projectId: k}}));
|
|
214
|
+
treeExpanded[k] = true;
|
|
215
|
+
saveProjectIdForAgent(agentId, projectId);
|
|
216
|
+
|
|
217
|
+
function finishProjectSwitch(targetSid) {
|
|
218
|
+
sessionId = targetSid;
|
|
219
|
+
saveSessionIdForAgent(agentId, projectId, sessionId);
|
|
220
|
+
if (typeof restoreMsgDraft === 'function') restoreMsgDraft(sessionId);
|
|
221
|
+
try {
|
|
222
|
+
window.dispatchEvent(new CustomEvent('session-changed', {detail: {sessionId: sessionId, projectId: k}}));
|
|
223
|
+
} catch (_) {}
|
|
224
|
+
updateComposerButtons();
|
|
225
|
+
log.innerHTML = '';
|
|
226
|
+
try { reconnectWsForSession(); } catch (_) {}
|
|
227
|
+
applyProjectExpandStateToDom(k);
|
|
228
|
+
syncTreeProjectActiveHighlight();
|
|
229
|
+
syncTreeSessionActiveHighlight();
|
|
230
|
+
if (typeof refreshSessionList === 'function') refreshSessionList().catch(function() {});
|
|
231
|
+
if (typeof loadSessionHistoryIntoLog === 'function') loadSessionHistoryIntoLog(true).catch(function() {});
|
|
232
|
+
var inp = document.getElementById('treeSearchInput');
|
|
233
|
+
if (inp) inp.value = '';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fetchSessionsForProject(k).then(function(sessions) {
|
|
237
|
+
var list = sessions || [];
|
|
238
|
+
if (list.length > 0) {
|
|
239
|
+
finishProjectSwitch(list[0].session_id);
|
|
240
|
+
} else {
|
|
241
|
+
finishProjectSwitch(loadSessionIdForAgent(agentId, projectId));
|
|
242
|
+
}
|
|
243
|
+
}).catch(function() {
|
|
244
|
+
finishProjectSwitch(loadSessionIdForAgent(agentId, projectId));
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function createProjectNode(pr) {
|
|
249
|
+
var pid = treePid(pr.id);
|
|
250
|
+
var displayName = pr.name || pr.id;
|
|
251
|
+
var expanded = treeIsExpanded(pid);
|
|
252
|
+
var isActive = pid === treePid(treeProjectActiveId) || pid === treePid(projectId);
|
|
253
|
+
|
|
254
|
+
var projDiv = document.createElement('div');
|
|
255
|
+
projDiv.className = 'tree-project';
|
|
256
|
+
projDiv.dataset.projectId = pid;
|
|
257
|
+
|
|
258
|
+
var row = document.createElement('div');
|
|
259
|
+
row.className = 'tree-project__row' + (isActive ? ' tree-project__row--active' : '');
|
|
260
|
+
|
|
261
|
+
var toggle = document.createElement('button');
|
|
262
|
+
toggle.type = 'button';
|
|
263
|
+
toggle.className = 'tree-project__toggle' + (expanded ? ' tree-project__toggle--expanded' : '');
|
|
264
|
+
toggle.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
265
|
+
toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
266
|
+
toggle.addEventListener('click', function(e) {
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
toggleTreeProjectExpanded(pid);
|
|
269
|
+
});
|
|
270
|
+
row.appendChild(toggle);
|
|
271
|
+
|
|
272
|
+
var icon = document.createElement('span');
|
|
273
|
+
icon.className = 'tree-project__icon';
|
|
274
|
+
if (pid === '__unassigned__') {
|
|
275
|
+
icon.innerHTML = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M2 3h12v10H2V3zm1 1v5h3l1 2h2l1-2h3V4H3z" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linejoin="round"/></svg>';
|
|
276
|
+
} else {
|
|
277
|
+
icon.innerHTML = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M3 6.5h3L6.5 5H12a1 1 0 011 1v7a1 1 0 01-1 1H4a1 1 0 01-1-1v-6.5z" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linejoin="round"/><path d="M3 6.5V5.5a1 1 0 011-1h2.5L8 5" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linejoin="round"/></svg>';
|
|
278
|
+
}
|
|
279
|
+
row.appendChild(icon);
|
|
280
|
+
|
|
281
|
+
var name = document.createElement('span');
|
|
282
|
+
name.className = 'tree-project__name';
|
|
283
|
+
name.textContent = displayName;
|
|
284
|
+
row.appendChild(name);
|
|
285
|
+
|
|
286
|
+
var addBtn = document.createElement('button');
|
|
287
|
+
addBtn.type = 'button'; addBtn.className = 'tree-project__add-session';
|
|
288
|
+
addBtn.textContent = '+';
|
|
289
|
+
addBtn.setAttribute('aria-label', '新建会话');
|
|
290
|
+
addBtn.addEventListener('click', function(e) {
|
|
291
|
+
e.stopPropagation();
|
|
292
|
+
// 切换到该项目,然后创建新会话
|
|
293
|
+
if (pid !== treePid(projectId)) {
|
|
294
|
+
projectId = pid; treeProjectActiveId = pid;
|
|
295
|
+
window.dispatchEvent(new CustomEvent('project-changed', {detail: {projectId: pid}}));
|
|
296
|
+
saveProjectIdForAgent(agentId, projectId);
|
|
297
|
+
}
|
|
298
|
+
document.getElementById('btnNewSession').click();
|
|
299
|
+
});
|
|
300
|
+
row.appendChild(addBtn);
|
|
301
|
+
|
|
302
|
+
var menu = document.createElement('button');
|
|
303
|
+
menu.type = 'button'; menu.className = 'tree-project__menu';
|
|
304
|
+
menu.textContent = '⋮';
|
|
305
|
+
menu.addEventListener('click', function(e) {
|
|
306
|
+
e.stopPropagation();
|
|
307
|
+
showProjectContextMenu(e, pid, name.textContent || displayName);
|
|
308
|
+
});
|
|
309
|
+
row.appendChild(menu);
|
|
310
|
+
|
|
311
|
+
if (isActive) {
|
|
312
|
+
var bar = document.createElement('div');
|
|
313
|
+
bar.className = 'tree-project__active-bar';
|
|
314
|
+
row.appendChild(bar);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
row.addEventListener('click', function() {
|
|
318
|
+
handleProjectRowClick(pid);
|
|
319
|
+
});
|
|
320
|
+
projDiv.appendChild(row);
|
|
321
|
+
|
|
322
|
+
if (expanded) {
|
|
323
|
+
var children = document.createElement('div');
|
|
324
|
+
children.className = 'tree-sessions';
|
|
325
|
+
children.dataset.projectId = pid;
|
|
326
|
+
projDiv.appendChild(children);
|
|
327
|
+
loadSessionsIntoContainer(children, pid);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return projDiv;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function refreshSessionsUnderProject(pid) {
|
|
334
|
+
var k = treePid(pid || projectId);
|
|
335
|
+
var projDiv = findProjDiv(k);
|
|
336
|
+
if (!projDiv) return;
|
|
337
|
+
var box = projDiv.querySelector(':scope > .tree-sessions');
|
|
338
|
+
if (!box) return;
|
|
339
|
+
box.innerHTML = '';
|
|
340
|
+
loadSessionsIntoContainer(box, k);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function appendProjectTreeNode(pr) {
|
|
344
|
+
var treeEl = document.getElementById('projectTree');
|
|
345
|
+
if (!treeEl) return;
|
|
346
|
+
var empty = treeEl.querySelector('.project-tree__empty');
|
|
347
|
+
if (empty) empty.remove();
|
|
348
|
+
treeEl.appendChild(createProjectNode(pr));
|
|
349
|
+
syncTreeProjectActiveHighlight();
|
|
350
|
+
syncTreeSessionActiveHighlight();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* @param {boolean} [forceFetch] 为 true 时始终向服务端拉项目列表;默认 false(有缓存且 agent 未变则只用缓存)。
|
|
355
|
+
* 仅在此处整棵树替换 DOM;展开/折叠走 applyProjectExpandStateToDom,不重绘兄弟节点。
|
|
356
|
+
*/
|
|
357
|
+
async function refreshProjects(forceFetch) {
|
|
358
|
+
if (forceFetch === undefined) forceFetch = false;
|
|
359
|
+
var treeEl = document.getElementById('projectTree');
|
|
360
|
+
if (!treeEl) return;
|
|
361
|
+
var renderGen = ++treeProjectRenderGen;
|
|
362
|
+
// 保留滚动位置:重建 DOM 期间子树会被销毁,渲染后恢复
|
|
363
|
+
var savedScrollTop = treeEl.scrollTop;
|
|
364
|
+
try {
|
|
365
|
+
var projects;
|
|
366
|
+
var cacheOk = !forceFetch && treeProjectsCache && treeProjectsCache.aid === agentId &&
|
|
367
|
+
Array.isArray(treeProjectsCache.projects);
|
|
368
|
+
if (cacheOk) {
|
|
369
|
+
projects = treeProjectsCache.projects.slice();
|
|
370
|
+
if (renderGen !== treeProjectRenderGen) return;
|
|
371
|
+
} else {
|
|
372
|
+
var r = await fetch('/api/ui/projects?agent_id=' + encodeURIComponent(agentId));
|
|
373
|
+
if (renderGen !== treeProjectRenderGen) return;
|
|
374
|
+
if (!r.ok) return;
|
|
375
|
+
var j = await r.json();
|
|
376
|
+
if (renderGen !== treeProjectRenderGen) return;
|
|
377
|
+
projects = j.projects || [];
|
|
378
|
+
treeProjectsCache = { aid: agentId, projects: projects };
|
|
379
|
+
}
|
|
380
|
+
treeEl.innerHTML = '';
|
|
381
|
+
if (projects.length === 0) {
|
|
382
|
+
treeEl.innerHTML = '<div class="project-tree__empty">暂无项目,点击下方按钮新建</div>';
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
var curPk = treePid(projectId);
|
|
386
|
+
if (curPk && treeExpanded[curPk] === undefined) {
|
|
387
|
+
treeExpanded[curPk] = true;
|
|
388
|
+
}
|
|
389
|
+
projects.forEach(function(pr) {
|
|
390
|
+
treeEl.appendChild(createProjectNode(pr));
|
|
391
|
+
});
|
|
392
|
+
// 渲染完成后恢复滚动位置(避免全量重建跳到顶部)
|
|
393
|
+
if (savedScrollTop > 0) treeEl.scrollTop = savedScrollTop;
|
|
394
|
+
// 重建后重新应用搜索过滤(如有)
|
|
395
|
+
var inp = document.getElementById('treeSearchInput');
|
|
396
|
+
if (inp && inp.value.trim()) _treeSearchFilter(inp.value);
|
|
397
|
+
} catch (_) {}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function fetchSessionsForProject(pid) {
|
|
401
|
+
try {
|
|
402
|
+
var r = await fetch('/api/ui/sessions?agent_id=' + encodeURIComponent(agentId) + '&project_id=' + encodeURIComponent(pid));
|
|
403
|
+
if (!r.ok) return [];
|
|
404
|
+
var j = await r.json();
|
|
405
|
+
return j.sessions || [];
|
|
406
|
+
} catch(_) { return []; }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function getTreeProjectPath(pid) {
|
|
410
|
+
var k = treePid(pid);
|
|
411
|
+
if (!k || typeof treeProjectsCache === 'undefined' || !treeProjectsCache || treeProjectsCache.aid !== agentId) return '';
|
|
412
|
+
for (var i = 0; i < treeProjectsCache.projects.length; i++) {
|
|
413
|
+
var p = treeProjectsCache.projects[i];
|
|
414
|
+
if (treePid(p.id) === k && p.path) return String(p.path);
|
|
415
|
+
}
|
|
416
|
+
return '';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ================================================================
|
|
420
|
+
// Project tree search (filter by project name / session title)
|
|
421
|
+
// ================================================================
|
|
422
|
+
|
|
423
|
+
function _treeSearchFilter(q) {
|
|
424
|
+
q = q.trim().toLowerCase();
|
|
425
|
+
var treeEl = document.getElementById('projectTree');
|
|
426
|
+
if (!treeEl) return;
|
|
427
|
+
var projDivs = treeEl.querySelectorAll(':scope > .tree-project');
|
|
428
|
+
var anyVisible = false;
|
|
429
|
+
projDivs.forEach(function(projDiv) {
|
|
430
|
+
var nameEl = projDiv.querySelector('.tree-project__name');
|
|
431
|
+
var pName = nameEl ? (nameEl.textContent || '').toLowerCase() : '';
|
|
432
|
+
var sessionRows = projDiv.querySelectorAll(':scope > .tree-sessions > .tree-session');
|
|
433
|
+
var pMatch = q === '' || pName.indexOf(q) !== -1;
|
|
434
|
+
var sMatch = false;
|
|
435
|
+
sessionRows.forEach(function(sRow) {
|
|
436
|
+
var titleEl = sRow.querySelector('.tree-session__title');
|
|
437
|
+
var sTitle = titleEl ? (titleEl.textContent || '').toLowerCase() : '';
|
|
438
|
+
var match = q === '' || pMatch || sTitle.indexOf(q) !== -1;
|
|
439
|
+
sRow.style.display = match ? '' : 'none';
|
|
440
|
+
if (match) sMatch = true;
|
|
441
|
+
});
|
|
442
|
+
// 项目整体隐藏:无匹配且无匹配的会话
|
|
443
|
+
var hide = q !== '' && !pMatch && !sMatch;
|
|
444
|
+
projDiv.style.display = hide ? 'none' : '';
|
|
445
|
+
if (!hide) anyVisible = true;
|
|
446
|
+
// 有搜索词时自动展开项目(如果有匹配的子会话或项目名匹配)
|
|
447
|
+
if (q !== '' && !hide) {
|
|
448
|
+
var k = treePid(projDiv.dataset.projectId);
|
|
449
|
+
if (!treeIsExpanded(k)) {
|
|
450
|
+
treeExpanded[k] = true;
|
|
451
|
+
var toggle = projDiv.querySelector('.tree-project__toggle');
|
|
452
|
+
if (toggle) toggle.classList.add('tree-project__toggle--expanded');
|
|
453
|
+
}
|
|
454
|
+
var existing = projDiv.querySelector(':scope > .tree-sessions');
|
|
455
|
+
if (!existing) {
|
|
456
|
+
var children = document.createElement('div');
|
|
457
|
+
children.className = 'tree-sessions';
|
|
458
|
+
children.dataset.projectId = k;
|
|
459
|
+
projDiv.appendChild(children);
|
|
460
|
+
loadSessionsIntoContainer(children, k);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
// 空状态
|
|
465
|
+
var empty = treeEl.querySelector('.project-tree__empty');
|
|
466
|
+
if (q !== '' && !anyVisible) {
|
|
467
|
+
if (!empty || !empty.classList.contains('tree-search__empty')) {
|
|
468
|
+
var ne = document.createElement('div');
|
|
469
|
+
ne.className = 'project-tree__empty tree-search__empty';
|
|
470
|
+
ne.textContent = '没有找到匹配的项目或会话';
|
|
471
|
+
if (empty) empty.remove();
|
|
472
|
+
treeEl.appendChild(ne);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
if (empty && empty.classList.contains('tree-search__empty')) empty.remove();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
var _treeSearchTimer = null;
|
|
480
|
+
|
|
481
|
+
function bindTreeSearch() {
|
|
482
|
+
var inp = document.getElementById('treeSearchInput');
|
|
483
|
+
var clearBtn = document.getElementById('treeSearchClear');
|
|
484
|
+
if (!inp) return;
|
|
485
|
+
|
|
486
|
+
// 输入过滤
|
|
487
|
+
inp.addEventListener('input', function() {
|
|
488
|
+
if (_treeSearchTimer) clearTimeout(_treeSearchTimer);
|
|
489
|
+
_treeSearchTimer = setTimeout(function() {
|
|
490
|
+
_treeSearchFilter(inp.value);
|
|
491
|
+
if (clearBtn) clearBtn.style.display = inp.value.trim() ? '' : 'none';
|
|
492
|
+
}, 150);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// 清除按钮
|
|
496
|
+
if (clearBtn) {
|
|
497
|
+
clearBtn.addEventListener('click', function() {
|
|
498
|
+
inp.value = '';
|
|
499
|
+
_treeSearchFilter('');
|
|
500
|
+
clearBtn.style.display = 'none';
|
|
501
|
+
inp.focus();
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Escape 清除
|
|
506
|
+
inp.addEventListener('keydown', function(e) {
|
|
507
|
+
if (e.key === 'Escape') {
|
|
508
|
+
inp.value = '';
|
|
509
|
+
_treeSearchFilter('');
|
|
510
|
+
if (clearBtn) clearBtn.style.display = 'none';
|
|
511
|
+
inp.blur();
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Ctrl+K / Cmd+K 全局快捷键聚焦搜索
|
|
516
|
+
document.addEventListener('keydown', function(e) {
|
|
517
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
518
|
+
e.preventDefault();
|
|
519
|
+
inp.focus();
|
|
520
|
+
inp.select();
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 在 DOM 就绪后绑定搜索
|
|
526
|
+
if (document.readyState === 'loading') {
|
|
527
|
+
document.addEventListener('DOMContentLoaded', bindTreeSearch);
|
|
528
|
+
} else {
|
|
529
|
+
bindTreeSearch();
|
|
530
|
+
}
|
|
531
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* ================================================================
|
|
2
|
+
* 01c-session-utilities.js
|
|
3
|
+
* sessionId 初始化 + 共用工具函数(normReply / formatBubbleTime)。
|
|
4
|
+
*
|
|
5
|
+
* 加载顺序:01c-session-identity.js 之后(依赖 agentId / projectId),
|
|
6
|
+
* 但在所有引用 sessionId 的代码(01c-session-tree.js 等)之前。
|
|
7
|
+
*
|
|
8
|
+
* 依赖 01c-session-identity.js 的 agentId / projectId。
|
|
9
|
+
* ================================================================ */
|
|
10
|
+
|
|
11
|
+
let sessionId = loadSessionIdForAgent(agentId, projectId);
|
|
12
|
+
|
|
13
|
+
// ---------------- Core utility functions ----------------
|
|
14
|
+
// escapeHtml/escAttr 已统一在 00-utils.js(顶层声明)。
|
|
15
|
+
|
|
16
|
+
function normReply(s) {
|
|
17
|
+
return String(s || '').replace(/\r\n/g, '\n').trim();
|
|
18
|
+
}
|
|
19
|
+
function formatBubbleTime(at) {
|
|
20
|
+
if (at == null || at === '') return '';
|
|
21
|
+
const d = typeof at === 'number' ? new Date(at) : new Date(at);
|
|
22
|
+
if (isNaN(d.getTime())) return '';
|
|
23
|
+
return d.toLocaleString('zh-CN', {
|
|
24
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
25
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------- Per-session input draft ----------------
|
|
30
|
+
|
|
31
|
+
const DRAFT_KEY_PREFIX = STORAGE_KEYS.SESS_DRAFT_PREFIX || 'oa_sess_draft_';
|
|
32
|
+
|
|
33
|
+
function _draftKey(sid) {
|
|
34
|
+
return DRAFT_KEY_PREFIX + String(sid || '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 保存当前会话的输入框内容到 localStorage */
|
|
38
|
+
function saveMsgDraft() {
|
|
39
|
+
if (typeof msg === 'undefined' || !msg) return;
|
|
40
|
+
const sid = typeof sessionId !== 'undefined' ? sessionId : '';
|
|
41
|
+
if (!sid) return;
|
|
42
|
+
const text = msg.value || '';
|
|
43
|
+
if (text) {
|
|
44
|
+
trySetLS(_draftKey(sid), text);
|
|
45
|
+
} else {
|
|
46
|
+
tryRemoveLS(_draftKey(sid));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 恢复指定会话的输入框内容,不清空则不清除 localStorage 记录 */
|
|
51
|
+
function restoreMsgDraft(sid, clearAfter) {
|
|
52
|
+
if (typeof msg === 'undefined' || !msg) return;
|
|
53
|
+
if (!sid) { msg.value = ''; return; }
|
|
54
|
+
const saved = tryGetLS(_draftKey(sid), '');
|
|
55
|
+
msg.value = saved;
|
|
56
|
+
if (clearAfter && saved) {
|
|
57
|
+
tryRemoveLS(_draftKey(sid));
|
|
58
|
+
}
|
|
59
|
+
// 触发 auto-resize
|
|
60
|
+
if (typeof msg.style !== 'undefined') {
|
|
61
|
+
msg.style.height = 'auto';
|
|
62
|
+
msg.style.height = Math.min(msg.scrollHeight, 220) + 'px';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------- Auto-save draft on blur ----------------
|
|
67
|
+
// 用户点击侧边栏/切换标签页等场景也会丢失焦点,借此兜底保存。
|
|
68
|
+
if (typeof msg !== 'undefined' && msg) {
|
|
69
|
+
msg.addEventListener('blur', function () {
|
|
70
|
+
if (typeof saveMsgDraft === 'function') saveMsgDraft();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------- Restore draft on page load ----------------
|
|
75
|
+
(function restoreDraftOnLoad() {
|
|
76
|
+
var sid = typeof sessionId !== 'undefined' ? sessionId : '';
|
|
77
|
+
if (sid && typeof restoreMsgDraft === 'function') {
|
|
78
|
+
restoreMsgDraft(sid);
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
|
|
82
|
+
// ---------------- Chat inflight tracking ----------------
|
|
83
|
+
// 主体实现在 01q-inflight.js(chatInflightBySid / bumpChatInflight / restoreRunningSessions)。
|