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,409 @@
1
+ /* ================================================================
2
+ * 11f-env-llm-b-form.js
3
+ * LLM Preset 编辑表单:
4
+ * - 标签/segment 构造
5
+ * - 整张表单 HTML(presetFormHtml)
6
+ * - 表单 ↔ 状态的双向同步(syncPresetConnectionVisibility /
7
+ * refreshPresetCopyFromOptions / applyConnectionFromPreset /
8
+ * refreshPresetModelOptions / syncPresetFormFromProvider)
9
+ * - 收集器(collectPresetPayload)
10
+ * - 事件绑定(wirePresetForm / wirePresetSaveAndTest)
11
+ *
12
+ * 依赖 11f-env-llm-a-state.js(按字母序保证加载)。
13
+ * 上游依赖:00-utils.js (escAttr)。
14
+ * ================================================================ */
15
+
16
+ function setSelectValue(sel, value) {
17
+ if (!sel || value == null || value === '') return;
18
+ sel.value = value;
19
+ if (sel.value !== value) {
20
+ for (let i = 0; i < sel.options.length; i++) {
21
+ if (sel.options[i].value === value) {
22
+ sel.selectedIndex = i;
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ function presetAutoId(provider, model) {
30
+ const raw = ((provider || 'custom').trim() || 'custom') + '_' + ((model || '').trim() || 'default');
31
+ return raw
32
+ .replace(/[^a-zA-Z0-9\u4e00-\u9fff_-]+/g, '_')
33
+ .replace(/_+/g, '_')
34
+ .replace(/^_|_$/g, '')
35
+ .toLowerCase() || 'preset';
36
+ }
37
+
38
+ function presetCardTitle(p) {
39
+ if (!p) return '未命名';
40
+ const prov = providerLabel(p);
41
+ const model = (p.model_label || p.model || '').trim();
42
+ return model ? prov + ' · ' + model : prov;
43
+ }
44
+
45
+ function presetProviderOptionsHtml(selectedId, includeCustom) {
46
+ let opts = '';
47
+ llmProviderCatalog.forEach(function(prov) {
48
+ if (prov.id === 'custom' && !includeCustom) return;
49
+ const sel = prov.id === selectedId ? ' selected' : '';
50
+ opts += '<option value="' + escAttr(prov.id) + '">' + escAttr(prov.label || prov.id) + '</option>';
51
+ });
52
+ return opts;
53
+ }
54
+
55
+ function presetUseTypeSegmentHtml(selectedUseType) {
56
+ const cur = (selectedUseType || 'chat').trim();
57
+ let html = '<div class="preset-use-type-seg" role="group" aria-label="用途">';
58
+ PRESET_USE_TYPE_ORDER.forEach(function(ut) {
59
+ const label = PRESET_USE_TYPE_LABELS[ut] || ut;
60
+ const active = ut === cur ? ' is-active' : '';
61
+ html += '<button type="button" class="preset-use-type-seg__btn' + active + '" data-use-type="' + escAttr(ut) + '">' + escAttr(label) + '</button>';
62
+ });
63
+ html += '</div>';
64
+ return html;
65
+ }
66
+
67
+ function getSelectedUseType(editWrap) {
68
+ if (!editWrap) return 'chat';
69
+ const active = editWrap.querySelector('.preset-use-type-seg__btn.is-active');
70
+ return (active && active.getAttribute('data-use-type')) || 'chat';
71
+ }
72
+
73
+ function setSelectedUseType(editWrap, useType) {
74
+ if (!editWrap) return;
75
+ const ut = (useType || 'chat').trim();
76
+ editWrap.querySelectorAll('.preset-use-type-seg__btn').forEach(function(btn) {
77
+ btn.classList.toggle('is-active', btn.getAttribute('data-use-type') === ut);
78
+ });
79
+ }
80
+
81
+ function presetFormHtml(p) {
82
+ p = p || {};
83
+ const provider = resolvePresetProviderId(p);
84
+ const useType = inferPresetUseType(p);
85
+ const manualConn = providerNeedsManualConnection(provider);
86
+ const authNone = !p.auth_scheme || p.auth_scheme === 'None' || p.auth_scheme === '';
87
+ return (
88
+ '<label class="form-label">用途</label>' +
89
+ presetUseTypeSegmentHtml(useType) +
90
+ '<label class="form-label">服务商</label>' +
91
+ '<select class="preset-fld-provider md-select" style="max-width:100%;">' +
92
+ presetProviderOptionsHtml(provider, true) +
93
+ '</select>' +
94
+ '<div class="preset-catalog-fields"' + (manualConn ? ' style="display:none;"' : '') + '>' +
95
+ ' <label class="form-label">模型</label>' +
96
+ ' <select class="preset-fld-model-id md-select" style="max-width:100%;"></select>' +
97
+ ' <div class="preset-manual-model-row" style="display:none;">' +
98
+ ' <label class="form-label">模型名称(手动)</label>' +
99
+ ' <input class="preset-fld-model-manual" type="text" placeholder="模型 id,如 qwen2.5:7b"/>' +
100
+ ' </div>' +
101
+ '</div>' +
102
+ '<div class="preset-connection-fields"' + (manualConn ? '' : ' style="display:none;"') + '>' +
103
+ ' <label class="form-label">Base URL</label>' +
104
+ ' <input class="preset-fld-base" type="text" placeholder="http://127.0.0.1:11434/v1" value="' + escAttr(p.base_url || '') + '"/>' +
105
+ ' <label class="form-label">模型名称</label>' +
106
+ ' <input class="preset-fld-model" type="text" placeholder="服务端 model id" value="' + escAttr(p.model || '') + '"/>' +
107
+ ' <label class="form-label">认证</label>' +
108
+ ' <select class="preset-fld-scheme md-select" style="max-width:100%;">' +
109
+ ' <option' + (!authNone ? ' selected' : '') + '>Bearer</option>' +
110
+ ' <option' + (authNone ? ' selected' : '') + '>None</option>' +
111
+ ' </select>' +
112
+ '</div>' +
113
+ '<label class="form-label">API Key</label>' +
114
+ '<input class="preset-fld-key" type="password" placeholder="本地部署可留空" value="' + escAttr(p.api_key || '') + '"/>' +
115
+ '<label class="form-label">最大输出 tokens</label>' +
116
+ '<input class="preset-fld-max-tokens" type="number" min="256" max="65536" step="512" style="width:100%;" value="' + (p.max_tokens || 8192) + '"/>' +
117
+ '<div class="preset-copy-row">' +
118
+ ' <label class="form-label">复制连接</label>' +
119
+ ' <select class="preset-fld-copy-from md-select" style="max-width:100%;"><option value="">从已有预设复制 Base URL / Key…</option></select>' +
120
+ '</div>' +
121
+ '<p class="form-hint preset-capability-hint" style="margin:var(--sp-2) 0 0;"></p>' +
122
+ '<div class="row-actions" style="margin-top: var(--sp-3);">' +
123
+ ' <button type="button" class="btn btn--primary btn--sm preset-save-btn">保存</button>' +
124
+ ' <button type="button" class="btn btn--ghost btn--sm preset-test-btn">测试连接</button>' +
125
+ ' <button type="button" class="btn btn--subtle btn--sm preset-cancel-btn">取消</button>' +
126
+ '</div>'
127
+ );
128
+ }
129
+
130
+ function syncPresetConnectionVisibility(editWrap) {
131
+ if (!editWrap) return;
132
+ const prov = (editWrap.querySelector('.preset-fld-provider') || {}).value || 'deepseek';
133
+ const manual = providerNeedsManualConnection(prov);
134
+ const catalog = editWrap.querySelector('.preset-catalog-fields');
135
+ const connection = editWrap.querySelector('.preset-connection-fields');
136
+ if (catalog) catalog.style.display = manual ? 'none' : '';
137
+ if (connection) connection.style.display = manual ? 'block' : 'none';
138
+ if (manual && prov !== 'custom') {
139
+ const spec = getProviderEntry(prov);
140
+ const baseInp = editWrap.querySelector('.preset-fld-base');
141
+ if (baseInp && !baseInp.value.trim() && spec && spec.default_base_url) {
142
+ baseInp.value = spec.default_base_url;
143
+ }
144
+ }
145
+ }
146
+
147
+ function refreshPresetCopyFromOptions(editWrap) {
148
+ const sel = editWrap && editWrap.querySelector('.preset-fld-copy-from');
149
+ if (!sel) return;
150
+ const curId = editWrap.getAttribute('data-preset-id') || '';
151
+ const presets = window._llmPresetsCache || [];
152
+ sel.innerHTML = '<option value="">从已有预设复制 Base URL / Key…</option>';
153
+ presets.forEach(function(p) {
154
+ if (!p || !p.id || p.id === curId) return;
155
+ const opt = document.createElement('option');
156
+ opt.value = p.id;
157
+ opt.textContent = presetCardTitle(p) + ' · ' + presetUseTypeLabel(p);
158
+ sel.appendChild(opt);
159
+ });
160
+ }
161
+
162
+ function applyConnectionFromPreset(editWrap, source) {
163
+ if (!editWrap || !source) return;
164
+ const provSel = editWrap.querySelector('.preset-fld-provider');
165
+ if (provSel) {
166
+ setSelectValue(provSel, resolvePresetProviderId(source));
167
+ }
168
+ const baseInp = editWrap.querySelector('.preset-fld-base');
169
+ const modelInp = editWrap.querySelector('.preset-fld-model');
170
+ const keyInp = editWrap.querySelector('.preset-fld-key');
171
+ const schemeSel = editWrap.querySelector('.preset-fld-scheme');
172
+ if (baseInp) baseInp.value = source.base_url || '';
173
+ if (modelInp && !modelInp.value.trim()) modelInp.value = source.model || '';
174
+ if (keyInp && !keyInp.value.trim()) keyInp.value = source.api_key || '';
175
+ if (schemeSel) {
176
+ const none = !source.auth_scheme || source.auth_scheme === 'None';
177
+ schemeSel.value = none ? 'None' : 'Bearer';
178
+ }
179
+ syncPresetConnectionVisibility(editWrap);
180
+ syncPresetFormFromProvider(editWrap, {
181
+ providerId: resolvePresetProviderId(source),
182
+ modelId: source.model || '',
183
+ });
184
+ updatePresetCapabilityHint(editWrap);
185
+ }
186
+
187
+ function refreshPresetModelOptions(editWrap, providerId, selectedModelId, useType) {
188
+ const sel = editWrap.querySelector('.preset-fld-model-id');
189
+ const manualRow = editWrap.querySelector('.preset-manual-model-row');
190
+ const manualInp = editWrap.querySelector('.preset-fld-model-manual');
191
+ if (!sel) return;
192
+ const ut = useType || getSelectedUseType(editWrap);
193
+ const rows = listProviderModelsGrouped(providerId, ut);
194
+ sel.innerHTML = '';
195
+ if (!rows.length) {
196
+ sel.style.display = 'none';
197
+ if (manualRow) manualRow.style.display = 'block';
198
+ if (manualInp && selectedModelId) manualInp.value = selectedModelId;
199
+ return;
200
+ }
201
+ sel.style.display = '';
202
+ if (manualRow) manualRow.style.display = 'none';
203
+ rows.forEach(function(row) {
204
+ const opt = document.createElement('option');
205
+ opt.value = row.id;
206
+ opt.textContent = row.label;
207
+ opt.setAttribute('data-use-type', row.useType);
208
+ if (row.id === selectedModelId) opt.selected = true;
209
+ sel.appendChild(opt);
210
+ });
211
+ if (!sel.value && sel.options.length) sel.selectedIndex = 0;
212
+ }
213
+
214
+ function syncPresetFormFromProvider(editWrap, initial) {
215
+ initial = initial || {};
216
+ const provSel = editWrap.querySelector('.preset-fld-provider');
217
+ const pid = initial.providerId || (provSel && provSel.value) || 'deepseek';
218
+ if (initial.useType) setSelectedUseType(editWrap, initial.useType);
219
+ if (provSel) setSelectValue(provSel, pid);
220
+ syncPresetConnectionVisibility(editWrap);
221
+ refreshPresetModelOptions(editWrap, pid, initial.modelId || '', getSelectedUseType(editWrap));
222
+ const modelSel = editWrap.querySelector('.preset-fld-model-id');
223
+ if (modelSel && initial.modelId) setSelectValue(modelSel, initial.modelId);
224
+ updatePresetCapabilityHint(editWrap);
225
+ }
226
+
227
+ function getFormModelName(editWrap) {
228
+ const prov = (editWrap.querySelector('.preset-fld-provider') || {}).value || 'deepseek';
229
+ if (providerNeedsManualConnection(prov)) {
230
+ return (editWrap.querySelector('.preset-fld-model') || {}).value.trim();
231
+ }
232
+ const modelSel = editWrap.querySelector('.preset-fld-model-id');
233
+ const manual = (editWrap.querySelector('.preset-fld-model-manual') || {}).value.trim();
234
+ if (modelSel && modelSel.style.display !== 'none') return modelSel.value;
235
+ return manual;
236
+ }
237
+
238
+ function formUsesManualConnection(editWrap) {
239
+ const prov = (editWrap.querySelector('.preset-fld-provider') || {}).value || 'deepseek';
240
+ if (providerNeedsManualConnection(prov)) return true;
241
+ const rows = listProviderModelsGrouped(prov, getSelectedUseType(editWrap));
242
+ return rows.length === 0;
243
+ }
244
+
245
+ function updatePresetCapabilityHint(editWrap) {
246
+ const hint = editWrap && editWrap.querySelector('.preset-capability-hint');
247
+ if (!hint) return;
248
+ const prov = (editWrap.querySelector('.preset-fld-provider') || {}).value || 'deepseek';
249
+ const useType = getSelectedUseType(editWrap);
250
+ const cap = PRESET_USE_TYPE_LABELS[useType] || useType;
251
+ const extra = presetCapabilityHint(prov, useType);
252
+ hint.textContent = extra ? ('能力:' + cap + ' — ' + extra) : ('能力:' + cap);
253
+ }
254
+
255
+ function collectPresetPayload(editWrap, presetId) {
256
+ const provider = (editWrap.querySelector('.preset-fld-provider') || {}).value.trim();
257
+ const key = (editWrap.querySelector('.preset-fld-key') || {}).value.trim();
258
+ const useType = getSelectedUseType(editWrap);
259
+ let maxTokens = parseInt((editWrap.querySelector('.preset-fld-max-tokens') || {}).value, 10);
260
+ if (isNaN(maxTokens) || maxTokens < 256) maxTokens = 8192;
261
+ if (maxTokens > 65536) maxTokens = 65536;
262
+ const manualConn = formUsesManualConnection(editWrap);
263
+ const schemeSel = editWrap.querySelector('.preset-fld-scheme');
264
+ const scheme = schemeSel ? schemeSel.value : 'Bearer';
265
+
266
+ if (manualConn) {
267
+ const model = (editWrap.querySelector('.preset-fld-model') || {}).value.trim()
268
+ || getFormModelName(editWrap);
269
+ const prov = provider || 'custom';
270
+ return {
271
+ id: presetId || presetAutoId(prov, model),
272
+ provider: prov,
273
+ use_type: useType,
274
+ base_url: (editWrap.querySelector('.preset-fld-base') || {}).value.trim(),
275
+ model: model,
276
+ api_key: key,
277
+ auth_scheme: scheme === 'None' ? '' : scheme,
278
+ max_tokens: maxTokens,
279
+ advanced: true,
280
+ };
281
+ }
282
+
283
+ const model = getFormModelName(editWrap);
284
+ return {
285
+ id: presetId || presetAutoId(provider, model),
286
+ provider: provider,
287
+ use_type: useType,
288
+ model: model,
289
+ api_key: key,
290
+ auth_scheme: 'Bearer',
291
+ max_tokens: maxTokens,
292
+ };
293
+ }
294
+
295
+ function wirePresetForm(editWrap, p) {
296
+ p = p || {};
297
+ if (p.id) editWrap.setAttribute('data-preset-id', p.id);
298
+ setSelectedUseType(editWrap, inferPresetUseType(p));
299
+
300
+ syncPresetFormFromProvider(editWrap, {
301
+ providerId: resolvePresetProviderId(p),
302
+ modelId: p.model || '',
303
+ useType: inferPresetUseType(p),
304
+ });
305
+ refreshPresetCopyFromOptions(editWrap);
306
+
307
+ const provSel = editWrap.querySelector('.preset-fld-provider');
308
+ if (provSel) {
309
+ provSel.addEventListener('change', function() {
310
+ syncPresetConnectionVisibility(editWrap);
311
+ syncPresetFormFromProvider(editWrap, { useType: getSelectedUseType(editWrap) });
312
+ });
313
+ }
314
+
315
+ editWrap.querySelectorAll('.preset-use-type-seg__btn').forEach(function(btn) {
316
+ btn.addEventListener('click', function() {
317
+ setSelectedUseType(editWrap, btn.getAttribute('data-use-type'));
318
+ syncPresetFormFromProvider(editWrap, { useType: getSelectedUseType(editWrap) });
319
+ });
320
+ });
321
+
322
+ const modelSel = editWrap.querySelector('.preset-fld-model-id');
323
+ const manualInp = editWrap.querySelector('.preset-fld-model-manual');
324
+ const connModelInp = editWrap.querySelector('.preset-fld-model');
325
+ if (modelSel) modelSel.addEventListener('change', function() { updatePresetCapabilityHint(editWrap); });
326
+ if (manualInp) manualInp.addEventListener('input', function() { updatePresetCapabilityHint(editWrap); });
327
+ if (connModelInp) connModelInp.addEventListener('input', function() { updatePresetCapabilityHint(editWrap); });
328
+
329
+ const copySel = editWrap.querySelector('.preset-fld-copy-from');
330
+ if (copySel) {
331
+ copySel.addEventListener('change', function() {
332
+ const pid = copySel.value;
333
+ if (!pid) return;
334
+ const src = (window._llmPresetsCache || []).find(function(x) { return x.id === pid; });
335
+ if (src) applyConnectionFromPreset(editWrap, src);
336
+ copySel.value = '';
337
+ });
338
+ }
339
+
340
+ updatePresetCapabilityHint(editWrap);
341
+ }
342
+
343
+ function wirePresetSaveAndTest(editWrap, editStatus, options) {
344
+ options = options || {};
345
+ const presetId = options.presetId || '';
346
+
347
+ editWrap.querySelector('.preset-save-btn').addEventListener('click', async function() {
348
+ const kv = collectPresetPayload(editWrap, presetId);
349
+ if (!kv.provider) {
350
+ editStatus.textContent = '请选择服务商';
351
+ editStatus.classList.add('is-err');
352
+ return;
353
+ }
354
+ if (!kv.model) {
355
+ editStatus.textContent = '请填写或选择模型';
356
+ editStatus.classList.add('is-err');
357
+ return;
358
+ }
359
+ if (kv.advanced && !kv.base_url) {
360
+ editStatus.textContent = '请填写 Base URL';
361
+ editStatus.classList.add('is-err');
362
+ return;
363
+ }
364
+ editStatus.textContent = '保存中…';
365
+ editStatus.classList.remove('is-err');
366
+ try {
367
+ const r = await fetch('/api/ui/llm/presets', {
368
+ method: 'POST',
369
+ headers: { 'Content-Type': 'application/json' },
370
+ credentials: 'same-origin',
371
+ body: JSON.stringify(kv),
372
+ });
373
+ const j = await r.json().catch(function() { return {}; });
374
+ if (!r.ok) throw new Error(j.detail || r.statusText);
375
+ editStatus.textContent = '已保存。';
376
+ if (options.onSaved) options.onSaved();
377
+ await loadLlmPresets();
378
+ if (typeof refreshMultimodalModelSelects === 'function') {
379
+ await refreshMultimodalModelSelects();
380
+ } else {
381
+ refreshChatModelSelect();
382
+ if (typeof refreshTtsOptions === 'function') refreshTtsOptions().catch(function() {});
383
+ }
384
+ } catch (e) {
385
+ editStatus.classList.add('is-err');
386
+ editStatus.textContent = String(e);
387
+ }
388
+ });
389
+
390
+ editWrap.querySelector('.preset-test-btn').addEventListener('click', async function() {
391
+ const kv = collectPresetPayload(editWrap, presetId);
392
+ editStatus.textContent = '测试中…';
393
+ editStatus.classList.remove('is-err');
394
+ try {
395
+ const r = await fetch('/api/ui/llm/presets/test', {
396
+ method: 'POST',
397
+ headers: { 'Content-Type': 'application/json' },
398
+ credentials: 'same-origin',
399
+ body: JSON.stringify(kv),
400
+ });
401
+ const j = await r.json().catch(function() { return {}; });
402
+ if (!r.ok) throw new Error(j.detail || r.statusText);
403
+ editStatus.textContent = j.model_hint || '测试通过';
404
+ } catch (e) {
405
+ editStatus.classList.add('is-err');
406
+ editStatus.textContent = String(e);
407
+ }
408
+ });
409
+ }