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,488 @@
1
+ /* Real-time camera: getUserMedia preview, single capture + interval frames + MediaRecorder video */
2
+
3
+ (function initCameraUi() {
4
+ const cameraBtn = document.getElementById('cameraBtn');
5
+ const nativeInput = document.getElementById('nativeCameraInput');
6
+ const modal = document.getElementById('cameraModal');
7
+ const closeBtn = document.getElementById('cameraModalClose');
8
+ const doneBtn = document.getElementById('cameraModalDone');
9
+ const doneBtnVideo = document.getElementById('cameraModalDoneVideo');
10
+ const captureBtn = document.getElementById('cameraCaptureBtn');
11
+ const intervalBtn = document.getElementById('cameraToggleInterval');
12
+ const recordBtn = document.getElementById('cameraRecordBtn');
13
+ const video = document.getElementById('cameraPreview');
14
+ const canvas = document.getElementById('cameraCanvas');
15
+ const statusEl = document.getElementById('cameraStatus');
16
+ const modeBar = document.getElementById('cameraModeBar');
17
+ const footerPhoto = document.getElementById('cameraFooterPhoto');
18
+ const footerVideo = document.getElementById('cameraFooterVideo');
19
+ const modalTitle = document.getElementById('cameraModalTitle');
20
+ const recIndicator = document.getElementById('cameraRecIndicator');
21
+ const recTimer = document.getElementById('cameraRecTimer');
22
+
23
+ if (!modal) return;
24
+
25
+ // --- State ---
26
+ let stream = null;
27
+ let intervalId = null;
28
+ let captureCount = 0;
29
+ const MAX_INTERVAL_CAPTURES = 8;
30
+ const INTERVAL_MS = 3000;
31
+
32
+ // Recording state
33
+ let mediaRecorder = null;
34
+ let recordedBlobs = [];
35
+ let recordStartTs = 0;
36
+ let recordTimerId = null;
37
+ let isRecording = false;
38
+ let currentMode = 'photo'; // 'photo' | 'video'
39
+ // 'user' = front, 'environment' = back — prefer front first (desktop / laptop)
40
+ let currentFacing = 'user';
41
+ let modeLocked = false; // true when opened from "+" menu (photo vs video)
42
+
43
+ function isPreviewMirrored() {
44
+ return currentFacing === 'user';
45
+ }
46
+
47
+ function updatePreviewMirror() {
48
+ if (!video) return;
49
+ video.classList.toggle('camera-modal__video--mirror', isPreviewMirrored());
50
+ }
51
+
52
+ function syncFacingFromStream() {
53
+ if (!stream) return;
54
+ const track = stream.getVideoTracks()[0];
55
+ if (!track || typeof track.getSettings !== 'function') return;
56
+ try {
57
+ const fm = track.getSettings().facingMode;
58
+ if (fm === 'user' || fm === 'environment') currentFacing = fm;
59
+ } catch (_) {}
60
+ updatePreviewMirror();
61
+ }
62
+
63
+ // --- Helpers ---
64
+ function setStatus(text) {
65
+ if (statusEl) statusEl.textContent = text || '';
66
+ }
67
+
68
+ function visionReady() {
69
+ return typeof attachmentImageReady === 'function' && attachmentImageReady();
70
+ }
71
+
72
+ function videoModelReady() {
73
+ return typeof attachmentVideoReady === 'function' && attachmentVideoReady();
74
+ }
75
+
76
+ function stopInterval() {
77
+ if (intervalId) {
78
+ clearInterval(intervalId);
79
+ intervalId = null;
80
+ }
81
+ if (intervalBtn) {
82
+ intervalBtn.classList.remove('is-active');
83
+ intervalBtn.textContent = '定时截帧';
84
+ }
85
+ }
86
+
87
+ function stopStream() {
88
+ stopInterval();
89
+ stopRecording(false);
90
+ if (stream) {
91
+ stream.getTracks().forEach(function(t) { t.stop(); });
92
+ stream = null;
93
+ }
94
+ if (video) video.srcObject = null;
95
+ }
96
+
97
+ function closeModal() {
98
+ stopStream();
99
+ captureCount = 0;
100
+ if (recIndicator) recIndicator.hidden = true;
101
+ if (recordBtn) {
102
+ recordBtn.textContent = '开始录制';
103
+ recordBtn.classList.remove('is-recording');
104
+ }
105
+ modal.style.display = 'none';
106
+ setStatus('');
107
+ }
108
+
109
+ // --- Photo capture ---
110
+ async function captureFrame() {
111
+ if (!video || !canvas || !video.videoWidth) {
112
+ systemMsg('err', '摄像头未就绪');
113
+ return false;
114
+ }
115
+ canvas.width = video.videoWidth;
116
+ canvas.height = video.videoHeight;
117
+ const ctx = canvas.getContext('2d');
118
+ if (!ctx) return false;
119
+ const mirror = isPreviewMirrored();
120
+ if (mirror) {
121
+ ctx.translate(canvas.width, 0);
122
+ ctx.scale(-1, 1);
123
+ }
124
+ ctx.drawImage(video, 0, 0);
125
+ if (mirror) ctx.setTransform(1, 0, 0, 1, 0, 0);
126
+ return new Promise(function(resolve) {
127
+ canvas.toBlob(function(blob) {
128
+ if (!blob) {
129
+ resolve(false);
130
+ return;
131
+ }
132
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
133
+ const file = new File([blob], 'camera-' + ts + '.jpg', { type: 'image/jpeg' });
134
+ if (typeof stageFiles === 'function') {
135
+ stageFiles([file]).then(function() {
136
+ captureCount += 1;
137
+ setStatus('已捕获 ' + captureCount + ' 张');
138
+ resolve(true);
139
+ }).catch(function() { resolve(false); });
140
+ } else {
141
+ resolve(false);
142
+ }
143
+ }, 'image/jpeg', 0.92);
144
+ });
145
+ }
146
+
147
+ function toggleIntervalCapture() {
148
+ if (intervalId) {
149
+ stopInterval();
150
+ setStatus('定时截帧已停止 · 共 ' + captureCount + ' 张');
151
+ return;
152
+ }
153
+ if (intervalBtn) {
154
+ intervalBtn.classList.add('is-active');
155
+ intervalBtn.textContent = '停止截帧';
156
+ }
157
+ setStatus('每 ' + (INTERVAL_MS / 1000) + ' 秒截帧(最多 ' + MAX_INTERVAL_CAPTURES + ' 张)');
158
+ intervalId = setInterval(function() {
159
+ if (captureCount >= MAX_INTERVAL_CAPTURES) {
160
+ stopInterval();
161
+ if (typeof systemMsg === 'function') systemMsg('info', '已达截帧上限 ' + MAX_INTERVAL_CAPTURES);
162
+ return;
163
+ }
164
+ captureFrame();
165
+ }, INTERVAL_MS);
166
+ }
167
+
168
+ // --- Video recording ---
169
+ function pickRecorderMime() {
170
+ const candidates = [
171
+ 'video/webm;codecs=vp9',
172
+ 'video/webm;codecs=vp8',
173
+ 'video/webm',
174
+ 'video/mp4',
175
+ ];
176
+ for (let i = 0; i < candidates.length; i++) {
177
+ if (typeof MediaRecorder !== 'undefined' &&
178
+ MediaRecorder.isTypeSupported &&
179
+ MediaRecorder.isTypeSupported(candidates[i])) {
180
+ return candidates[i];
181
+ }
182
+ }
183
+ return '';
184
+ }
185
+
186
+ function formatRecordTime(ms) {
187
+ const total = Math.floor(ms / 1000);
188
+ const m = Math.floor(total / 60);
189
+ const s = total % 60;
190
+ return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
191
+ }
192
+
193
+ function startRecording() {
194
+ if (!stream) {
195
+ systemMsg('err', '摄像头未就绪,无法录制');
196
+ return;
197
+ }
198
+ const mime = pickRecorderMime();
199
+ try {
200
+ mediaRecorder = mime ? new MediaRecorder(stream, { mimeType: mime })
201
+ : new MediaRecorder(stream);
202
+ } catch (e) {
203
+ systemMsg('err', '当前浏览器不支持视频录制: ' + (e.message || e));
204
+ return;
205
+ }
206
+ recordedBlobs = [];
207
+ mediaRecorder.ondataavailable = function(ev) {
208
+ if (ev.data && ev.data.size > 0) recordedBlobs.push(ev.data);
209
+ };
210
+ mediaRecorder.onstop = function() {
211
+ finalizeRecording();
212
+ };
213
+ mediaRecorder.onerror = function(ev) {
214
+ systemMsg('err', '录制出错: ' + (ev.error && ev.error.name ? ev.error.name : 'unknown'));
215
+ };
216
+ mediaRecorder.start(250); // collect a chunk every 250ms
217
+ isRecording = true;
218
+ recordStartTs = Date.now();
219
+ if (recIndicator) recIndicator.hidden = false;
220
+ if (recTimer) recTimer.textContent = '00:00';
221
+ if (recordBtn) {
222
+ recordBtn.textContent = '停止录制';
223
+ recordBtn.classList.add('is-recording');
224
+ }
225
+ setStatus('正在录制…点击「停止录制」结束');
226
+ recordTimerId = setInterval(function() {
227
+ if (recTimer) recTimer.textContent = formatRecordTime(Date.now() - recordStartTs);
228
+ }, 250);
229
+ }
230
+
231
+ function stopRecording(finish) {
232
+ if (recordTimerId) {
233
+ clearInterval(recordTimerId);
234
+ recordTimerId = null;
235
+ }
236
+ if (!isRecording) return;
237
+ isRecording = false;
238
+ if (recIndicator) recIndicator.hidden = true;
239
+ if (recordBtn) {
240
+ recordBtn.textContent = '开始录制';
241
+ recordBtn.classList.remove('is-recording');
242
+ }
243
+ if (mediaRecorder && mediaRecorder.state !== 'inactive') {
244
+ try {
245
+ mediaRecorder.stop();
246
+ } catch (_) {}
247
+ }
248
+ if (!finish) {
249
+ // forced stop without saving (e.g. stream was torn down)
250
+ recordedBlobs = [];
251
+ }
252
+ }
253
+
254
+ function finalizeRecording() {
255
+ if (!recordedBlobs.length) {
256
+ setStatus('录制已停止(无数据)');
257
+ return;
258
+ }
259
+ const mime = (mediaRecorder && mediaRecorder.mimeType) || 'video/webm';
260
+ const ext = mime.indexOf('mp4') >= 0 ? 'mp4' : 'webm';
261
+ const blob = new Blob(recordedBlobs, { type: mime });
262
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
263
+ const file = new File([blob], 'camera-' + ts + '.' + ext, { type: mime });
264
+ const dur = formatRecordTime(Date.now() - recordStartTs);
265
+ if (typeof stageFiles === 'function') {
266
+ stageFiles([file]).then(function() {
267
+ setStatus('已录制 ' + dur + ' · 已加入附件');
268
+ if (typeof systemMsg === 'function') systemMsg('info', '已附加录像 ' + dur);
269
+ }).catch(function() {
270
+ setStatus('录像已停止,但加入附件失败');
271
+ });
272
+ } else {
273
+ setStatus('录像已停止(无 stageFiles)');
274
+ }
275
+ recordedBlobs = [];
276
+ }
277
+
278
+ // --- Stream acquisition ---
279
+ // 4 级降级配置:requested facing → 切换 facing → 不带 facing → video:true
280
+ // 任一成功立即返回;全部失败时抛出第一次的错误(保留原行为)。
281
+ function _streamConstraintCandidates(facing) {
282
+ const alt = facing === 'user' ? 'environment' : 'user';
283
+ const audio = currentMode === 'video';
284
+ return [
285
+ { video: { facingMode: { ideal: facing }, width: { ideal: 1280 }, height: { ideal: 720 } }, audio },
286
+ { video: { facingMode: { ideal: alt }, width: { ideal: 1280 }, height: { ideal: 720 } }, audio },
287
+ { video: { width: { ideal: 1280 }, height: { ideal: 720 } }, audio },
288
+ { video: true, audio },
289
+ ];
290
+ }
291
+
292
+ async function tryGetStream(facing) {
293
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
294
+ throw new Error('浏览器不支持 getUserMedia');
295
+ }
296
+ const candidates = _streamConstraintCandidates(facing);
297
+ let firstErr = null;
298
+ for (const c of candidates) {
299
+ try {
300
+ return await navigator.mediaDevices.getUserMedia(c);
301
+ } catch (e) {
302
+ if (!firstErr) firstErr = e;
303
+ }
304
+ }
305
+ throw firstErr;
306
+ }
307
+
308
+ function hasMediaDevices() {
309
+ return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
310
+ }
311
+
312
+ function nativeInputForMode(mode) {
313
+ return (mode === 'video')
314
+ ? document.getElementById('nativeVideoInput')
315
+ : nativeInput;
316
+ }
317
+
318
+ async function attachPreviewStream(nextStream) {
319
+ stream = nextStream;
320
+ if (!video) return;
321
+ video.srcObject = stream;
322
+ video.muted = true;
323
+ syncFacingFromStream();
324
+ try { await video.play(); } catch (_) {}
325
+ }
326
+
327
+ async function openStream(forceNew) {
328
+ if (stream && !forceNew) return stream;
329
+ if (stream) {
330
+ stream.getTracks().forEach(function(t) { t.stop(); });
331
+ stream = null;
332
+ }
333
+ setStatus('正在打开摄像头…');
334
+ stream = await tryGetStream(currentFacing);
335
+ await attachPreviewStream(stream);
336
+ return stream;
337
+ }
338
+
339
+ function showStreamError(err) {
340
+ const msg = (err && (err.name || err.message))
341
+ ? (err.name + (err.message ? ': ' + err.message : ''))
342
+ : String(err || 'unknown');
343
+ setStatus('无法访问摄像头:' + msg + '。可点下方「从文件选择」,或改用 HTTPS / localhost 访问。');
344
+ if (typeof systemMsg === 'function') {
345
+ systemMsg('err', '无法访问摄像头:' + msg);
346
+ }
347
+ const fb = document.getElementById('cameraFallbackBtn');
348
+ if (fb) fb.hidden = false;
349
+ }
350
+
351
+ // --- Modal open ---
352
+ async function openCameraModal(mode, preStream) {
353
+ currentMode = (mode === 'video') ? 'video' : 'photo';
354
+ setModeUi(currentMode, false);
355
+
356
+ if (currentMode === 'photo' && !visionReady()) {
357
+ systemMsg('err', '请先配置识图(多模态 LLM 或 MiniMax MCP)');
358
+ }
359
+ if (currentMode === 'video' && !videoModelReady()) {
360
+ systemMsg('err', '请先配置视频识图模型');
361
+ }
362
+
363
+ captureCount = 0;
364
+ modal.style.display = 'flex';
365
+ const fb = document.getElementById('cameraFallbackBtn');
366
+ if (fb) fb.hidden = true;
367
+
368
+ try {
369
+ if (preStream) {
370
+ await attachPreviewStream(preStream);
371
+ } else {
372
+ await openStream(true);
373
+ }
374
+ setStatus(currentMode === 'video'
375
+ ? '点击「开始录制」开始录像'
376
+ : '对准目标后点击「拍照」,或开启定时截帧');
377
+ } catch (e) {
378
+ showStreamError(e);
379
+ }
380
+ }
381
+
382
+ /** Called synchronously from "+" menu click — opens modal first, then getUserMedia in the same gesture. */
383
+ function launchCameraCapture(mode) {
384
+ const m = (mode === 'video') ? 'video' : 'photo';
385
+
386
+ if (!hasMediaDevices()) {
387
+ const nativeEl = nativeInputForMode(m);
388
+ if (nativeEl) nativeEl.click();
389
+ return;
390
+ }
391
+
392
+ currentMode = m;
393
+ setModeUi(m, true);
394
+ captureCount = 0;
395
+ modal.style.display = 'flex';
396
+ const fb = document.getElementById('cameraFallbackBtn');
397
+ if (fb) fb.hidden = true;
398
+ setStatus('正在打开摄像头…');
399
+
400
+ tryGetStream(currentFacing).then(function(s) {
401
+ return attachPreviewStream(s).then(function() {
402
+ setStatus(m === 'video'
403
+ ? '点击「开始录制」开始录像'
404
+ : '对准目标后点击「拍照」,或开启定时截帧');
405
+ });
406
+ }).catch(function(err) {
407
+ showStreamError(err);
408
+ });
409
+ }
410
+
411
+ function setModeUi(mode, lockMode) {
412
+ currentMode = mode;
413
+ if (typeof lockMode === 'boolean') modeLocked = lockMode;
414
+ if (modeBar) modeBar.hidden = modeLocked;
415
+ if (modeBar) {
416
+ Array.from(modeBar.querySelectorAll('.camera-modal__mode-btn')).forEach(function(btn) {
417
+ const isActive = btn.getAttribute('data-mode') === mode;
418
+ btn.classList.toggle('is-active', isActive);
419
+ btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
420
+ });
421
+ }
422
+ if (footerPhoto) {
423
+ const showPhoto = mode === 'photo';
424
+ footerPhoto.hidden = !showPhoto;
425
+ footerPhoto.style.display = showPhoto ? 'flex' : 'none';
426
+ }
427
+ if (footerVideo) {
428
+ const showVideo = mode === 'video';
429
+ footerVideo.hidden = !showVideo;
430
+ footerVideo.style.display = showVideo ? 'flex' : 'none';
431
+ }
432
+ if (modalTitle) modalTitle.textContent = (mode === 'video') ? '录像' : '拍照';
433
+ }
434
+
435
+ // --- Expose to popup ---
436
+ window.openCameraModal = openCameraModal;
437
+ window.launchCameraCapture = launchCameraCapture;
438
+
439
+ const fallbackBtn = document.getElementById('cameraFallbackBtn');
440
+ fallbackBtn && fallbackBtn.addEventListener('click', function() {
441
+ const inp = nativeInputForMode(currentMode);
442
+ if (inp) inp.click();
443
+ });
444
+
445
+ // --- Bindings ---
446
+ captureBtn && captureBtn.addEventListener('click', function() { captureFrame(); });
447
+ intervalBtn && intervalBtn.addEventListener('click', function() { toggleIntervalCapture(); });
448
+
449
+ recordBtn && recordBtn.addEventListener('click', function() {
450
+ if (isRecording) stopRecording(true);
451
+ else startRecording();
452
+ });
453
+
454
+ // Mode switch — restart stream when audio requirement changes (photo vs video)
455
+ if (modeBar) {
456
+ modeBar.addEventListener('click', function(e) {
457
+ const btn = e.target.closest('.camera-modal__mode-btn');
458
+ if (!btn) return;
459
+ const m = btn.getAttribute('data-mode');
460
+ if (!m || m === currentMode) return;
461
+ if (isRecording) stopRecording(true);
462
+ const prev = currentMode;
463
+ setModeUi(m);
464
+ if (prev !== m && hasMediaDevices()) {
465
+ openStream(true).then(function() {
466
+ setStatus(m === 'video' ? '点击「开始录制」开始录像' : '对准目标后点击「拍照」');
467
+ }).catch(function(err) {
468
+ showStreamError(err);
469
+ });
470
+ } else {
471
+ setStatus(m === 'video' ? '点击「开始录制」开始录像' : '对准目标后点击「拍照」');
472
+ }
473
+ });
474
+ }
475
+
476
+ closeBtn && closeBtn.addEventListener('click', closeModal);
477
+ doneBtn && doneBtn.addEventListener('click', closeModal);
478
+ doneBtnVideo && doneBtnVideo.addEventListener('click', closeModal);
479
+
480
+ modal.addEventListener('click', function(e) {
481
+ if (e.target === modal) closeModal();
482
+ });
483
+
484
+ document.addEventListener('keydown', function(e) {
485
+ if (modal.style.display === 'none' || modal.style.display === '') return;
486
+ if (e.key === 'Escape') closeModal();
487
+ });
488
+ })();
@@ -0,0 +1,127 @@
1
+ .activity-bar {
2
+ width: var(--activity-bar-w, 48px);
3
+ min-width: var(--activity-bar-w, 48px);
4
+ height: 100vh;
5
+ display: flex;
6
+ flex-direction: column;
7
+ background: var(--bg-panel);
8
+ border-right: 1px solid var(--border);
9
+ flex-shrink: 0;
10
+ overflow: hidden;
11
+ z-index: 10;
12
+ }
13
+ .activity-bar__top {
14
+ flex: 1;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ gap: 2px;
19
+ padding: var(--sp-3) 0;
20
+ }
21
+ .activity-bar__bottom {
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ padding: var(--sp-2) 0;
26
+ border-top: 1px solid var(--divider);
27
+ }
28
+ .activity-btn {
29
+ width: 36px; height: 36px;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ border: none;
34
+ border-radius: var(--r-md);
35
+ background: transparent;
36
+ color: var(--text-muted);
37
+ cursor: pointer;
38
+ font-size: 16px;
39
+ line-height: 1;
40
+ position: relative;
41
+ transition: background 0.12s ease, color 0.12s ease;
42
+ }
43
+ .activity-btn svg { display: block; flex-shrink: 0; }
44
+ .activity-btn:hover { background: var(--bg-hover); color: var(--text); }
45
+ .activity-btn.active { color: var(--accent); background: var(--accent-soft); }
46
+ .activity-btn.active::before {
47
+ content: ''; position: absolute; left: -6px; top: 50%; transform: translateY(-50%);
48
+ width: 3px; height: 20px; border-radius: 0 3px 3px 0; background: var(--accent);
49
+ }
50
+
51
+ /* App shell grid: explicit columns so workspace stays in the main column when sidebar collapses */
52
+ .activity-bar {
53
+ grid-column: 1;
54
+ grid-row: 1;
55
+ }
56
+ .app > aside.sidebar {
57
+ grid-column: 2;
58
+ grid-row: 1;
59
+ }
60
+ .workspace {
61
+ grid-column: 3;
62
+ grid-row: 1;
63
+ position: relative;
64
+ }
65
+
66
+ /* workspace 内各 page 叠在同一区域,避免 flex 列里多页同时占位把配置挤到视口外 */
67
+ .workspace > .page {
68
+ position: absolute;
69
+ left: 0;
70
+ right: 0;
71
+ top: var(--topbar-h, 48px);
72
+ bottom: 0;
73
+ flex: none !important;
74
+ width: 100%;
75
+ box-sizing: border-box;
76
+ z-index: 0;
77
+ }
78
+ .workspace > .page.active {
79
+ z-index: 1;
80
+ }
81
+
82
+ /* 配置 / 计划 / Agent:全宽主内容,隐藏项目侧栏 */
83
+ body[data-activity-mode="config"] .app,
84
+ body[data-activity-mode="tasks"] .app,
85
+ body[data-activity-mode="agent"] .app,
86
+ body[data-activity-mode="team"] .app,
87
+ body[data-activity-mode="hub"] .app {
88
+ grid-template-columns: var(--activity-bar-w, 48px) 1fr;
89
+ }
90
+
91
+ body[data-activity-mode="config"] .workspace,
92
+ body[data-activity-mode="tasks"] .workspace,
93
+ body[data-activity-mode="agent"] .workspace,
94
+ body[data-activity-mode="team"] .workspace,
95
+ body[data-activity-mode="hub"] .workspace {
96
+ grid-column: 2;
97
+ }
98
+
99
+ body[data-activity-mode="config"] .app > aside.sidebar,
100
+ body[data-activity-mode="tasks"] .app > aside.sidebar,
101
+ body[data-activity-mode="agent"] .app > aside.sidebar,
102
+ body[data-activity-mode="team"] .app > aside.sidebar,
103
+ body[data-activity-mode="hub"] .app > aside.sidebar {
104
+ display: none !important;
105
+ }
106
+
107
+ /* 按活动模式强制显示对应 workspace 页面(避免多个 .page.active 叠在一起) */
108
+ body[data-activity-mode="chat"] .workspace > .page:not(#page-chat),
109
+ body[data-activity-mode="config"] .workspace > .page:not(#page-config),
110
+ body[data-activity-mode="tasks"] .workspace > .page:not(#page-tasks),
111
+ body[data-activity-mode="agent"] .workspace > .page:not(#page-agent),
112
+ body[data-activity-mode="files"] .workspace > .page:not(#page-files) {
113
+ display: none !important;
114
+ flex: 0 0 0 !important;
115
+ min-height: 0 !important;
116
+ overflow: hidden !important;
117
+ }
118
+
119
+ body[data-activity-mode="chat"] .workspace > #page-chat,
120
+ body[data-activity-mode="config"] .workspace > #page-config,
121
+ body[data-activity-mode="tasks"] .workspace > #page-tasks,
122
+ body[data-activity-mode="agent"] .workspace > #page-agent,
123
+ body[data-activity-mode="files"] .workspace > #page-files {
124
+ display: flex !important;
125
+ flex-direction: column;
126
+ min-height: 0 !important;
127
+ }
@@ -0,0 +1 @@
1
+ /* === Git 滑出面板 === */.git-slide-panel { position: absolute; top: var(--topbar-h, 44px); right: 0; bottom: 0; width: 420px; max-width: 92vw; background: var(--bg-panel); border-left: 1px solid var(--border); display: none; flex-direction: column; z-index: 50; box-shadow: -4px 0 16px rgba(0,0,0,0.06); overflow: hidden;}.git-slide-panel__header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-3) var(--sp-4); font-size: 13px; font-weight: 600; color: var(--text); border-bottom: 1px solid var(--border); flex-shrink: 0;}.git-slide-panel__commit { flex-shrink: 0; padding: 6px var(--sp-4); border-bottom: 1px solid var(--border);}/* ---- 远程仓库配置 ---- */.git-slide-panel__remote { flex-shrink: 0; padding: 4px var(--sp-4); border-bottom: 1px solid var(--border); font-size: 11px;}.git-slide-panel__remote-info { display: flex; align-items: center; gap: 6px; min-height: 24px;}.git-slide-panel__remote-form { display: flex; flex-direction: column; gap: 4px; padding: 6px 0;}.git-remote-input { background: var(--bg-input, #2d2d2d); color: var(--text, #ccc); border: 1px solid var(--border, #555); border-radius: 4px; padding: 4px 6px; font-size: 11px; font-family: inherit; box-sizing: border-box;}.git-remote-input:focus { outline: none; border-color: var(--accent, #0078d4);}/* ---- 提供商选择卡片 ---- */.git-provider-card { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border: 1px solid var(--border, #555); border-radius: 12px; font-size: 11px; cursor: pointer; background: transparent; color: var(--text-muted, #888); transition: all 0.12s; user-select: none;}.git-provider-card:hover { border-color: var(--accent, #0078d4); color: var(--text, #ccc); background: rgba(0,120,212,0.06);}.git-provider-card.is-active { border-color: var(--accent, #0078d4); background: rgba(0,120,212,0.12); color: var(--accent, #0078d4); font-weight: 500;}/* ---- SSH 密钥管理 ---- */.git-slide-panel__ssh { border-top: 1px solid var(--border, #333); padding: 2px 0;}.git-slide-panel__ssh-header { display: flex; align-items: center; justify-content: space-between; padding: 5px 0; font-size: 11px; color: var(--text-muted, #888); cursor: pointer; user-select: none;}.git-slide-panel__ssh-header:hover { color: var(--text, #ccc);}.git-slide-panel__ssh-body { padding: 2px 0 6px;}/* ---- Tab 与内容 ---- */.git-slide-panel__tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); flex-shrink: 0;}.git-slide-panel__tab { flex: 1; padding: 6px 0; border: none; background: transparent; color: var(--text-muted); font-size: 11px; font-weight: 500; cursor: pointer; transition: color 0.12s, background 0.12s; border-bottom: 2px solid transparent;}.git-slide-panel__tab:hover { color: var(--text); background: var(--bg-hover);}.git-slide-panel__tab.is-active { color: var(--accent); border-bottom-color: var(--accent);}.git-slide-panel__content { flex: 1; overflow-y: auto; font-size: 12px; line-height: 1.5; font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace; padding: 4px 0;}
@@ -0,0 +1 @@
1
+ /* ---- Commits 输入 ---- */.git-panel__commit-input { width: 100%; background: var(--bg-input, #2d2d2d); color: var(--text, #ccc); border: 1px solid var(--border, #555); border-radius: 6px; padding: 6px 8px; font-size: 12px; font-family: inherit; resize: vertical; box-sizing: border-box; margin-bottom: 4px; line-height: 1.4; transition: border-color 0.15s;}.git-panel__commit-input:focus { outline: none; border-color: var(--accent, #0078d4);}.git-panel__commit-input::placeholder { color: var(--text-muted, #666);}/* ---- 文件列表 ---- */.git-panel__file { display: flex; align-items: center; gap: 8px; padding: 4px 12px; cursor: pointer; border-radius: 0; font-size: 12px; transition: background 0.1s;}.git-panel__file:hover { background: var(--bg-hover, rgba(128,128,128,0.1));}.git-panel__file-icon { font-size: 14px; width: 20px; text-align: center; flex-shrink: 0;}.git-panel__file-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}/* ---- 加载中 ---- */.git-panel__loading { color: var(--text-muted, #888); font-style: italic; padding: 16px; text-align: center;}/* ---- 状态颜色 ---- */.git-status--M { color: #e5c07b; }.git-status--A { color: #98c379; }.git-status--D { color: #e06c75; }.git-status--R { color: #61afef; }.git-status--U { color: #c678dd; }.git-status--C { color: #56b6c2; }/* ---- Log / Diff 预览 ---- */.git-diff-pre { margin: 0; padding: 8px 12px; font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; color: var(--text, #ccc);}/* ---- 按钮辅助尺寸 ---- */.btn--xs { font-size: 10px; padding: 2px 6px; line-height: 1.4;}