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.
Files changed (173) hide show
  1. codeagent/__init__.py +10 -0
  2. codeagent/__main__.py +13 -0
  3. codeagent/cli/__init__.py +13 -0
  4. codeagent/cli/entrypoint.py +161 -0
  5. codeagent/cli/main.py +979 -0
  6. codeagent/core/__init__.py +2 -0
  7. codeagent/core/attachments.py +342 -0
  8. codeagent/core/audio_models.py +50 -0
  9. codeagent/core/bootstrap.py +178 -0
  10. codeagent/core/default_presets.py +159 -0
  11. codeagent/core/env.py +106 -0
  12. codeagent/core/folder_picker.py +151 -0
  13. codeagent/core/image_understanding.py +90 -0
  14. codeagent/core/models.py +6 -0
  15. codeagent/core/paths.py +238 -0
  16. codeagent/core/pricing.py +107 -0
  17. codeagent/core/process_ports.py +258 -0
  18. codeagent/core/seed_bridge.py +21 -0
  19. codeagent/core/settings.py +98 -0
  20. codeagent/core/speech_synth.py +330 -0
  21. codeagent/core/team_config.py +247 -0
  22. codeagent/core/team_manager.py +245 -0
  23. codeagent/core/token_counter.py +41 -0
  24. codeagent/core/tts_voices.py +103 -0
  25. codeagent/core/usage_billing.py +72 -0
  26. codeagent/core/vision_models.py +93 -0
  27. codeagent/memory/diary.py +144 -0
  28. codeagent/persona_defaults/README.md +12 -0
  29. codeagent/persona_defaults/agent.md +352 -0
  30. codeagent/persona_defaults/identity.md +29 -0
  31. codeagent/persona_defaults/memory.md +4 -0
  32. codeagent/persona_defaults/skills.md +325 -0
  33. codeagent/persona_defaults/soul.md +38 -0
  34. codeagent/persona_defaults/tools.md +41 -0
  35. codeagent/persona_defaults/user.md +18 -0
  36. codeagent/runtime/__init__.py +11 -0
  37. codeagent/runtime/agent_context.py +6 -0
  38. codeagent/runtime/agent_runtime.py +21 -0
  39. codeagent/runtime/compact_prompt.py +9 -0
  40. codeagent/runtime/compact_state.py +40 -0
  41. codeagent/runtime/llm_worker.py +132 -0
  42. codeagent/runtime/orchestrator.py +161 -0
  43. codeagent/runtime/prompt_enrichment.py +324 -0
  44. codeagent/runtime/session_manager.py +30 -0
  45. codeagent/runtime/task_split.py +38 -0
  46. codeagent/runtime/turn_loop.py +13 -0
  47. codeagent/runtime/worker.py +48 -0
  48. codeagent/server/__init__.py +505 -0
  49. codeagent/server/app_factory.py +1292 -0
  50. codeagent/server/attachment_api.py +261 -0
  51. codeagent/server/hub.py +175 -0
  52. codeagent/server/icon.png +0 -0
  53. codeagent/server/self_healing.py +143 -0
  54. codeagent/server/team_engine.py +243 -0
  55. codeagent/server/tts_api.py +51 -0
  56. codeagent/server/webui_api_app.py +3180 -0
  57. codeagent/skills/__init__.py +2 -0
  58. codeagent/skills/select.py +144 -0
  59. codeagent/tools/__init__.py +6 -0
  60. codeagent/tools/agent_tools.py +10 -0
  61. codeagent/web/__init__.py +1 -0
  62. codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  63. codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  64. codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  65. codeagent/web/auth_impl.py +326 -0
  66. codeagent/web/static/00-marked.min.js +6 -0
  67. codeagent/web/static/00-mermaid.min.js +2314 -0
  68. codeagent/web/static/00-purify.min.js +3 -0
  69. codeagent/web/static/00-storage.js +83 -0
  70. codeagent/web/static/00-utils.js +51 -0
  71. codeagent/web/static/01-theme.css +96 -0
  72. codeagent/web/static/01a-scroll.js +86 -0
  73. codeagent/web/static/01b-think.js +62 -0
  74. codeagent/web/static/01c-session-identity.js +81 -0
  75. codeagent/web/static/01c-session-thinking.js +61 -0
  76. codeagent/web/static/01c-session-tree-menus.js +349 -0
  77. codeagent/web/static/01c-session-tree.js +531 -0
  78. codeagent/web/static/01c-session-utilities.js +83 -0
  79. codeagent/web/static/01c-vision-model.js +132 -0
  80. codeagent/web/static/01d-image-gen-model.js +84 -0
  81. codeagent/web/static/01e-audio-model.js +97 -0
  82. codeagent/web/static/01f-compose-ui.js +88 -0
  83. codeagent/web/static/01g-tts-settings.js +128 -0
  84. codeagent/web/static/01h-music-model.js +85 -0
  85. codeagent/web/static/01i-model-stack.js +291 -0
  86. codeagent/web/static/01j-video-gen-model.js +85 -0
  87. codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
  88. codeagent/web/static/01k-model-stack-state.js +209 -0
  89. codeagent/web/static/01q-inflight.js +195 -0
  90. codeagent/web/static/02-reset.css +22 -0
  91. codeagent/web/static/02-session-ui.js +201 -0
  92. codeagent/web/static/03-layout-p1.css +2 -0
  93. codeagent/web/static/03-layout-p2.css +2 -0
  94. codeagent/web/static/03a-links-stream.js +88 -0
  95. codeagent/web/static/03b-stream-text.js +119 -0
  96. codeagent/web/static/03c-render-bubble.js +520 -0
  97. codeagent/web/static/03d-agent-bubble.js +126 -0
  98. codeagent/web/static/03e-tool-trace.js +182 -0
  99. codeagent/web/static/03f-timeline.js +54 -0
  100. codeagent/web/static/03g-bubble-tts.js +292 -0
  101. codeagent/web/static/04-components-p1.css +1 -0
  102. codeagent/web/static/04-components-p2b.css +1 -0
  103. codeagent/web/static/04-components-p3.css +2 -0
  104. codeagent/web/static/04-components-p4.css +1 -0
  105. codeagent/web/static/04-components-p5.css +1 -0
  106. codeagent/web/static/04-components-p6.css +58 -0
  107. codeagent/web/static/04-components-p7.css +4 -0
  108. codeagent/web/static/04-components-p8.css +1 -0
  109. codeagent/web/static/04-components-p9.css +1 -0
  110. codeagent/web/static/04-ws-connect.js +351 -0
  111. codeagent/web/static/04-ws-helpers.js +116 -0
  112. codeagent/web/static/05-chat-p1.css +1058 -0
  113. codeagent/web/static/05-chat-p2.css +1 -0
  114. codeagent/web/static/05-chat-p3.css +295 -0
  115. codeagent/web/static/05a-session-history.js +176 -0
  116. codeagent/web/static/05b-session-list.js +114 -0
  117. codeagent/web/static/05c-dialog.js +123 -0
  118. codeagent/web/static/05d-project-create.js +172 -0
  119. codeagent/web/static/05e-project-remote.js +143 -0
  120. codeagent/web/static/06-chat.js +552 -0
  121. codeagent/web/static/06-utilities.css +181 -0
  122. codeagent/web/static/06b-attachments.js +304 -0
  123. codeagent/web/static/06c-camera.js +488 -0
  124. codeagent/web/static/07-activity-bar.css +127 -0
  125. codeagent/web/static/09-git-p1.css +1 -0
  126. codeagent/web/static/09-git-p2.css +1 -0
  127. codeagent/web/static/09a-init-bind.js +110 -0
  128. codeagent/web/static/09b-panel-toggle.js +80 -0
  129. codeagent/web/static/09c-api-refresh.js +100 -0
  130. codeagent/web/static/09d-status-log.js +80 -0
  131. codeagent/web/static/09e-commit.js +80 -0
  132. codeagent/web/static/09f-remote.js +93 -0
  133. codeagent/web/static/11a-cron-parse.js +117 -0
  134. codeagent/web/static/11b-cron-panel.js +100 -0
  135. codeagent/web/static/11c-cron-card.js +133 -0
  136. codeagent/web/static/11d-cron-form.js +105 -0
  137. codeagent/web/static/11e-env-config.js +168 -0
  138. codeagent/web/static/11f-env-llm-a-state.js +136 -0
  139. codeagent/web/static/11f-env-llm-b-form.js +409 -0
  140. codeagent/web/static/11f-env-llm-c-board.js +322 -0
  141. codeagent/web/static/11g-env-paths-git.js +973 -0
  142. codeagent/web/static/11h-env-mcp-a-state.js +161 -0
  143. codeagent/web/static/11h-env-mcp-b-form.js +229 -0
  144. codeagent/web/static/11h-env-mcp-c-board.js +565 -0
  145. codeagent/web/static/12-plugins.js +127 -0
  146. codeagent/web/static/13a-skills.js +156 -0
  147. codeagent/web/static/13a-todo-utils.js +140 -0
  148. codeagent/web/static/13b-todo-fetch.js +106 -0
  149. codeagent/web/static/13b-tools.js +127 -0
  150. codeagent/web/static/13c-todo-crud.js +92 -0
  151. codeagent/web/static/14-activity-bar.js +177 -0
  152. codeagent/web/static/15-plans.css +194 -0
  153. codeagent/web/static/15-plans.js +219 -0
  154. codeagent/web/static/16-mobile.css +612 -0
  155. codeagent/web/static/16-mobile.js +178 -0
  156. codeagent/web/static/17-preset-ux.css +287 -0
  157. codeagent/web/static/18-agent-mgr.css +616 -0
  158. codeagent/web/static/18-agent-mgr.js +204 -0
  159. codeagent/web/static/24-health-dashboard.js +68 -0
  160. codeagent/web/static/99-brand-polish.css +152 -0
  161. codeagent/web/static/body.html +824 -0
  162. codeagent/web/static/setup/setup_form.html +171 -0
  163. codeagent/web/static/setup/setup_head.html +100 -0
  164. codeagent/web/static/setup/setup_tail.html +264 -0
  165. codeagent/web/static/setup/setup_test.html +38 -0
  166. codeagent/web/web_login.html +265 -0
  167. codeagent/web/webui.html +35 -0
  168. tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
  169. tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
  170. tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
  171. tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
  172. tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
  173. tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,177 @@
