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,291 @@
1
+ /* Model stack: auto-select single presets + compose pill summary */
2
+
3
+ function truncateStackLabel(text, maxLen) {
4
+ const s = String(text || '').trim();
5
+ if (s.length <= maxLen) return s;
6
+ return s.slice(0, maxLen - 1) + '…';
7
+ }
8
+
9
+ function presetShortLabel(p) {
10
+ if (!p) return '';
11
+ const model = (p.model_label || p.model || p.name || p.id || '').trim();
12
+ const prov = (p.provider_label || p.provider || '').trim();
13
+ if (model && prov) return model;
14
+ return model || prov || p.id || '';
15
+ }
16
+
17
+ function stripModelPath(name) {
18
+ const s = String(name || '').trim();
19
+ if (!s) return '';
20
+ const i = s.lastIndexOf('/');
21
+ return (i >= 0 && i < s.length - 1) ? s.slice(i + 1) : s;
22
+ }
23
+
24
+ function findChatPresetById(id) {
25
+ if (!id) return null;
26
+ return (window._llmPresetsCache || []).find(function(p) {
27
+ return p.id === id && (typeof inferPresetUseType !== 'function' || inferPresetUseType(p) === 'chat');
28
+ }) || null;
29
+ }
30
+
31
+ function presetComposeLabel(p, opts) {
32
+ if (!p) return '';
33
+ opts = opts || {};
34
+ let base = stripModelPath(presetShortLabel(p) || p.model || p.name || p.id || '');
35
+ if (!base) return '';
36
+ if (opts.isDefault) base += ' · 默认';
37
+ return base;
38
+ }
39
+
40
+ function presetOptionDetail(p) {
41
+ if (!p) return '';
42
+ const parts = [];
43
+ const name = (p.name || '').trim();
44
+ const model = stripModelPath(p.model_label || p.model || '');
45
+ const prov = (p.provider_label || p.provider || '').trim();
46
+ if (name && name !== model) parts.push(name);
47
+ if (model) parts.push(model);
48
+ if (prov) parts.push(prov);
49
+ const ut = (p.use_type_label || '').trim();
50
+ if (ut) parts.push(ut);
51
+ return parts.filter(Boolean).join(' · ') || String(p.id || '');
52
+ }
53
+
54
+ function getChatMainDisplayLabel(sel) {
55
+ if (!sel || sel.disabled) return '默认模型';
56
+ const v = sel.value;
57
+ if (!v || v === '__default__') {
58
+ const def = findChatPresetById(window._llmDefaultId || '');
59
+ if (def) return presetComposeLabel(def, { isDefault: true });
60
+ return '默认模型';
61
+ }
62
+ const preset = findChatPresetById(v);
63
+ if (preset) {
64
+ return presetComposeLabel(preset, { isDefault: preset.id === (window._llmDefaultId || '') });
65
+ }
66
+ return getSelectShortLabel(sel) || '默认模型';
67
+ }
68
+
69
+ function getSelectShortLabel(sel) {
70
+ if (!sel || sel.disabled) return '';
71
+ const v = sel.value;
72
+ if (!v || v === '__default__' || v === '__none__') return '';
73
+
74
+ const chatPreset = findChatPresetById(v);
75
+ if (chatPreset) return stripModelPath(presetShortLabel(chatPreset) || chatPreset.model || chatPreset.id);
76
+
77
+ const opt = sel.options[sel.selectedIndex];
78
+ if (!opt) return stripModelPath(v);
79
+ let t = (opt.textContent || '').trim().replace(/\s*(默认)\s*$/, '').replace(/\s*·\s*默认\s*$/, '');
80
+ const parts = t.split(/\s*·\s*/).map(function(p) { return stripModelPath(p.trim()); }).filter(Boolean);
81
+ if (!parts.length) return stripModelPath(v);
82
+
83
+ const skip = /^(对话|chat|识图|生图|音频|音乐|视频|多模态)$/i;
84
+ const modelish = parts.filter(function(p) { return p.includes('-') || p.includes('.'); });
85
+ if (modelish.length) return modelish[0];
86
+ const filtered = parts.filter(function(p) { return !skip.test(p); });
87
+ if (filtered.length) {
88
+ filtered.sort(function(a, b) { return a.length - b.length; });
89
+ return filtered[0];
90
+ }
91
+ return parts[parts.length - 1] || stripModelPath(v);
92
+ }
93
+
94
+ function composePillMaxLen() {
95
+ try {
96
+ return window.matchMedia('(max-width: 768px)').matches ? 12 : 20;
97
+ } catch (_) {
98
+ return 20;
99
+ }
100
+ }
101
+
102
+ function autoSelectSinglePreset(sel, getVal, setVal, presets) {
103
+ if (!sel || sel.disabled || !Array.isArray(presets) || presets.length !== 1) return;
104
+ const only = presets[0];
105
+ const pid = only && only.id;
106
+ if (!pid) return;
107
+ const cur = (typeof getVal === 'function' ? getVal() : '') || '';
108
+ if (cur && cur !== '__none__') return;
109
+ if (typeof setVal === 'function') setVal(pid);
110
+ sel.value = pid;
111
+ }
112
+
113
+ function autoSelectModelStackPresets() {
114
+ autoSelectSinglePreset(
115
+ document.getElementById('visionModelSelect'),
116
+ typeof getSelectedVisionModel === 'function' ? getSelectedVisionModel : null,
117
+ typeof setSelectedVisionModel === 'function' ? setSelectedVisionModel : null,
118
+ window._visionPresetsCache
119
+ );
120
+ autoSelectSinglePreset(
121
+ document.getElementById('imageGenModelSelect'),
122
+ typeof getSelectedImageGenModel === 'function' ? getSelectedImageGenModel : null,
123
+ typeof setSelectedImageGenModel === 'function' ? setSelectedImageGenModel : null,
124
+ window._imageGenPresetsCache
125
+ );
126
+ autoSelectSinglePreset(
127
+ document.getElementById('audioModelSelect'),
128
+ typeof getSelectedAudioModel === 'function' ? getSelectedAudioModel : null,
129
+ typeof setSelectedAudioModel === 'function' ? setSelectedAudioModel : null,
130
+ window._audioPresetsCache
131
+ );
132
+ autoSelectSinglePreset(
133
+ document.getElementById('musicModelSelect'),
134
+ typeof getSelectedMusicModel === 'function' ? getSelectedMusicModel : null,
135
+ typeof setSelectedMusicModel === 'function' ? setSelectedMusicModel : null,
136
+ window._musicPresetsCache
137
+ );
138
+ autoSelectSinglePreset(
139
+ document.getElementById('videoGenModelSelect'),
140
+ typeof getSelectedVideoGenModel === 'function' ? getSelectedVideoGenModel : null,
141
+ typeof setSelectedVideoGenModel === 'function' ? setSelectedVideoGenModel : null,
142
+ window._videoGenPresetsCache
143
+ );
144
+
145
+ const chatSel = document.getElementById('modelSelect');
146
+ if (chatSel && !chatSel.disabled) {
147
+ const chatPresets = (window._llmPresetsCache || []).filter(function(p) {
148
+ return typeof inferPresetUseType === 'function' && inferPresetUseType(p) === 'chat';
149
+ });
150
+ const cur = typeof getSelectedModel === 'function' ? getSelectedModel() : '';
151
+ if (!cur && chatPresets.length === 1 && chatPresets[0].id) {
152
+ chatSel.value = chatPresets[0].id;
153
+ if (typeof setSelectedModel === 'function') setSelectedModel(chatPresets[0].id);
154
+ }
155
+ }
156
+ }
157
+
158
+ function updateComposeStackMissingHint() {
159
+ const hint = document.getElementById('composeStackMissingHint');
160
+ if (!hint) return;
161
+ const checks = [
162
+ { sel: document.getElementById('visionModelSelect'), cache: window._visionPresetsCache },
163
+ { sel: document.getElementById('imageGenModelSelect'), cache: window._imageGenPresetsCache },
164
+ { sel: document.getElementById('audioModelSelect'), cache: window._audioPresetsCache },
165
+ { sel: document.getElementById('musicModelSelect'), cache: window._musicPresetsCache },
166
+ { sel: document.getElementById('videoGenModelSelect'), cache: window._videoGenPresetsCache },
167
+ ];
168
+ let anyMissing = false;
169
+ checks.forEach(function(c) {
170
+ if (Array.isArray(c.cache) && c.cache.length === 0) anyMissing = true;
171
+ });
172
+ hint.hidden = !anyMissing;
173
+ }
174
+
175
+ function updateComposeModelStackPill() {
176
+ const pill = document.getElementById('composeModelPillLabel');
177
+ const extra = document.getElementById('composeModelPillExtra');
178
+ const pin = document.getElementById('composeModelPillPin');
179
+ const trigger = document.getElementById('composeModelTrigger');
180
+ const chatSel = document.getElementById('modelSelect');
181
+ if (!pill || !chatSel) return;
182
+
183
+ const main = getChatMainDisplayLabel(chatSel);
184
+ const extras = [];
185
+
186
+ const visionLabel = getSelectShortLabel(document.getElementById('visionModelSelect'));
187
+ if (visionLabel) extras.push('识图 ' + visionLabel);
188
+
189
+ const imageLabel = getSelectShortLabel(document.getElementById('imageGenModelSelect'));
190
+ if (imageLabel) extras.push('生图 ' + imageLabel);
191
+
192
+ const audioLabel = getSelectShortLabel(document.getElementById('audioModelSelect'));
193
+ if (audioLabel) extras.push('音频 ' + audioLabel);
194
+
195
+ const musicLabel = getSelectShortLabel(document.getElementById('musicModelSelect'));
196
+ if (musicLabel) extras.push('音乐 ' + musicLabel);
197
+
198
+ const videoGenLabel = getSelectShortLabel(document.getElementById('videoGenModelSelect'));
199
+ if (videoGenLabel) extras.push('视频 ' + videoGenLabel);
200
+
201
+ const summary = [main].concat(extras).join(' · ');
202
+ pill.textContent = truncateStackLabel(main, composePillMaxLen());
203
+ pill.title = main;
204
+
205
+ if (extra) {
206
+ if (extras.length > 0) {
207
+ extra.textContent = '+' + extras.length;
208
+ extra.hidden = false;
209
+ extra.setAttribute('aria-hidden', 'false');
210
+ extra.title = extras.join('、');
211
+ } else {
212
+ extra.hidden = true;
213
+ extra.setAttribute('aria-hidden', 'true');
214
+ extra.textContent = '';
215
+ }
216
+ }
217
+
218
+ if (trigger) trigger.title = '模型栈:' + summary;
219
+
220
+ // Per-session override pin badge + "clear overrides" button visibility
221
+ let hasOverride = false;
222
+ if (window.ModelStackState) {
223
+ const slots = window.ModelStackState.listSessionOverrideSlots();
224
+ hasOverride = slots.length > 0;
225
+ }
226
+ if (pin) {
227
+ pin.hidden = !hasOverride;
228
+ pin.setAttribute('aria-hidden', hasOverride ? 'false' : 'true');
229
+ if (hasOverride) pin.title = '本会话已自定义模型(按 session 隔离)';
230
+ }
231
+ const clearBtn = document.getElementById('composeClearOverridesBtn');
232
+ if (clearBtn) clearBtn.hidden = !hasOverride;
233
+ const hint = document.getElementById('composeStackActionsHint');
234
+ if (hint) {
235
+ hint.textContent = hasOverride
236
+ ? '当前会话已自定义以下模型:切到别的会话不会受影响。'
237
+ : '当前会话未自定义:使用全局默认。';
238
+ }
239
+ }
240
+
241
+ function refreshComposeModelStackUi() {
242
+ autoSelectModelStackPresets();
243
+ updateComposeStackMissingHint();
244
+ if (typeof updateComposeModelPill === 'function') {
245
+ updateComposeModelPill();
246
+ } else {
247
+ updateComposeModelStackPill();
248
+ }
249
+ }
250
+
251
+ (function initModelStackUi() {
252
+ ['modelSelect', 'visionModelSelect', 'imageGenModelSelect', 'audioModelSelect', 'musicModelSelect', 'videoGenModelSelect'].forEach(function(id) {
253
+ const sel = document.getElementById(id);
254
+ if (!sel) return;
255
+ sel.addEventListener('change', function() {
256
+ updateComposeModelStackPill();
257
+ updateComposeStackMissingHint();
258
+ });
259
+ });
260
+
261
+ window.addEventListener('resize', function() {
262
+ if (typeof updateComposeModelStackPill === 'function') updateComposeModelStackPill();
263
+ });
264
+
265
+ document.addEventListener('DOMContentLoaded', function() {
266
+ refreshComposeModelStackUi();
267
+ });
268
+ if (document.readyState !== 'loading') {
269
+ setTimeout(refreshComposeModelStackUi, 0);
270
+ }
271
+
272
+ // Bind "clear all overrides" button (only meaningful when MS is available)
273
+ const clearBtn = document.getElementById('composeClearOverridesBtn');
274
+ if (clearBtn) {
275
+ clearBtn.addEventListener('click', async function() {
276
+ if (!window.ModelStackState) return;
277
+ const sid = window.ModelStackState.getModelStackScope();
278
+ if (!sid) return;
279
+ if (!confirm('清除当前会话的所有模型覆盖吗?将恢复为全局默认。')) return;
280
+ window.ModelStackState.clearAllSessionOverrides(sid);
281
+ // Persist (empty) to backend
282
+ try { await window.ModelStackState.persistToBackendNow(); } catch (_) {}
283
+ // Re-apply selectors
284
+ if (typeof window.refreshModelStackUi === 'function') {
285
+ window.refreshModelStackUi();
286
+ } else {
287
+ refreshComposeModelStackUi();
288
+ }
289
+ });
290
+ }
291
+ })();
@@ -0,0 +1,85 @@
1
+ /* Video-gen LLM selector — per-session via ModelStackState */
2
+
3
+ const VIDEO_GEN_MODEL_KEY = 'oa_video_gen_preset_id'; // legacy, kept for migration
4
+
5
+ function getSelectedVideoGenModel() {
6
+ if (window.ModelStackState) return window.ModelStackState.getEffectiveModel('video_gen');
7
+ try { return localStorage.getItem(VIDEO_GEN_MODEL_KEY) || ''; } catch (_) { return ''; }
8
+ }
9
+ function setSelectedVideoGenModel(val) {
10
+ if (window.ModelStackState) {
11
+ if (val && val !== '__none__') {
12
+ window.ModelStackState.setSessionOverride('video_gen', val);
13
+ window.ModelStackState.schedulePersistToBackend();
14
+ } else {
15
+ window.ModelStackState.clearSessionOverride('video_gen');
16
+ window.ModelStackState.schedulePersistToBackend();
17
+ }
18
+ return;
19
+ }
20
+ try { localStorage.setItem(VIDEO_GEN_MODEL_KEY, String(val || '').trim()); } catch (_) {}
21
+ }
22
+
23
+ async function refreshVideoGenModelSelect() {
24
+ const sel = document.getElementById('videoGenModelSelect');
25
+ if (!sel) return;
26
+ try {
27
+ const r = await fetch('/api/ui/llm/presets');
28
+ if (!r.ok) return;
29
+ const j = await r.json();
30
+ const presets = (j.presets || []).filter(function(p) { return p.supports_video_gen === true; });
31
+ window._videoGenPresetsCache = presets;
32
+ const cur = getSelectedVideoGenModel();
33
+ sel.innerHTML = '';
34
+ if (!presets.length) {
35
+ sel.disabled = true;
36
+ const o = document.createElement('option');
37
+ o.value = '';
38
+ o.textContent = '未配置视频生成模型';
39
+ sel.appendChild(o);
40
+ return;
41
+ }
42
+ sel.disabled = false;
43
+ const ph = document.createElement('option');
44
+ ph.value = '';
45
+ ph.textContent = '— 视频生成 —';
46
+ sel.appendChild(ph);
47
+ presets.forEach(function(p) {
48
+ const o = document.createElement('option');
49
+ o.value = p.id || '';
50
+ const modelLabel = (p.model_label || p.model || p.id || '').trim();
51
+ o.textContent = (p.name || modelLabel) + ' · ' + modelLabel + ' (视频生成)';
52
+ sel.appendChild(o);
53
+ });
54
+ if (cur && presets.some(function(p) { return p.id === cur; })) {
55
+ sel.value = cur;
56
+ } else if (presets.length === 1) {
57
+ sel.value = presets[0].id;
58
+ setSelectedVideoGenModel(presets[0].id);
59
+ }
60
+ } catch (_) {}
61
+ }
62
+
63
+ (function bindVideoGenModelSelect() {
64
+ const sel = document.getElementById('videoGenModelSelect');
65
+ if (!sel) return;
66
+ sel.addEventListener('change', function() {
67
+ setSelectedVideoGenModel(sel.value);
68
+ });
69
+ })();
70
+
71
+ if (typeof refreshMusicModelSelect === 'function') {
72
+ const _origRefreshMusicForVideo = refreshMusicModelSelect;
73
+ refreshMusicModelSelect = async function() {
74
+ await _origRefreshMusicForVideo();
75
+ await refreshVideoGenModelSelect();
76
+ };
77
+ } else if (typeof refreshAudioModelSelect === 'function') {
78
+ const _origRefreshAudioForVideo = refreshAudioModelSelect;
79
+ refreshAudioModelSelect = async function() {
80
+ await _origRefreshAudioForVideo();
81
+ await refreshVideoGenModelSelect();
82
+ };
83
+ }
84
+
85
+ refreshVideoGenModelSelect().catch(function() {});
@@ -0,0 +1,66 @@
1
+ /* ================================================================
2
+ * 01k-model-stack-bootstrap.js
3
+ * Boot-time wiring for the per-session model stack.
4
+ * Listens to the existing 'session-changed' CustomEvent
5
+ * dispatched by 01c-session.js and:
6
+ * 1) migrates any legacy global keys (F7) on first session
7
+ * 2) restores backend overrides into session storage
8
+ * 3) refreshes the 6 selectors so the dropdown values match
9
+ * ================================================================ */
10
+
11
+ (function bootstrapModelStack() {
12
+ if (!window.ModelStackState) return; // 01k-state.js must load first
13
+
14
+ const MS = window.ModelStackState;
15
+ const SLOTS = MS.SLOTS;
16
+
17
+ // Re-apply all 6 selectors' current values from ModelStackState.
18
+ function applyEffectiveToSelectors() {
19
+ if (typeof refreshModelSelect === 'function') refreshModelSelect();
20
+ if (typeof refreshVisionModelSelect === 'function') refreshVisionModelSelect();
21
+ if (typeof refreshImageGenModelSelect === 'function') refreshImageGenModelSelect();
22
+ if (typeof refreshAudioModelSelect === 'function') refreshAudioModelSelect();
23
+ if (typeof refreshMusicModelSelect === 'function') refreshMusicModelSelect();
24
+ if (typeof refreshVideoGenModelSelect === 'function') refreshVideoGenModelSelect();
25
+ }
26
+
27
+ async function onSessionChanged(sid) {
28
+ try {
29
+ // F7: one-shot migration of legacy global keys → bind to current session
30
+ MS.migrateLegacyOnce();
31
+ // Restore any backend-persisted overrides for this session
32
+ await MS.restoreFromBackend(sid);
33
+ // Apply the effective (session → global → empty) values to dropdowns
34
+ applyEffectiveToSelectors();
35
+ // Refresh the pill/badge so 📌 shows up
36
+ if (typeof updateComposeModelPill === 'function') {
37
+ updateComposeModelPill();
38
+ } else if (typeof updateComposeModelStackPill === 'function') {
39
+ updateComposeModelStackPill();
40
+ }
41
+ } catch (e) {
42
+ try { console.warn('[model-stack] onSessionChanged failed', e); } catch (_) {}
43
+ }
44
+ }
45
+
46
+ // Listen for session changes (01c-session.js dispatches this).
47
+ window.addEventListener('session-changed', function(ev) {
48
+ const sid = ev && ev.detail && ev.detail.sessionId;
49
+ if (sid) onSessionChanged(sid);
50
+ });
51
+
52
+ // Also handle initial load (event already fired before this listener attached)
53
+ document.addEventListener('DOMContentLoaded', function() {
54
+ // Defer one tick so sessionId is assigned
55
+ setTimeout(function() {
56
+ const sid = MS.getModelStackScope();
57
+ if (sid) onSessionChanged(sid);
58
+ }, 0);
59
+ });
60
+
61
+ // Expose for manual triggers (e.g. after "clear all overrides")
62
+ window.refreshModelStackUi = function() {
63
+ applyEffectiveToSelectors();
64
+ if (typeof updateComposeModelPill === 'function') updateComposeModelPill();
65
+ };
66
+ })();
@@ -0,0 +1,209 @@
1
+ /* ================================================================
2
+ * 01k-model-stack-state.js
3
+ * Per-session + global-default model selection state.
4
+ *
5
+ * Three-level precedence (highest → lowest):
6
+ * 1. session override — oa_ms_over::session::<sid>::<slot>
7
+ * 2. global default — oa_ms_global::<slot>
8
+ * 3. system default — empty string (handled by server)
9
+ *
10
+ * Also keeps the legacy keys in sync for one-shot migration (F7).
11
+ * ================================================================ */
12
+
13
+ (function initModelStackState() {
14
+ // Slot definitions: the 6 model types. Order is fixed.
15
+ const SLOTS = ['llm', 'vision', 'image_gen', 'audio', 'music', 'video_gen'];
16
+
17
+ // Legacy single-key names (pre per-session). Read on init to seed
18
+ // session override once, then ignored.
19
+ const LEGACY_KEYS = {
20
+ llm: 'oa_llm_preset_id',
21
+ vision: 'oa_vision_preset_id',
22
+ image_gen: 'oa_image_gen_preset_id',
23
+ audio: 'oa_audio_preset_id',
24
+ music: 'oa_music_preset_id',
25
+ video_gen: 'oa_video_gen_preset_id',
26
+ };
27
+
28
+ function safeGet(k) {
29
+ try { return localStorage.getItem(k) || ''; } catch (_) { return ''; }
30
+ }
31
+ function safeSet(k, v) {
32
+ try { localStorage.setItem(k, String(v == null ? '' : v).trim()); } catch (_) {}
33
+ }
34
+ function safeDel(k) {
35
+ try { localStorage.removeItem(k); } catch (_) {}
36
+ }
37
+
38
+ // ---- Current session scope (driven by 01c-session.js) ----
39
+ function getModelStackScope() {
40
+ // sessionId is a `let` in 01c-session.js. Reading `typeof sessionId` in
41
+ // TDZ would throw, so wrap in try. After TDZ, `typeof` returns 'undefined'
42
+ // cleanly. We fall back to window.sessionId (which bootstrap module may set).
43
+ try {
44
+ if (typeof sessionId !== 'undefined' && sessionId) return String(sessionId);
45
+ } catch (_) {}
46
+ try {
47
+ if (window.sessionId) return String(window.sessionId);
48
+ } catch (_) {}
49
+ return '';
50
+ }
51
+
52
+ // ---- Session-level override ----
53
+ function sessionKey(sid, slot) {
54
+ return 'oa_ms_over::session::' + (sid || 'default') + '::' + slot;
55
+ }
56
+ function getSessionOverride(slot, sid) {
57
+ if (!SLOTS.includes(slot)) return '';
58
+ return safeGet(sessionKey(sid || getModelStackScope(), slot));
59
+ }
60
+ function setSessionOverride(slot, val, sid) {
61
+ if (!SLOTS.includes(slot)) return;
62
+ safeSet(sessionKey(sid || getModelStackScope(), slot), val);
63
+ }
64
+ function clearSessionOverride(slot, sid) {
65
+ if (!SLOTS.includes(slot)) return;
66
+ safeDel(sessionKey(sid || getModelStackScope(), slot));
67
+ }
68
+ function clearAllSessionOverrides(sid) {
69
+ const k = sid || getModelStackScope();
70
+ SLOTS.forEach(function(s) { safeDel(sessionKey(k, s)); });
71
+ }
72
+ function listSessionOverrideSlots(sid) {
73
+ const k = sid || getModelStackScope();
74
+ return SLOTS.filter(function(s) { return !!safeGet(sessionKey(k, s)); });
75
+ }
76
+
77
+ // ---- Global default ----
78
+ function getGlobalDefault(slot) {
79
+ if (!SLOTS.includes(slot)) return '';
80
+ return safeGet('oa_ms_global::' + slot);
81
+ }
82
+ function setGlobalDefault(slot, val) {
83
+ if (!SLOTS.includes(slot)) return;
84
+ safeSet('oa_ms_global::' + slot, val);
85
+ }
86
+ function getAllGlobalDefaults() {
87
+ const out = {};
88
+ SLOTS.forEach(function(s) { out[s] = getGlobalDefault(s); });
89
+ return out;
90
+ }
91
+ function setAllGlobalDefaults(obj) {
92
+ SLOTS.forEach(function(s) {
93
+ if (obj && Object.prototype.hasOwnProperty.call(obj, s)) {
94
+ setGlobalDefault(s, obj[s]);
95
+ }
96
+ });
97
+ }
98
+
99
+ // ---- Three-level precedence resolver ----
100
+ function getEffectiveModel(slot, sid) {
101
+ if (!SLOTS.includes(slot)) return '';
102
+ const s = sid || getModelStackScope();
103
+ const a = getSessionOverride(slot, s);
104
+ if (a) return a;
105
+ const b = getGlobalDefault(slot);
106
+ if (b) return b;
107
+ return '';
108
+ }
109
+ function getEffectiveModelStack(sid) {
110
+ const out = {};
111
+ SLOTS.forEach(function(s) { out[s] = getEffectiveModel(s, sid); });
112
+ return out;
113
+ }
114
+
115
+ // ---- One-shot migration from legacy global keys ----
116
+ // Binds legacy localStorage values to the currently active session,
117
+ // then clears them. Idempotent (uses a guard flag).
118
+ function migrateLegacyOnce() {
119
+ const flag = 'oa_ms_legacy_migrated_v1';
120
+ if (safeGet(flag)) return false;
121
+ const sid = getModelStackScope();
122
+ if (!sid) return false; // No session yet — wait until one is active.
123
+ let migrated = 0;
124
+ SLOTS.forEach(function(s) {
125
+ const legacy = safeGet(LEGACY_KEYS[s]);
126
+ if (legacy) {
127
+ if (!getSessionOverride(s, sid)) {
128
+ setSessionOverride(s, legacy, sid);
129
+ migrated++;
130
+ }
131
+ safeDel(LEGACY_KEYS[s]);
132
+ }
133
+ });
134
+ safeSet(flag, '1');
135
+ if (migrated > 0) {
136
+ try { console.info('[model-stack] migrated', migrated, 'legacy keys to session', sid); } catch (_) {}
137
+ }
138
+ return true;
139
+ }
140
+
141
+ // ---- Persist overrides to backend (best-effort, debounced) ----
142
+ let _persistTimer = null;
143
+ function schedulePersistToBackend() {
144
+ if (_persistTimer) clearTimeout(_persistTimer);
145
+ _persistTimer = setTimeout(persistToBackendNow, 500);
146
+ }
147
+ async function persistToBackendNow() {
148
+ _persistTimer = null;
149
+ const sid = getModelStackScope();
150
+ if (!sid) return;
151
+ const aid = (typeof agentId !== 'undefined' && agentId) ? agentId : 'default';
152
+ const overrides = {};
153
+ SLOTS.forEach(function(s) {
154
+ const v = getSessionOverride(s, sid);
155
+ if (v) overrides[s] = v;
156
+ });
157
+ try {
158
+ await fetch('/api/ui/session/model-stack', {
159
+ method: 'POST',
160
+ headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({ session_id: sid, agent_id: aid, overrides: overrides }),
162
+ });
163
+ } catch (_) {}
164
+ }
165
+
166
+ // ---- Restore overrides from backend on session switch ----
167
+ async function restoreFromBackend(sid, aid) {
168
+ const s = sid || getModelStackScope();
169
+ if (!s) return;
170
+ const a = aid || (typeof agentId !== 'undefined' ? agentId : 'default') || 'default';
171
+ try {
172
+ const r = await fetch('/api/ui/session/model-stack?session_id=' + encodeURIComponent(s) + '&agent_id=' + encodeURIComponent(a));
173
+ if (!r.ok) return;
174
+ const j = await r.json();
175
+ const ov = (j && j.overrides) || {};
176
+ // Clear existing session overrides for this sid, then apply backend ones.
177
+ clearAllSessionOverrides(s);
178
+ SLOTS.forEach(function(slot) {
179
+ // Backend keys are llm_id, vision_llm_id, etc. → slot = strip _id
180
+ const k = slot + '_id';
181
+ if (typeof ov[k] === 'string' && ov[k]) {
182
+ setSessionOverride(slot, ov[k], s);
183
+ }
184
+ });
185
+ } catch (_) {}
186
+ }
187
+
188
+ // ---- Expose ----
189
+ window.ModelStackState = {
190
+ SLOTS: SLOTS,
191
+ LEGACY_KEYS: LEGACY_KEYS,
192
+ getModelStackScope: getModelStackScope,
193
+ getSessionOverride: getSessionOverride,
194
+ setSessionOverride: setSessionOverride,
195
+ clearSessionOverride: clearSessionOverride,
196
+ clearAllSessionOverrides: clearAllSessionOverrides,
197
+ listSessionOverrideSlots: listSessionOverrideSlots,
198
+ getGlobalDefault: getGlobalDefault,
199
+ setGlobalDefault: setGlobalDefault,
200
+ getAllGlobalDefaults: getAllGlobalDefaults,
201
+ setAllGlobalDefaults: setAllGlobalDefaults,
202
+ getEffectiveModel: getEffectiveModel,
203
+ getEffectiveModelStack: getEffectiveModelStack,
204
+ migrateLegacyOnce: migrateLegacyOnce,
205
+ schedulePersistToBackend: schedulePersistToBackend,
206
+ persistToBackendNow: persistToBackendNow,
207
+ restoreFromBackend: restoreFromBackend,
208
+ };
209
+ })();