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,156 @@
|
|
|
1
|
+
/* ================================================================
|
|
2
|
+
* 13-agent.js
|
|
3
|
+
* - Agent page: skill (Agents/<agent_id>/skills/) management,
|
|
4
|
+
* tool (integrated plugins) management,
|
|
5
|
+
* system prompt MD file editor.
|
|
6
|
+
*
|
|
7
|
+
* Depends on (from earlier files): agentId, refreshChatModelSelect
|
|
8
|
+
* ================================================================ */
|
|
9
|
+
|
|
10
|
+
// ---------------- Skill management ----------------
|
|
11
|
+
|
|
12
|
+
// Track which agent's skills are currently displayed.
|
|
13
|
+
// When viewing an agent detail, this is the detail agent ID;
|
|
14
|
+
// on page load, it falls back to the global agentId.
|
|
15
|
+
var _skillAgentId = null;
|
|
16
|
+
|
|
17
|
+
function _currentSkillAgentId() {
|
|
18
|
+
return _skillAgentId || agentId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function loadSkills(overrideAgentId) {
|
|
22
|
+
const aid = overrideAgentId || agentId;
|
|
23
|
+
_skillAgentId = aid;
|
|
24
|
+
const list = document.getElementById('skillList');
|
|
25
|
+
const status = document.getElementById('skillStatus');
|
|
26
|
+
if (!list) return;
|
|
27
|
+
if (status) status.classList.remove('is-err');
|
|
28
|
+
try {
|
|
29
|
+
const r = await fetch('/api/ui/skills?agent_id=' + encodeURIComponent(aid));
|
|
30
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
31
|
+
const j = await r.json();
|
|
32
|
+
const skills = j.skills || [];
|
|
33
|
+
list.innerHTML = '';
|
|
34
|
+
if (skills.length === 0) {
|
|
35
|
+
list.innerHTML = '<div class="cron-empty">暂无技能文件,点击"+ 新增"添加。</div>';
|
|
36
|
+
if (status) status.textContent = '共 0 个技能文件';
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
skills.forEach(function(s) { list.appendChild(buildSkillCard(s)); });
|
|
40
|
+
if (status) status.textContent = '共 ' + skills.length + ' 个技能文件';
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (status) { status.classList.add('is-err'); status.textContent = '加载失败:' + String(e); }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildSkillCard(s) {
|
|
47
|
+
var wrap = document.createElement('div'); wrap.className = 'cron-card-wrap';
|
|
48
|
+
var header = document.createElement('div'); header.className = 'cron-card';
|
|
49
|
+
var info = document.createElement('div'); info.className = 'cron-card__info';
|
|
50
|
+
|
|
51
|
+
var nameRow = document.createElement('div'); nameRow.className = 'cron-card__name';
|
|
52
|
+
nameRow.textContent = s.name;
|
|
53
|
+
var badge = document.createElement('span');
|
|
54
|
+
badge.className = 'cron-card__badge' + (s.enabled !== false ? '' : ' cron-card__badge-off');
|
|
55
|
+
badge.textContent = (s.enabled !== false) ? '启用' : '停用';
|
|
56
|
+
nameRow.appendChild(badge);
|
|
57
|
+
info.appendChild(nameRow);
|
|
58
|
+
|
|
59
|
+
var detail = document.createElement('div'); detail.className = 'cron-card__detail';
|
|
60
|
+
detail.textContent = s.path || '';
|
|
61
|
+
info.appendChild(detail);
|
|
62
|
+
|
|
63
|
+
var actions = document.createElement('div'); actions.className = 'cron-card__actions';
|
|
64
|
+
var btnToggle = document.createElement('button');
|
|
65
|
+
btnToggle.type = 'button'; btnToggle.className = 'btn btn--ghost btn--xs';
|
|
66
|
+
btnToggle.textContent = (s.enabled !== false) ? '停用' : '启用';
|
|
67
|
+
btnToggle.addEventListener('click', function(ev) {
|
|
68
|
+
ev.stopPropagation();
|
|
69
|
+
s.enabled = !(s.enabled !== false);
|
|
70
|
+
toggleSkillEnabled(s);
|
|
71
|
+
});
|
|
72
|
+
actions.appendChild(btnToggle);
|
|
73
|
+
|
|
74
|
+
var btnDel = document.createElement('button');
|
|
75
|
+
btnDel.type = 'button'; btnDel.className = 'btn btn--ghost btn--xs cron-del'; btnDel.textContent = '删除';
|
|
76
|
+
btnDel.addEventListener('click', function(ev) {
|
|
77
|
+
ev.stopPropagation();
|
|
78
|
+
if (!confirm('确定删除技能「' + s.name + '」?')) return;
|
|
79
|
+
deleteSkill(s.name);
|
|
80
|
+
});
|
|
81
|
+
actions.appendChild(btnDel);
|
|
82
|
+
|
|
83
|
+
header.appendChild(info); header.appendChild(actions);
|
|
84
|
+
|
|
85
|
+
var editWrap = document.createElement('div'); editWrap.className = 'cron-edit-wrap'; editWrap.style.display = 'none';
|
|
86
|
+
var editStatus = document.createElement('div'); editStatus.className = 'status-line';
|
|
87
|
+
editWrap.innerHTML = '<label class="form-label">文件名</label>'
|
|
88
|
+
+ '<input class="skill-fld-name" type="text" value="' + escAttr(s.name) + '" placeholder="my-skill.md"/>'
|
|
89
|
+
+ '<label class="form-label">内容(Markdown)</label>'
|
|
90
|
+
+ '<textarea class="skill-fld-body" spellcheck="false" style="height:200px;font-family:var(--font-mono);font-size:13px;width:100%;padding:var(--sp-2);border:1px solid var(--border);border-radius:var(--r-sm);background:var(--bg);color:var(--text);resize:vertical;">' + escAttr(s.content || '') + '</textarea>'
|
|
91
|
+
+ '<div class="row-actions" style="margin-top:var(--sp-2);">'
|
|
92
|
+
+ '<button type="button" class="btn btn--primary btn--sm skill-save-btn">保存</button>'
|
|
93
|
+
+ '<button type="button" class="btn btn--subtle btn--sm skill-cancel-btn">取消</button>'
|
|
94
|
+
+ '</div>';
|
|
95
|
+
editWrap.appendChild(editStatus);
|
|
96
|
+
|
|
97
|
+
var toggleEdit = function(show) { editWrap.style.display = show ? 'block' : 'none'; editStatus.textContent = ''; editStatus.classList.remove('is-err'); };
|
|
98
|
+
|
|
99
|
+
// Toggle edit on header click
|
|
100
|
+
header.style.cursor = 'pointer';
|
|
101
|
+
header.addEventListener('click', function(e) {
|
|
102
|
+
if (e.target.closest('.btn') || e.target.closest('.cron-card__actions')) return;
|
|
103
|
+
toggleEdit(editWrap.style.display !== 'block');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
editWrap.querySelector('.skill-cancel-btn').addEventListener('click', function() { toggleEdit(false); });
|
|
107
|
+
editWrap.querySelector('.skill-save-btn').addEventListener('click', async function() {
|
|
108
|
+
var name = editWrap.querySelector('.skill-fld-name').value.trim();
|
|
109
|
+
var body = editWrap.querySelector('.skill-fld-body').value;
|
|
110
|
+
if (!name) { editStatus.textContent = '文件名不能为空'; editStatus.classList.add('is-err'); return; }
|
|
111
|
+
editStatus.textContent = '保存中…';
|
|
112
|
+
editStatus.classList.remove('is-err');
|
|
113
|
+
try {
|
|
114
|
+
var r = await fetch('/api/ui/skills?agent_id=' + encodeURIComponent(_currentSkillAgentId()), {
|
|
115
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({ action: 'save', name: name, content: body })
|
|
117
|
+
});
|
|
118
|
+
var j = await r.json().catch(function() { return {}; });
|
|
119
|
+
if (!r.ok) throw new Error(j.detail || r.statusText);
|
|
120
|
+
editStatus.textContent = j.hint || '已保存。';
|
|
121
|
+
toggleEdit(false);
|
|
122
|
+
await loadSkills(_currentSkillAgentId());
|
|
123
|
+
} catch (e) { editStatus.classList.add('is-err'); editStatus.textContent = String(e); }
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
wrap.appendChild(header); wrap.appendChild(editWrap);
|
|
127
|
+
return wrap;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function toggleSkillEnabled(s) {
|
|
131
|
+
try {
|
|
132
|
+
var r = await fetch('/api/ui/skills?agent_id=' + encodeURIComponent(_currentSkillAgentId()), {
|
|
133
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ action: 'save', name: s.name, content: s.content || '', enabled: s.enabled })
|
|
135
|
+
});
|
|
136
|
+
if (!r.ok) throw new Error((await r.json()).detail || r.statusText);
|
|
137
|
+
await loadSkills(_currentSkillAgentId());
|
|
138
|
+
} catch (e) {
|
|
139
|
+
const st = document.getElementById('skillStatus');
|
|
140
|
+
if (st) { st.classList.add('is-err'); st.textContent = String(e); }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function deleteSkill(name) {
|
|
145
|
+
try {
|
|
146
|
+
var r = await fetch('/api/ui/skills?agent_id=' + encodeURIComponent(_currentSkillAgentId()), {
|
|
147
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({ action: 'delete', name: name })
|
|
149
|
+
});
|
|
150
|
+
if (!r.ok) throw new Error((await r.json()).detail || r.statusText);
|
|
151
|
+
await loadSkills(_currentSkillAgentId());
|
|
152
|
+
} catch (e) {
|
|
153
|
+
const st = document.getElementById('skillStatus');
|
|
154
|
+
if (st) { st.classList.add('is-err'); st.textContent = String(e); }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* ================================================================
|
|
2
|
+
* 13-todos.js — Todo panel UI (v3: scope toggle + session isolation)
|
|
3
|
+
* - render todo items from /api/ui/projects/todos
|
|
4
|
+
* - scope toggle: 当前会话(session) / 全部(project)
|
|
5
|
+
* - toggle checkboxes to update status
|
|
6
|
+
* - delete buttons
|
|
7
|
+
* - grouped by session_id with round identifiers
|
|
8
|
+
* - auto-refresh on project changes
|
|
9
|
+
* ================================================================ */
|
|
10
|
+
|
|
11
|
+
const btnToggleTodos = document.getElementById('btnToggleTodos');
|
|
12
|
+
const todoPanel = document.getElementById('todoPanel');
|
|
13
|
+
const todoList = document.getElementById('todoList');
|
|
14
|
+
const btnTodoRefresh = document.getElementById('btnTodoRefresh');
|
|
15
|
+
const todoScopeToggle = document.getElementById('todoScopeToggle');
|
|
16
|
+
|
|
17
|
+
let todoScope = 'session'; // 'session' | 'project'
|
|
18
|
+
|
|
19
|
+
// 用实际 DOM display 状态判断,避免被对方面板互斥关闭后状态不同步
|
|
20
|
+
function todoIsVisible() {
|
|
21
|
+
return todoPanel.style.display === 'flex';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (tryGetLS(STORAGE_KEYS.TODO_PANEL_OPEN) === '1') {
|
|
26
|
+
// 初始化互斥:如果 Plan 已打开,不覆盖
|
|
27
|
+
var planP = document.getElementById('planPanel');
|
|
28
|
+
if (planP && planP.style.display === 'flex') {
|
|
29
|
+
// Plan 已开 → 忽略 localStorage,自己不打开
|
|
30
|
+
} else {
|
|
31
|
+
todoPanel.style.display = 'flex';
|
|
32
|
+
btnToggleTodos.classList.add('is-active');
|
|
33
|
+
setTimeout(function() { refreshTodos(); }, 100);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const saved = tryGetLS(STORAGE_KEYS.TODO_SCOPE);
|
|
40
|
+
if (saved === 'project' || saved === 'session') todoScope = saved;
|
|
41
|
+
} catch (_) {}
|
|
42
|
+
|
|
43
|
+
btnToggleTodos.addEventListener('click', () => {
|
|
44
|
+
var opening = !todoIsVisible();
|
|
45
|
+
todoPanel.style.display = opening ? 'flex' : 'none';
|
|
46
|
+
btnToggleTodos.classList.toggle('is-active', opening);
|
|
47
|
+
trySetLS(STORAGE_KEYS.TODO_PANEL_OPEN, opening ? '1' : '0');
|
|
48
|
+
// 互斥:打开待办时自动关闭计划面板
|
|
49
|
+
if (opening) {
|
|
50
|
+
var planPanel = document.getElementById('planPanel');
|
|
51
|
+
var planBtn = document.getElementById('btnTogglePlans');
|
|
52
|
+
if (planPanel && planPanel.style.display !== 'none') {
|
|
53
|
+
planPanel.style.display = 'none';
|
|
54
|
+
if (planBtn) planBtn.classList.remove('is-active');
|
|
55
|
+
trySetLS(STORAGE_KEYS.PLAN_PANEL_OPEN, '0');
|
|
56
|
+
}
|
|
57
|
+
refreshTodos();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (btnTodoRefresh) { btnTodoRefresh.addEventListener('click', refreshTodos); }
|
|
62
|
+
|
|
63
|
+
/* ---- Scope toggle ---- */
|
|
64
|
+
|
|
65
|
+
function applyScopeUI() {
|
|
66
|
+
if (!todoScopeToggle) return;
|
|
67
|
+
const btns = todoScopeToggle.querySelectorAll('.todo-scope-btn');
|
|
68
|
+
for (const btn of btns) {
|
|
69
|
+
btn.classList.toggle('is-active', btn.dataset.scope === todoScope);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (todoScopeToggle) {
|
|
74
|
+
todoScopeToggle.addEventListener('click', (e) => {
|
|
75
|
+
const btn = e.target.closest('.todo-scope-btn');
|
|
76
|
+
if (!btn) return;
|
|
77
|
+
const scope = btn.dataset.scope;
|
|
78
|
+
if (scope === todoScope) return;
|
|
79
|
+
todoScope = scope;
|
|
80
|
+
trySetLS(STORAGE_KEYS.TODO_SCOPE, todoScope);
|
|
81
|
+
applyScopeUI();
|
|
82
|
+
refreshTodos();
|
|
83
|
+
});
|
|
84
|
+
applyScopeUI();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ---- helpers ---- */
|
|
88
|
+
|
|
89
|
+
function shortenId(id) {
|
|
90
|
+
if (!id) return '—';
|
|
91
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function formatRelativeTime(isoStr) {
|
|
95
|
+
if (!isoStr) return '';
|
|
96
|
+
const then = new Date(isoStr);
|
|
97
|
+
if (isNaN(then.getTime())) return isoStr;
|
|
98
|
+
const diffMs = Date.now() - then.getTime();
|
|
99
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
100
|
+
if (diffSec < 10) return '刚刚';
|
|
101
|
+
if (diffSec < 60) return diffSec + '秒前';
|
|
102
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
103
|
+
if (diffMin < 60) return diffMin + '分钟前';
|
|
104
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
105
|
+
if (diffHr < 24) return diffHr + '小时前';
|
|
106
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
107
|
+
if (diffDay < 30) return diffDay + '天前';
|
|
108
|
+
return then.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Deterministic color from session_id hash
|
|
112
|
+
const sessionColors = [
|
|
113
|
+
{ bg: '#e3f2fd', text: '#1565c0', border: '#90caf9' },
|
|
114
|
+
{ bg: '#fce4ec', text: '#c62828', border: '#f48fb1' },
|
|
115
|
+
{ bg: '#e8f5e9', text: '#2e7d32', border: '#a5d6a7' },
|
|
116
|
+
{ bg: '#fff3e0', text: '#e65100', border: '#ffcc80' },
|
|
117
|
+
{ bg: '#f3e5f5', text: '#7b1fa2', border: '#ce93d8' },
|
|
118
|
+
{ bg: '#e0f7fa', text: '#00695c', border: '#80deea' },
|
|
119
|
+
{ bg: '#fbe9e7', text: '#bf360c', border: '#ffab91' },
|
|
120
|
+
{ bg: '#ede7f6', text: '#4527a0', border: '#b39ddb' },
|
|
121
|
+
{ bg: '#f1f8e9', text: '#558b2f', border: '#aed581' },
|
|
122
|
+
{ bg: '#fff8e1', text: '#f57f17', border: '#ffe082' },
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
function sessionColor(sessionId) {
|
|
126
|
+
if (!sessionId) return sessionColors[0];
|
|
127
|
+
let hash = 0;
|
|
128
|
+
for (let i = 0; i < sessionId.length; i++) {
|
|
129
|
+
hash = ((hash << 5) - hash) + sessionId.charCodeAt(i);
|
|
130
|
+
hash |= 0;
|
|
131
|
+
}
|
|
132
|
+
const idx = Math.abs(hash) % sessionColors.length;
|
|
133
|
+
return sessionColors[idx];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function formatSessionLabel(sessionId) {
|
|
137
|
+
if (!sessionId) return '未分组';
|
|
138
|
+
return shortenId(sessionId);
|
|
139
|
+
}
|
|
140
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* ---- API ---- */
|
|
2
|
+
|
|
3
|
+
async function fetchTodos() {
|
|
4
|
+
const pid = typeof projectId !== 'undefined' ? (projectId || '') : '';
|
|
5
|
+
if (!pid) return [];
|
|
6
|
+
try {
|
|
7
|
+
let url = '/api/ui/projects/todos?project_id=' + encodeURIComponent(pid)
|
|
8
|
+
+ '&agent_id=' + encodeURIComponent(typeof agentId !== 'undefined' ? agentId : 'default');
|
|
9
|
+
// When scope is "session", filter by current session_id
|
|
10
|
+
if (todoScope === 'session') {
|
|
11
|
+
const sid = typeof sessionId !== 'undefined' ? (sessionId || '') : '';
|
|
12
|
+
if (sid) url += '&session_id=' + encodeURIComponent(sid);
|
|
13
|
+
}
|
|
14
|
+
const r = await fetch(url);
|
|
15
|
+
if (!r.ok) return [];
|
|
16
|
+
const j = await r.json();
|
|
17
|
+
return j.todos || [];
|
|
18
|
+
} catch (_) { return []; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* ---- Grouping & Rendering ---- */
|
|
22
|
+
|
|
23
|
+
function groupBySession(todos) {
|
|
24
|
+
const groups = {};
|
|
25
|
+
for (const t of todos) {
|
|
26
|
+
const sid = t.session_id || '';
|
|
27
|
+
if (!groups[sid]) groups[sid] = [];
|
|
28
|
+
groups[sid].push(t);
|
|
29
|
+
}
|
|
30
|
+
for (const sid in groups) {
|
|
31
|
+
groups[sid].sort((a, b) => {
|
|
32
|
+
const da = a.created_at || '';
|
|
33
|
+
const db = b.created_at || '';
|
|
34
|
+
return da.localeCompare(db);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const keys = Object.keys(groups);
|
|
38
|
+
keys.sort((a, b) => {
|
|
39
|
+
const aOldest = groups[a][0]?.created_at || '';
|
|
40
|
+
const bOldest = groups[b][0]?.created_at || '';
|
|
41
|
+
const cmp = aOldest.localeCompare(bOldest);
|
|
42
|
+
if (cmp !== 0) return cmp;
|
|
43
|
+
return a.localeCompare(b);
|
|
44
|
+
});
|
|
45
|
+
return keys.map(k => ({ sessionId: k, items: groups[k] }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function refreshTodos() {
|
|
49
|
+
const todos = await fetchTodos();
|
|
50
|
+
if (!todoList) return;
|
|
51
|
+
todoList.innerHTML = '';
|
|
52
|
+
if (!todos || !todos.length) {
|
|
53
|
+
const msg = todoScope === 'session'
|
|
54
|
+
? '当前会话暂无待办事项<br/>让 Agent 创建待办'
|
|
55
|
+
: '暂无待办事项<br/>在会话中让 Agent 创建待办';
|
|
56
|
+
todoList.innerHTML = '<div class="todo-empty">' + msg + '</div>';
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const grouped = groupBySession(todos);
|
|
60
|
+
let first = true;
|
|
61
|
+
for (const g of grouped) {
|
|
62
|
+
if (!first) {
|
|
63
|
+
const sep = document.createElement('div');
|
|
64
|
+
sep.className = 'todo-group-sep';
|
|
65
|
+
todoList.appendChild(sep);
|
|
66
|
+
}
|
|
67
|
+
first = false;
|
|
68
|
+
// In session scope, the group header is simpler — just a count
|
|
69
|
+
if (todoScope === 'session') {
|
|
70
|
+
// Show a minimal "来自当前会话" indicator instead of full group header
|
|
71
|
+
const c = sessionColor(g.sessionId);
|
|
72
|
+
const header = document.createElement('div');
|
|
73
|
+
header.className = 'todo-group-header';
|
|
74
|
+
header.innerHTML = '<span class="todo-group-badge" style="--gb-bg:' + c.bg + ';--gb-text:' + c.text + ';--gb-border:' + c.border + '">' + formatSessionLabel(g.sessionId) + '</span>'
|
|
75
|
+
+ '<span class="todo-group-meta"><span class="todo-group-count">' + g.items.length + ' 项</span></span>';
|
|
76
|
+
todoList.appendChild(header);
|
|
77
|
+
} else {
|
|
78
|
+
todoList.appendChild(buildGroupHeader(g.sessionId, g.items));
|
|
79
|
+
}
|
|
80
|
+
for (const t of g.items) {
|
|
81
|
+
todoList.appendChild(buildTodoItem(t, g.sessionId));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildGroupHeader(sessionId, items) {
|
|
87
|
+
const c = sessionColor(sessionId);
|
|
88
|
+
const header = document.createElement('div');
|
|
89
|
+
header.className = 'todo-group-header';
|
|
90
|
+
const label = formatSessionLabel(sessionId);
|
|
91
|
+
const count = items.length;
|
|
92
|
+
const times = items.map(t => t.created_at).filter(Boolean).sort();
|
|
93
|
+
const timeStr = times.length > 0
|
|
94
|
+
? formatRelativeTime(times[0]) + (times.length > 1 ? ' ~ ' + formatRelativeTime(times[times.length - 1]) : '')
|
|
95
|
+
: '';
|
|
96
|
+
header.innerHTML = `
|
|
97
|
+
<span class="todo-group-badge" style="--gb-bg:${c.bg};--gb-text:${c.text};--gb-border:${c.border}">
|
|
98
|
+
${label}
|
|
99
|
+
</span>
|
|
100
|
+
<span class="todo-group-meta">
|
|
101
|
+
<span class="todo-group-count">${count} 项</span>
|
|
102
|
+
${timeStr ? '<span class="todo-group-time">' + timeStr + '</span>' : ''}
|
|
103
|
+
</span>
|
|
104
|
+
`;
|
|
105
|
+
return header;
|
|
106
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
function showNewSkillForm() {
|
|
2
|
+
var list = document.getElementById('skillList');
|
|
3
|
+
if (!list) return;
|
|
4
|
+
var existing = list.querySelector('.cron-card-wrap.is-new');
|
|
5
|
+
if (existing) existing.remove();
|
|
6
|
+
|
|
7
|
+
var wrap = document.createElement('div'); wrap.className = 'cron-card-wrap is-new';
|
|
8
|
+
var editWrap = document.createElement('div'); editWrap.className = 'cron-edit-wrap'; editWrap.style.display = 'block';
|
|
9
|
+
var editStatus = document.createElement('div'); editStatus.className = 'status-line';
|
|
10
|
+
editWrap.innerHTML = '<label class="form-label">文件名</label>'
|
|
11
|
+
+ '<input class="skill-fld-name" type="text" placeholder="my-skill.md"/>'
|
|
12
|
+
+ '<label class="form-label">内容(Markdown)</label>'
|
|
13
|
+
+ '<textarea class="skill-fld-body" spellcheck="false" style="height:200px;font-family:var(--font-mono);font-size:13px;width:100%;padding:var(--sp-2);border:1px solid var(--border);border-radius:var(--r-sm);background:var(--bg);color:var(--text);resize:vertical;"></textarea>'
|
|
14
|
+
+ '<div class="row-actions" style="margin-top:var(--sp-2);">'
|
|
15
|
+
+ '<button type="button" class="btn btn--primary btn--sm skill-save-btn">保存</button>'
|
|
16
|
+
+ '<button type="button" class="btn btn--subtle btn--sm skill-cancel-btn">取消</button>'
|
|
17
|
+
+ '</div>';
|
|
18
|
+
editWrap.appendChild(editStatus);
|
|
19
|
+
|
|
20
|
+
editWrap.querySelector('.skill-cancel-btn').addEventListener('click', function() { wrap.remove(); });
|
|
21
|
+
editWrap.querySelector('.skill-save-btn').addEventListener('click', async function() {
|
|
22
|
+
var name = editWrap.querySelector('.skill-fld-name').value.trim();
|
|
23
|
+
var body = editWrap.querySelector('.skill-fld-body').value;
|
|
24
|
+
if (!name) { editStatus.textContent = '文件名不能为空'; editStatus.classList.add('is-err'); return; }
|
|
25
|
+
editStatus.textContent = '保存中…';
|
|
26
|
+
editStatus.classList.remove('is-err');
|
|
27
|
+
try {
|
|
28
|
+
var r = await fetch('/api/ui/skills?agent_id=' + encodeURIComponent(_currentSkillAgentId()), {
|
|
29
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify({ action: 'save', name: name, content: body })
|
|
31
|
+
});
|
|
32
|
+
var j = await r.json().catch(function() { return {}; });
|
|
33
|
+
if (!r.ok) throw new Error(j.detail || r.statusText);
|
|
34
|
+
wrap.remove();
|
|
35
|
+
await loadSkills(_currentSkillAgentId());
|
|
36
|
+
} catch (e) { editStatus.classList.add('is-err'); editStatus.textContent = String(e); }
|
|
37
|
+
});
|
|
38
|
+
wrap.appendChild(editWrap);
|
|
39
|
+
if (list.firstChild) list.insertBefore(wrap, list.firstChild);
|
|
40
|
+
else list.appendChild(wrap);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
document.getElementById('btnSkillAdd') && document.getElementById('btnSkillAdd').addEventListener('click', showNewSkillForm);
|
|
44
|
+
document.getElementById('btnSkillRefresh') && document.getElementById('btnSkillRefresh').addEventListener('click', function() { loadSkills(typeof _currentSkillAgentId === 'function' ? _currentSkillAgentId() : undefined); });
|
|
45
|
+
|
|
46
|
+
// ---------------- Tool (integrated plugins) management ----------------
|
|
47
|
+
|
|
48
|
+
async function loadTools() {
|
|
49
|
+
const list = document.getElementById('toolList');
|
|
50
|
+
const status = document.getElementById('toolStatus');
|
|
51
|
+
if (!list) return;
|
|
52
|
+
status.textContent = '';
|
|
53
|
+
status.classList.remove('is-err');
|
|
54
|
+
try {
|
|
55
|
+
const r = await fetch('/api/ui/plugins');
|
|
56
|
+
const j = await r.json();
|
|
57
|
+
if (!r.ok) throw new Error(j.detail || r.statusText);
|
|
58
|
+
list.innerHTML = '';
|
|
59
|
+
|
|
60
|
+
var tools = j.plugins || {};
|
|
61
|
+
var toolKeys = Object.keys(tools);
|
|
62
|
+
if (toolKeys.length === 0) {
|
|
63
|
+
list.innerHTML = '<div class="cron-empty">暂无可用工具。</div>';
|
|
64
|
+
if (status) status.textContent = '0 个可用工具';
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
toolKeys.forEach(function(tid) {
|
|
68
|
+
var t = tools[tid] || {};
|
|
69
|
+
var wrap = document.createElement('div'); wrap.className = 'tool-card-wrap';
|
|
70
|
+
var card = document.createElement('div'); card.className = 'cron-card';
|
|
71
|
+
var info = document.createElement('div'); info.className = 'cron-card__info';
|
|
72
|
+
var nameRow = document.createElement('div'); nameRow.className = 'cron-card__name';
|
|
73
|
+
nameRow.textContent = tid;
|
|
74
|
+
var badge = document.createElement('span');
|
|
75
|
+
badge.className = 'cron-card__badge' + (t.enabled !== false ? '' : ' cron-card__badge-off');
|
|
76
|
+
badge.textContent = (t.enabled !== false) ? '启用' : '停用';
|
|
77
|
+
nameRow.appendChild(badge);
|
|
78
|
+
info.appendChild(nameRow);
|
|
79
|
+
var detail = document.createElement('div'); detail.className = 'cron-card__detail';
|
|
80
|
+
detail.textContent = t.description || '';
|
|
81
|
+
info.appendChild(detail);
|
|
82
|
+
var actions = document.createElement('div'); actions.className = 'cron-card__actions';
|
|
83
|
+
var btn = document.createElement('button');
|
|
84
|
+
btn.type = 'button'; btn.className = 'btn btn--ghost btn--xs';
|
|
85
|
+
btn.textContent = (t.enabled !== false) ? '停用' : '启用';
|
|
86
|
+
btn.addEventListener('click', function(ev) {
|
|
87
|
+
ev.stopPropagation();
|
|
88
|
+
toggleTool(tid, !(t.enabled !== false));
|
|
89
|
+
});
|
|
90
|
+
actions.appendChild(btn);
|
|
91
|
+
card.appendChild(info); card.appendChild(actions);
|
|
92
|
+
wrap.appendChild(card);
|
|
93
|
+
list.appendChild(wrap);
|
|
94
|
+
});
|
|
95
|
+
if (status) status.textContent = '共 ' + toolKeys.length + ' 个可用工具';
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (status) { status.classList.add('is-err'); status.textContent = String(e); }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function toggleTool(toolId, enabled) {
|
|
102
|
+
const body = {};
|
|
103
|
+
body[toolId] = { enabled: enabled };
|
|
104
|
+
try {
|
|
105
|
+
const r = await fetch('/api/ui/plugins', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ plugins: body }) });
|
|
106
|
+
const j = await r.json();
|
|
107
|
+
if (!r.ok) throw new Error(j.detail || r.statusText);
|
|
108
|
+
await loadTools();
|
|
109
|
+
} catch (e) {
|
|
110
|
+
const st = document.getElementById('toolStatus');
|
|
111
|
+
if (st) { st.classList.add('is-err'); st.textContent = String(e); }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
document.getElementById('btnToolRefresh') && document.getElementById('btnToolRefresh').addEventListener('click', function() { loadTools(); });
|
|
116
|
+
|
|
117
|
+
// ---------------- Agent page load orchestration ----------------
|
|
118
|
+
|
|
119
|
+
async function loadAgentPage() {
|
|
120
|
+
// 单 Agent 模式:加载当前 Agent 的技能、工具和身份配置文件
|
|
121
|
+
if (typeof renderCurrentAgent === 'function') renderCurrentAgent();
|
|
122
|
+
await loadSkills();
|
|
123
|
+
await loadTools();
|
|
124
|
+
if (typeof loadMdFiles === 'function') {
|
|
125
|
+
await loadMdFiles();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
function buildTodoItem(todo, sessionId) {
|
|
2
|
+
const c = sessionColor(sessionId);
|
|
3
|
+
const wrap = document.createElement('div');
|
|
4
|
+
wrap.className = 'todo-item';
|
|
5
|
+
wrap.dataset.todoId = todo.id;
|
|
6
|
+
|
|
7
|
+
// ── Main row: checkbox + content + delete ──
|
|
8
|
+
const mainRow = document.createElement('div');
|
|
9
|
+
mainRow.className = 'todo-item__main';
|
|
10
|
+
|
|
11
|
+
const cb = document.createElement('input');
|
|
12
|
+
cb.type = 'checkbox';
|
|
13
|
+
cb.className = 'todo-item__checkbox';
|
|
14
|
+
cb.checked = todo.status === 'completed';
|
|
15
|
+
cb.addEventListener('change', async () => {
|
|
16
|
+
await updateTodoApi(todo.id, { status: cb.checked ? 'completed' : 'pending' });
|
|
17
|
+
refreshTodos();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const content = document.createElement('span');
|
|
21
|
+
content.className = 'todo-item__content' + (todo.status === 'completed' ? ' todo-item__content--done' : '');
|
|
22
|
+
content.textContent = todo.content;
|
|
23
|
+
|
|
24
|
+
const delBtn = document.createElement('button');
|
|
25
|
+
delBtn.className = 'todo-item__delete';
|
|
26
|
+
delBtn.textContent = '\u00D7';
|
|
27
|
+
delBtn.title = '删除';
|
|
28
|
+
delBtn.addEventListener('click', async (e) => {
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
await deleteTodoApi(todo.id);
|
|
31
|
+
refreshTodos();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
mainRow.appendChild(cb);
|
|
35
|
+
mainRow.appendChild(content);
|
|
36
|
+
mainRow.appendChild(delBtn);
|
|
37
|
+
|
|
38
|
+
// ── Meta row: time + status + (optional) session badge ──
|
|
39
|
+
const metaRow = document.createElement('div');
|
|
40
|
+
metaRow.className = 'todo-item__meta';
|
|
41
|
+
|
|
42
|
+
const timeBadge = document.createElement('span');
|
|
43
|
+
timeBadge.className = 'todo-item__time';
|
|
44
|
+
timeBadge.textContent = formatRelativeTime(todo.created_at);
|
|
45
|
+
timeBadge.title = todo.created_at || '';
|
|
46
|
+
|
|
47
|
+
const statusBadge = document.createElement('span');
|
|
48
|
+
statusBadge.className = 'todo-item__status todo-item__status--' + todo.status;
|
|
49
|
+
const statusLabels = { pending: '待办', in_progress: '进行中', completed: '已完成', cancelled: '已取消' };
|
|
50
|
+
statusBadge.textContent = statusLabels[todo.status] || todo.status;
|
|
51
|
+
|
|
52
|
+
metaRow.appendChild(timeBadge);
|
|
53
|
+
metaRow.appendChild(statusBadge);
|
|
54
|
+
|
|
55
|
+
// Session badge — only in project scope (all sessions mode)
|
|
56
|
+
if (todoScope === 'project' && sessionId) {
|
|
57
|
+
const sidBadge = document.createElement('span');
|
|
58
|
+
sidBadge.className = 'todo-item__session';
|
|
59
|
+
sidBadge.style.setProperty('--s-bg', c.bg);
|
|
60
|
+
sidBadge.style.setProperty('--s-text', c.text);
|
|
61
|
+
sidBadge.style.setProperty('--s-border', c.border);
|
|
62
|
+
sidBadge.textContent = formatSessionLabel(sessionId);
|
|
63
|
+
sidBadge.title = '会话 ID: ' + (sessionId || '未分组');
|
|
64
|
+
metaRow.appendChild(sidBadge);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
wrap.appendChild(mainRow);
|
|
68
|
+
wrap.appendChild(metaRow);
|
|
69
|
+
return wrap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ---- Network ---- */
|
|
73
|
+
|
|
74
|
+
async function updateTodoApi(todoId, updates) {
|
|
75
|
+
const pid = typeof projectId !== 'undefined' ? (projectId || '') : '';
|
|
76
|
+
try {
|
|
77
|
+
await fetch('/api/ui/projects/todos/' + encodeURIComponent(todoId), {
|
|
78
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify(Object.assign({ project_id: pid, agent_id: typeof agentId !== 'undefined' ? agentId : 'default' }, updates))
|
|
80
|
+
});
|
|
81
|
+
} catch (_) {}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function deleteTodoApi(todoId) {
|
|
85
|
+
const pid = typeof projectId !== 'undefined' ? (projectId || '') : '';
|
|
86
|
+
try {
|
|
87
|
+
await fetch('/api/ui/projects/todos/' + encodeURIComponent(todoId) + '?project_id=' + encodeURIComponent(pid) + '&agent_id=' + encodeURIComponent(typeof agentId !== 'undefined' ? agentId : 'default'), { method: 'DELETE' });
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
window.addEventListener('project-changed', () => { if (todoIsVisible()) refreshTodos(); });
|
|
92
|
+
window.addEventListener('session-changed', () => { if (todoIsVisible()) refreshTodos(); });
|