1
+ var _activeMode = null;
2
+ var _FULL_WIDTH_MODES = ['config', 'tasks', 'agent'];
3
+
4
+ function _applySidebarForMode(mode) {
5
+ var chatSidebar = document.getElementById('chatSidebar');
6
+ var outerSidebar = document.querySelector('.app > aside.sidebar');
7
+ if (outerSidebar) outerSidebar.style.removeProperty('display');
8
+
9
+ if (mode === 'files') {
10
+ if (chatSidebar) chatSidebar.style.display = 'none';
11
+ return;
12
+ }
13
+ if (_FULL_WIDTH_MODES.indexOf(mode) >= 0) {
14
+ if (chatSidebar) chatSidebar.style.display = 'none';
15
+ return;
16
+ }
17
+ if (chatSidebar) {
18
+ if (typeof webuiSessionsEnabled !== 'undefined' && webuiSessionsEnabled) {
19
+ chatSidebar.style.display = 'flex';
20
+ } else {
21
+ chatSidebar.style.display = 'none';
22
+ }
23
+ }
24
+ }
25
+
26
+ function _syncWorkspacePages(pageId) {
27
+ if (typeof switchToPage === 'function') switchToPage(pageId);
28
+ }
29
+
30
+ function _syncFileBtnState() {
31
+ var fileBtn = document.getElementById('btnToggleFiles');
32
+ if (!fileBtn) return;
33
+ if (_activeMode === 'files') {
34
+ fileBtn.classList.add('is-active');
35
+ } else {
36
+ fileBtn.classList.remove('is-active');
37
+ }
38
+ }
39
+
40
+ function switchActivityMode(mode) {
41
+ // git 模式不走页面切换,由 09-git.js 的点击拦截处理
42
+ if (mode === 'git') return;
43
+
44
+ if (!mode) return;
45
+ if (mode === _activeMode) {
46
+ try { document.body.setAttribute('data-activity-mode', mode); } catch (_) {}
47
+ _syncWorkspacePages(mode);
48
+ return;
49
+ }
50
+ _activeMode = mode;
51
+ try { document.body.setAttribute('data-activity-mode', mode); } catch (_) {}
52
+
53
+ document.querySelectorAll('.activity-btn').forEach(function(b) {
54
+ if (b.id === 'btnLogout') return;
55
+ b.classList.toggle('active', b.getAttribute('data-mode') === mode);
56
+ });
57
+
58
+ var filesSection = document.getElementById('sidebarFiles');
59
+ var gitSidebarSection = document.getElementById('sidebarGit');
60
+
61
+ function hideSidebarModes() {
62
+ if (filesSection) filesSection.style.display = 'none';
63
+ if (gitSidebarSection) gitSidebarSection.style.display = 'none';
64
+ }
65
+
66
+ if (mode === 'files') {
67
+ hideSidebarModes();
68
+ if (filesSection) filesSection.style.display = 'flex';
69
+ _applySidebarForMode(mode);
70
+ if (typeof switchToPage === 'function') switchToPage('files');
71
+ else _syncWorkspacePages('files');
72
+ if (typeof activatePage === 'function') activatePage('files');
73
+ setTimeout(function() {
74
+ if (typeof window.webuiRefreshFileTreeIfVisible === 'function') {
75
+ window.webuiRefreshFileTreeIfVisible();
76
+ }
77
+ }, 0);
78
+ } else {
79
+ hideSidebarModes();
80
+ _applySidebarForMode(mode);
81
+ var pageId = mode;
82
+ _syncWorkspacePages(pageId);
83
+ if (typeof switchToPage === 'function') switchToPage(pageId);
84
+ if (typeof activatePage === 'function') activatePage(pageId);
85
+ }
86
+
87
+ if (mode === 'chat') {
88
+ if (typeof refreshProjects === 'function') setTimeout(function() { refreshProjects(false); }, 100);
89
+ }
90
+ trySetLS(STORAGE_KEYS.SESS_ACTIVITY_MODE, mode);
91
+ if (typeof window.webuiMobileSyncMode === 'function') window.webuiMobileSyncMode(mode);
92
+ syncTopbarChatShortcutUi();
93
+ _syncFileBtnState();
94
+ }
95
+
96
+ function syncTopbarChatShortcutUi() {
97
+ var grp = document.querySelector('.topbar__title-group');
98
+ if (!grp) return;
99
+ if (_activeMode && _activeMode !== 'chat') {
100
+ grp.style.cursor = 'pointer';
101
+ grp.title = '返回聊天';
102
+ } else {
103
+ grp.style.cursor = '';
104
+ grp.title = '';
105
+ }
106
+ }
107
+
108
+ function restoreActivityMode() {
109
+ try {
110
+ var saved = tryGetLS(STORAGE_KEYS.SESS_ACTIVITY_MODE);
111
+ if (saved && ['chat', 'tasks', 'agent', 'config', 'files'].indexOf(saved) >= 0) {
112
+ _activeMode = null;
113
+ switchActivityMode(saved);
114
+ return;
115
+ }
116
+ } catch (_) {}
117
+ _activeMode = null;
118
+ switchActivityMode('chat');
119
+ }
120
+
121
+ ;(function initActivityBar() {
122
+ document.querySelectorAll('.activity-btn').forEach(function(btn) {
123
+ // 手机视图下用 touchstart 捕获,避免键盘收起时 viewport 变化吞掉 click
124
+ btn.addEventListener('touchstart', function(e) {
125
+ var isMobile = window.matchMedia('(max-width: 768px)').matches;
126
+ if (!isMobile) return;
127
+ var mode = this.getAttribute('data-mode');
128
+ if (!mode) return;
129
+ // 键盘弹出时:在 touchstart 阶段 blur 并切换,不等 click
130
+ if (document.activeElement && document.activeElement.id === 'msg') {
131
+ document.activeElement.blur();
132
+ switchActivityMode(mode);
133
+ e.preventDefault();
134
+ }
135
+ }, { passive: false });
136
+ btn.addEventListener('click', function() {
137
+ var mode = this.getAttribute('data-mode');
138
+ if (mode) switchActivityMode(mode);
139
+ });
140
+ });
141
+ var titleGrp = document.querySelector('.topbar__title-group');
142
+ if (titleGrp && titleGrp.dataset.chatNavBound !== '1') {
143
+ titleGrp.dataset.chatNavBound = '1';
144
+ titleGrp.addEventListener('click', function() {
145
+ if (_activeMode != null && _activeMode !== 'chat') {
146
+ switchActivityMode('chat');
147
+ }
148
+ });
149
+ }
150
+ restoreActivityMode();
151
+
152
+ // ======== 文件按钮(topbar 切换) ========
153
+ var fileBtn = document.getElementById('btnToggleFiles');
154
+ if (fileBtn) {
155
+ _syncFileBtnState();
156
+
157
+ fileBtn.addEventListener('click', function() {
158
+ if (_activeMode === 'files') {
159
+ switchActivityMode('chat');
160
+ } else {
161
+ // 关闭顶层面板:Plan/Todo/Git
162
+ ['planPanel','todoPanel','gitPanel'].forEach(function(id) {
163
+ var p = document.getElementById(id);
164
+ if (p && p.style.display !== 'none') {
165
+ p.style.display = 'none';
166
+ try { localStorage.setItem('oa_' + id.replace('Panel','').toLowerCase() + '_panel_open', '0'); } catch (_) {}
167
+ }
168
+ });
169
+ ['btnTogglePlans','btnToggleTodos','btnToggleGit'].forEach(function(id) {
170
+ var b = document.getElementById(id);
171
+ if (b) b.classList.remove('is-active');
172
+ });
173
+ switchActivityMode('files');
174
+ }
175
+ });
176
+ }
177
+ })();
@@ -0,0 +1,194 @@
1
+ /* ================================================================
2
+ * 15-plans.css — Plan 面板样式
3
+ * 与 todo-panel 视觉一致,保持统一
4
+ * ================================================================ */
5
+
6
+ /* ---- Plan panel (right sidebar, like todo panel) ---- */
7
+
8
+ .plan-panel {
9
+ position: absolute;
10
+ top: var(--topbar-h, 44px);
11
+ right: 0;
12
+ bottom: 0;
13
+ width: 400px;
14
+ max-width: 92vw;
15
+ background: var(--bg-panel);
16
+ border-left: 1px solid var(--border);
17
+ display: none;
18
+ flex-direction: column;
19
+ z-index: 50;
20
+ box-shadow: -4px 0 16px rgba(0,0,0,0.06);
21
+ overflow: hidden;
22
+ }
23
+
24
+ .plan-panel__header {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ padding: var(--sp-3) var(--sp-4);
29
+ font-size: 13px;
30
+ font-weight: 600;
31
+ color: var(--text);
32
+ border-bottom: 1px solid var(--border);
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ .plan-panel__list {
37
+ flex: 1;
38
+ overflow-y: auto;
39
+ padding: var(--sp-2) 0;
40
+ scrollbar-width: thin;
41
+ scrollbar-color: var(--border-strong) transparent;
42
+ }
43
+ .plan-panel__list::-webkit-scrollbar { width: 8px; }
44
+ .plan-panel__list::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
45
+
46
+ .plan-panel__footer {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ padding: var(--sp-2) var(--sp-4);
51
+ border-top: 1px solid var(--border);
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ /* ---- Plan card ---- */
56
+
57
+ .plan-card {
58
+ margin: 0 var(--sp-2) var(--sp-1);
59
+ border: 1px solid var(--border);
60
+ border-radius: var(--r-sm, 6px);
61
+ background: var(--bg);
62
+ overflow: hidden;
63
+ transition: border-color 0.15s;
64
+ }
65
+ .plan-card:hover {
66
+ border-color: var(--border-strong);
67
+ }
68
+
69
+ .plan-card__header {
70
+ display: flex;
71
+ align-items: center;
72
+ padding: var(--sp-2) var(--sp-3);
73
+ cursor: pointer;
74
+ user-select: none;
75
+ gap: var(--sp-2);
76
+ }
77
+
78
+ .plan-card__icon {
79
+ flex-shrink: 0;
80
+ width: 18px;
81
+ height: 18px;
82
+ color: var(--text-muted);
83
+ opacity: 0.7;
84
+ }
85
+
86
+ .plan-card__info {
87
+ flex: 1;
88
+ min-width: 0;
89
+ }
90
+
91
+ .plan-card__name {
92
+ font-size: 13px;
93
+ font-weight: 500;
94
+ color: var(--text);
95
+ white-space: nowrap;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ }
99
+
100
+ /* icon 由 JS 中的 .plan-card__icon 提供 */
101
+
102
+ .plan-card__meta {
103
+ font-size: 11px;
104
+ color: var(--text-muted);
105
+ margin-top: 1px;
106
+ }
107
+
108
+ .plan-card__arrow {
109
+ flex-shrink: 0;
110
+ width: 14px;
111
+ height: 14px;
112
+ color: var(--text-muted);
113
+ transition: transform 0.2s;
114
+ }
115
+ .plan-card__arrow.is-open {
116
+ transform: rotate(90deg);
117
+ }
118
+
119
+ /* ---- Plan card expanded body ---- */
120
+
121
+ .plan-card__body-wrap {
122
+ display: none;
123
+ border-top: 1px solid var(--border);
124
+ background: var(--bg-subtle, #fafafa);
125
+ }
126
+ .plan-card__body-wrap.is-open {
127
+ display: block;
128
+ }
129
+
130
+ .plan-card__body {
131
+ padding: var(--sp-3);
132
+ font-size: 13px;
133
+ line-height: 1.6;
134
+ color: var(--text);
135
+ max-height: 60vh;
136
+ overflow-y: auto;
137
+ white-space: pre-wrap;
138
+ word-break: break-word;
139
+ font-family: var(--font-mono, monospace);
140
+ }
141
+
142
+ .plan-card__body h1,
143
+ .plan-card__body h2,
144
+ .plan-card__body h3 {
145
+ font-family: var(--font-sans, system-ui, sans-serif);
146
+ margin: 0.5em 0 0.25em;
147
+ font-weight: 600;
148
+ }
149
+ .plan-card__body h1 { font-size: 15px; }
150
+ .plan-card__body h2 { font-size: 14px; }
151
+ .plan-card__body h3 { font-size: 13px; }
152
+
153
+ .plan-card__body ul,
154
+ .plan-card__body ol {
155
+ padding-left: 1.5em;
156
+ margin: 0.3em 0;
157
+ }
158
+
159
+ .plan-card__body code {
160
+ background: var(--bg-hover);
161
+ padding: 1px 4px;
162
+ border-radius: 3px;
163
+ font-size: 0.92em;
164
+ }
165
+
166
+ .plan-card__body pre {
167
+ background: var(--bg-hover);
168
+ padding: var(--sp-2);
169
+ border-radius: var(--r-sm, 4px);
170
+ overflow-x: auto;
171
+ font-size: 12px;
172
+ line-height: 1.5;
173
+ }
174
+
175
+ /* ---- Empty state ---- */
176
+
177
+ .plan-empty {
178
+ text-align: center;
179
+ padding: var(--sp-6) var(--sp-4);
180
+ color: var(--text-muted);
181
+ font-size: 13px;
182
+ line-height: 1.8;
183
+ }
184
+
185
+ /* ---- Group headers ---- */
186
+
187
+ .plan-group-header {
188
+ padding: var(--sp-2) var(--sp-4) var(--sp-1);
189
+ font-size: 12px;
190
+ font-weight: 600;
191
+ color: var(--text-muted);
192
+ letter-spacing: 0.02em;
193
+ text-transform: uppercase;
194
+ }
@@ -0,0 +1,219 @@
1
+ /* ================================================================
2
+ * 15-plans.js — Plan 面板 UI
3
+ * - 与 todo-panel 互斥(打开一个自动关闭另一个)
4
+ * - 显示当前项目的 *-plan.md 文件列表
5
+ * - 点击展开/收起内容预览
6
+ * - 监听 project-changed 事件自动刷新
7
+ * ================================================================ */
8
+
9
+ (function() {
10
+
11
+ const btnToggle = document.getElementById('btnTogglePlans');
12
+ const planPanel = document.getElementById('planPanel');
13
+ const planList = document.getElementById('planList');
14
+ const planStatus = document.getElementById('planStatus');
15
+ const btnRefresh = document.getElementById('btnPlanRefresh');
16
+
17
+ if (!btnToggle || !planPanel || !planList) {
18
+ return;
19
+ }
20
+
21
+ // ---- 生命周期控制 ---- //
22
+
23
+ // 用实际 DOM display 状态判断,不依赖内部布尔变量。
24
+ // 这样即使被对方面板互斥关闭,下次点击也能正确打开。
25
+ function planIsVisible() {
26
+ return planPanel.style.display === 'flex';
27
+ }
28
+
29
+ try {
30
+ if (tryGetLS(STORAGE_KEYS.PLAN_PANEL_OPEN) === '1') {
31
+ // 初始化互斥:如果待办已打开,不覆盖
32
+ var todoPanel = document.getElementById('todoPanel');
33
+ if (todoPanel && todoPanel.style.display === 'flex') {
34
+ // 待办已开 → 忽略 localStorage,自己不打开
35
+ } else {
36
+ planPanel.style.display = 'flex';
37
+ btnToggle.classList.add('is-active');
38
+ setTimeout(refreshPlans, 150);
39
+ }
40
+ }
41
+ } catch (_) {}
42
+
43
+ // ---- Toggle: 打开 Plan 面板时关闭 Todo 面板(互斥) ---- //
44
+
45
+ btnToggle.addEventListener('click', function() {
46
+ var opening = !planIsVisible();
47
+ planPanel.style.display = opening ? 'flex' : 'none';
48
+ btnToggle.classList.toggle('is-active', opening);
49
+ trySetLS(STORAGE_KEYS.PLAN_PANEL_OPEN, opening ? '1' : '0');
50
+
51
+ // 互斥:打开 Plan 时自动关闭 Todo
52
+ if (opening) {
53
+ var todoP = document.getElementById('todoPanel');
54
+ var todoB = document.getElementById('btnToggleTodos');
55
+ if (todoP && todoP.style.display !== 'none') {
56
+ todoP.style.display = 'none';
57
+ if (todoB) todoB.classList.remove('is-active');
58
+ trySetLS(STORAGE_KEYS.TODO_PANEL_OPEN, '0');
59
+ }
60
+ refreshPlans();
61
+ }
62
+ });
63
+
64
+ if (btnRefresh) {
65
+ btnRefresh.addEventListener('click', refreshPlans);
66
+ }
67
+
68
+ // ---- 监听项目切换 ---- //
69
+
70
+ document.addEventListener('project-changed', function() {
71
+ if (planIsVisible()) refreshPlans();
72
+ });
73
+
74
+ // ---- API ---- //
75
+
76
+ async function fetchPlans() {
77
+ var pid = typeof projectId !== 'undefined' ? (projectId || '') : '';
78
+ if (!pid) return [];
79
+ try {
80
+ var r = await fetch('/api/ui/projects/plans?project_id=' + encodeURIComponent(pid));
81
+ if (!r.ok) return [];
82
+ var j = await r.json();
83
+ return j.plans || [];
84
+ } catch (_) { return []; }
85
+ }
86
+
87
+ // ---- 渲染 ---- //
88
+
89
+ var SOURCE_LABELS = {
90
+ docs: { icon: '\uD83D\uDCC4', label: '项目文档' }, // 📄
91
+ plans: { icon: '\uD83D\uDCCB', label: '工作计划' }, // 📋
92
+ project_plans: { icon: '\uD83D\uDCC1', label: '项目计划' }, // 📁
93
+ rules: { icon: '\u2699\uFE0F', label: '项目规则' }, // ⚙️
94
+ };
95
+ var SOURCE_ORDER = ['rules', 'docs', 'plans', 'project_plans'];
96
+
97
+ async function refreshPlans() {
98
+ var plans = await fetchPlans();
99
+ if (!planList) return;
100
+ planList.innerHTML = '';
101
+
102
+ if (!plans || plans.length === 0) {
103
+ planList.innerHTML = '<div class="plan-empty">当前项目暂无规划文档。<br/>Agent 在规划项目时会自动在 <code>docs/</code> 生成 <code>requirement.md / design.md / task.md</code>,<br/>在 <code>plans/</code> 生成 <code>*-plan.md</code>。<br/>你也可在 <code>.codeagent/rules.md</code> 中写入项目级规则,自动生效。</div>';
104
+ if (planStatus) planStatus.textContent = '';
105
+ return;
106
+ }
107
+
108
+ if (planStatus) planStatus.textContent = plans.length + ' 个文件';
109
+
110
+ // 按 source 分组
111
+ var groups = {};
112
+ plans.forEach(function(p) {
113
+ var src = p.source || 'project_plans';
114
+ if (!groups[src]) groups[src] = [];
115
+ groups[src].push(p);
116
+ });
117
+
118
+ // 按 SOURCE_ORDER 顺序渲染,每组的文件按修改时间倒序
119
+ SOURCE_ORDER.forEach(function(src) {
120
+ var items = groups[src];
121
+ if (!items || items.length === 0) return;
122
+
123
+ items.sort(function(a, b) { return (b.modified_at || 0) - (a.modified_at || 0); });
124
+
125
+ var label = SOURCE_LABELS[src] || { icon: '\uD83D\uDCC4', label: src };
126
+ var groupHeader = document.createElement('div');
127
+ groupHeader.className = 'plan-group-header';
128
+ groupHeader.textContent = label.icon + ' ' + label.label + ' (' + items.length + ')';
129
+ planList.appendChild(groupHeader);
130
+
131
+ items.forEach(function(p) {
132
+ planList.appendChild(buildPlanCard(p));
133
+ });
134
+ });
135
+ }
136
+
137
+ function buildPlanCard(plan) {
138
+ var wrap = document.createElement('div');
139
+ wrap.className = 'plan-card';
140
+
141
+ // ---- Header (clickable) ---- //
142
+ var header = document.createElement('div');
143
+ header.className = 'plan-card__header';
144
+
145
+ var icon = document.createElement('span');
146
+ icon.className = 'plan-card__icon';
147
+ icon.textContent = '📄';
148
+ icon.style.fontSize = '16px';
149
+
150
+ var info = document.createElement('div');
151
+ info.className = 'plan-card__info';
152
+
153
+ var nameEl = document.createElement('div');
154
+ nameEl.className = 'plan-card__name';
155
+ nameEl.textContent = plan.name;
156
+
157
+ var meta = document.createElement('div');
158
+ meta.className = 'plan-card__meta';
159
+ meta.textContent = formatRelativeTime(plan.modified_at) + (plan.size ? ' · ' + formatSize(plan.size) : '');
160
+
161
+ info.appendChild(nameEl);
162
+ info.appendChild(meta);
163
+
164
+ var arrow = document.createElement('span');
165
+ arrow.className = 'plan-card__arrow';
166
+ arrow.textContent = '\u25B6'; // ▶
167
+
168
+ header.appendChild(icon);
169
+ header.appendChild(info);
170
+ header.appendChild(arrow);
171
+ wrap.appendChild(header);
172
+
173
+ // ---- Body (collapsible) ---- //
174
+ var bodyWrap = document.createElement('div');
175
+ bodyWrap.className = 'plan-card__body-wrap';
176
+
177
+ var body = document.createElement('div');
178
+ body.className = 'plan-card__body';
179
+ body.textContent = plan.content || '';
180
+ bodyWrap.appendChild(body);
181
+ wrap.appendChild(bodyWrap);
182
+
183
+ // ---- Toggle ---- //
184
+ var isOpen = false;
185
+ header.addEventListener('click', function() {
186
+ isOpen = !isOpen;
187
+ bodyWrap.classList.toggle('is-open', isOpen);
188
+ arrow.classList.toggle('is-open', isOpen);
189
+ });
190
+
191
+ return wrap;
192
+ }
193
+
194
+ // ---- 工具函数 ---- //
195
+
196
+ function formatRelativeTime(epochSec) {
197
+ if (!epochSec) return '';
198
+ var diffMs = Date.now() - (epochSec * 1000);
199
+ var diffSec = Math.floor(diffMs / 1000);
200
+ if (diffSec < 10) return '刚刚';
201
+ if (diffSec < 60) return diffSec + '秒前';
202
+ var diffMin = Math.floor(diffSec / 60);
203
+ if (diffMin < 60) return diffMin + '分钟前';
204
+ var diffHr = Math.floor(diffMin / 60);
205
+ if (diffHr < 24) return diffHr + '小时前';
206
+ var diffDay = Math.floor(diffHr / 24);
207
+ if (diffDay < 30) return diffDay + '天前';
208
+ var d = new Date(epochSec * 1000);
209
+ return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
210
+ }
211
+
212
+ function formatSize(bytes) {
213
+ if (!bytes) return '';
214
+ if (bytes < 1024) return bytes + 'B';
215
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
216
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
217
+ }
218
+
219
+ })();