tagword-codeagent 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codeagent/__init__.py +10 -0
- codeagent/__main__.py +13 -0
- codeagent/cli/__init__.py +13 -0
- codeagent/cli/entrypoint.py +161 -0
- codeagent/cli/main.py +979 -0
- codeagent/core/__init__.py +2 -0
- codeagent/core/attachments.py +342 -0
- codeagent/core/audio_models.py +50 -0
- codeagent/core/bootstrap.py +178 -0
- codeagent/core/default_presets.py +159 -0
- codeagent/core/env.py +106 -0
- codeagent/core/folder_picker.py +151 -0
- codeagent/core/image_understanding.py +90 -0
- codeagent/core/models.py +6 -0
- codeagent/core/paths.py +238 -0
- codeagent/core/pricing.py +107 -0
- codeagent/core/process_ports.py +258 -0
- codeagent/core/seed_bridge.py +21 -0
- codeagent/core/settings.py +98 -0
- codeagent/core/speech_synth.py +330 -0
- codeagent/core/team_config.py +247 -0
- codeagent/core/team_manager.py +245 -0
- codeagent/core/token_counter.py +41 -0
- codeagent/core/tts_voices.py +103 -0
- codeagent/core/usage_billing.py +72 -0
- codeagent/core/vision_models.py +93 -0
- codeagent/memory/diary.py +144 -0
- codeagent/persona_defaults/README.md +12 -0
- codeagent/persona_defaults/agent.md +352 -0
- codeagent/persona_defaults/identity.md +29 -0
- codeagent/persona_defaults/memory.md +4 -0
- codeagent/persona_defaults/skills.md +325 -0
- codeagent/persona_defaults/soul.md +38 -0
- codeagent/persona_defaults/tools.md +41 -0
- codeagent/persona_defaults/user.md +18 -0
- codeagent/runtime/__init__.py +11 -0
- codeagent/runtime/agent_context.py +6 -0
- codeagent/runtime/agent_runtime.py +21 -0
- codeagent/runtime/compact_prompt.py +9 -0
- codeagent/runtime/compact_state.py +40 -0
- codeagent/runtime/llm_worker.py +132 -0
- codeagent/runtime/orchestrator.py +161 -0
- codeagent/runtime/prompt_enrichment.py +324 -0
- codeagent/runtime/session_manager.py +30 -0
- codeagent/runtime/task_split.py +38 -0
- codeagent/runtime/turn_loop.py +13 -0
- codeagent/runtime/worker.py +48 -0
- codeagent/server/__init__.py +505 -0
- codeagent/server/app_factory.py +1292 -0
- codeagent/server/attachment_api.py +261 -0
- codeagent/server/hub.py +175 -0
- codeagent/server/icon.png +0 -0
- codeagent/server/self_healing.py +143 -0
- codeagent/server/team_engine.py +243 -0
- codeagent/server/tts_api.py +51 -0
- codeagent/server/webui_api_app.py +3180 -0
- codeagent/skills/__init__.py +2 -0
- codeagent/skills/select.py +144 -0
- codeagent/tools/__init__.py +6 -0
- codeagent/tools/agent_tools.py +10 -0
- codeagent/web/__init__.py +1 -0
- codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
- codeagent/web/auth_impl.py +326 -0
- codeagent/web/static/00-marked.min.js +6 -0
- codeagent/web/static/00-mermaid.min.js +2314 -0
- codeagent/web/static/00-purify.min.js +3 -0
- codeagent/web/static/00-storage.js +83 -0
- codeagent/web/static/00-utils.js +51 -0
- codeagent/web/static/01-theme.css +96 -0
- codeagent/web/static/01a-scroll.js +86 -0
- codeagent/web/static/01b-think.js +62 -0
- codeagent/web/static/01c-session-identity.js +81 -0
- codeagent/web/static/01c-session-thinking.js +61 -0
- codeagent/web/static/01c-session-tree-menus.js +349 -0
- codeagent/web/static/01c-session-tree.js +531 -0
- codeagent/web/static/01c-session-utilities.js +83 -0
- codeagent/web/static/01c-vision-model.js +132 -0
- codeagent/web/static/01d-image-gen-model.js +84 -0
- codeagent/web/static/01e-audio-model.js +97 -0
- codeagent/web/static/01f-compose-ui.js +88 -0
- codeagent/web/static/01g-tts-settings.js +128 -0
- codeagent/web/static/01h-music-model.js +85 -0
- codeagent/web/static/01i-model-stack.js +291 -0
- codeagent/web/static/01j-video-gen-model.js +85 -0
- codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
- codeagent/web/static/01k-model-stack-state.js +209 -0
- codeagent/web/static/01q-inflight.js +195 -0
- codeagent/web/static/02-reset.css +22 -0
- codeagent/web/static/02-session-ui.js +201 -0
- codeagent/web/static/03-layout-p1.css +2 -0
- codeagent/web/static/03-layout-p2.css +2 -0
- codeagent/web/static/03a-links-stream.js +88 -0
- codeagent/web/static/03b-stream-text.js +119 -0
- codeagent/web/static/03c-render-bubble.js +520 -0
- codeagent/web/static/03d-agent-bubble.js +126 -0
- codeagent/web/static/03e-tool-trace.js +182 -0
- codeagent/web/static/03f-timeline.js +54 -0
- codeagent/web/static/03g-bubble-tts.js +292 -0
- codeagent/web/static/04-components-p1.css +1 -0
- codeagent/web/static/04-components-p2b.css +1 -0
- codeagent/web/static/04-components-p3.css +2 -0
- codeagent/web/static/04-components-p4.css +1 -0
- codeagent/web/static/04-components-p5.css +1 -0
- codeagent/web/static/04-components-p6.css +58 -0
- codeagent/web/static/04-components-p7.css +4 -0
- codeagent/web/static/04-components-p8.css +1 -0
- codeagent/web/static/04-components-p9.css +1 -0
- codeagent/web/static/04-ws-connect.js +351 -0
- codeagent/web/static/04-ws-helpers.js +116 -0
- codeagent/web/static/05-chat-p1.css +1058 -0
- codeagent/web/static/05-chat-p2.css +1 -0
- codeagent/web/static/05-chat-p3.css +295 -0
- codeagent/web/static/05a-session-history.js +176 -0
- codeagent/web/static/05b-session-list.js +114 -0
- codeagent/web/static/05c-dialog.js +123 -0
- codeagent/web/static/05d-project-create.js +172 -0
- codeagent/web/static/05e-project-remote.js +143 -0
- codeagent/web/static/06-chat.js +552 -0
- codeagent/web/static/06-utilities.css +181 -0
- codeagent/web/static/06b-attachments.js +304 -0
- codeagent/web/static/06c-camera.js +488 -0
- codeagent/web/static/07-activity-bar.css +127 -0
- codeagent/web/static/09-git-p1.css +1 -0
- codeagent/web/static/09-git-p2.css +1 -0
- codeagent/web/static/09a-init-bind.js +110 -0
- codeagent/web/static/09b-panel-toggle.js +80 -0
- codeagent/web/static/09c-api-refresh.js +100 -0
- codeagent/web/static/09d-status-log.js +80 -0
- codeagent/web/static/09e-commit.js +80 -0
- codeagent/web/static/09f-remote.js +93 -0
- codeagent/web/static/11a-cron-parse.js +117 -0
- codeagent/web/static/11b-cron-panel.js +100 -0
- codeagent/web/static/11c-cron-card.js +133 -0
- codeagent/web/static/11d-cron-form.js +105 -0
- codeagent/web/static/11e-env-config.js +168 -0
- codeagent/web/static/11f-env-llm-a-state.js +136 -0
- codeagent/web/static/11f-env-llm-b-form.js +409 -0
- codeagent/web/static/11f-env-llm-c-board.js +322 -0
- codeagent/web/static/11g-env-paths-git.js +973 -0
- codeagent/web/static/11h-env-mcp-a-state.js +161 -0
- codeagent/web/static/11h-env-mcp-b-form.js +229 -0
- codeagent/web/static/11h-env-mcp-c-board.js +565 -0
- codeagent/web/static/12-plugins.js +127 -0
- codeagent/web/static/13a-skills.js +156 -0
- codeagent/web/static/13a-todo-utils.js +140 -0
- codeagent/web/static/13b-todo-fetch.js +106 -0
- codeagent/web/static/13b-tools.js +127 -0
- codeagent/web/static/13c-todo-crud.js +92 -0
- codeagent/web/static/14-activity-bar.js +177 -0
- codeagent/web/static/15-plans.css +194 -0
- codeagent/web/static/15-plans.js +219 -0
- codeagent/web/static/16-mobile.css +612 -0
- codeagent/web/static/16-mobile.js +178 -0
- codeagent/web/static/17-preset-ux.css +287 -0
- codeagent/web/static/18-agent-mgr.css +616 -0
- codeagent/web/static/18-agent-mgr.js +204 -0
- codeagent/web/static/24-health-dashboard.js +68 -0
- codeagent/web/static/99-brand-polish.css +152 -0
- codeagent/web/static/body.html +824 -0
- codeagent/web/static/setup/setup_form.html +171 -0
- codeagent/web/static/setup/setup_head.html +100 -0
- codeagent/web/static/setup/setup_tail.html +264 -0
- codeagent/web/static/setup/setup_test.html +38 -0
- codeagent/web/web_login.html +265 -0
- codeagent/web/webui.html +35 -0
- tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
- tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
- tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
- tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
- tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
- tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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
|
+
})();
|