@sheepbun/yips 0.1.1

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 (99) hide show
  1. package/dist/agent/commands/command-catalog.js +243 -0
  2. package/dist/agent/commands/commands.js +418 -0
  3. package/dist/agent/conductor.js +118 -0
  4. package/dist/agent/context/code-context.js +68 -0
  5. package/dist/agent/context/memory-store.js +159 -0
  6. package/dist/agent/context/session-store.js +211 -0
  7. package/dist/agent/protocol/tool-protocol.js +160 -0
  8. package/dist/agent/skills/skills.js +327 -0
  9. package/dist/agent/tools/tool-executor.js +415 -0
  10. package/dist/agent/tools/tool-safety.js +52 -0
  11. package/dist/app/index.js +35 -0
  12. package/dist/app/repl.js +105 -0
  13. package/dist/app/update-check.js +132 -0
  14. package/dist/app/version.js +51 -0
  15. package/dist/code-context.js +68 -0
  16. package/dist/colors.js +204 -0
  17. package/dist/command-catalog.js +242 -0
  18. package/dist/commands.js +350 -0
  19. package/dist/conductor.js +94 -0
  20. package/dist/config/config.js +335 -0
  21. package/dist/config/hooks.js +187 -0
  22. package/dist/config.js +335 -0
  23. package/dist/downloader-state.js +302 -0
  24. package/dist/downloader-ui.js +289 -0
  25. package/dist/gateway/adapters/discord.js +108 -0
  26. package/dist/gateway/adapters/formatting.js +96 -0
  27. package/dist/gateway/adapters/telegram.js +106 -0
  28. package/dist/gateway/adapters/types.js +2 -0
  29. package/dist/gateway/adapters/whatsapp.js +124 -0
  30. package/dist/gateway/auth-policy.js +66 -0
  31. package/dist/gateway/core.js +87 -0
  32. package/dist/gateway/headless-conductor.js +328 -0
  33. package/dist/gateway/message-router.js +23 -0
  34. package/dist/gateway/rate-limiter.js +48 -0
  35. package/dist/gateway/runtime/backend-policy.js +18 -0
  36. package/dist/gateway/runtime/discord-bot.js +104 -0
  37. package/dist/gateway/runtime/discord-main.js +69 -0
  38. package/dist/gateway/session-manager.js +77 -0
  39. package/dist/gateway/types.js +2 -0
  40. package/dist/hardware.js +92 -0
  41. package/dist/hooks.js +187 -0
  42. package/dist/index.js +34 -0
  43. package/dist/input-engine.js +250 -0
  44. package/dist/llama-client.js +227 -0
  45. package/dist/llama-server.js +620 -0
  46. package/dist/llm/llama-client.js +227 -0
  47. package/dist/llm/llama-server.js +620 -0
  48. package/dist/llm/token-counter.js +47 -0
  49. package/dist/memory-store.js +159 -0
  50. package/dist/messages.js +59 -0
  51. package/dist/model-downloader.js +382 -0
  52. package/dist/model-manager-state.js +118 -0
  53. package/dist/model-manager-ui.js +194 -0
  54. package/dist/model-manager.js +190 -0
  55. package/dist/models/hardware.js +92 -0
  56. package/dist/models/model-downloader.js +382 -0
  57. package/dist/models/model-manager.js +190 -0
  58. package/dist/prompt-box.js +78 -0
  59. package/dist/prompt-composer.js +498 -0
  60. package/dist/repl.js +105 -0
  61. package/dist/session-store.js +211 -0
  62. package/dist/spinner.js +76 -0
  63. package/dist/title-box.js +388 -0
  64. package/dist/token-counter.js +47 -0
  65. package/dist/tool-executor.js +415 -0
  66. package/dist/tool-protocol.js +121 -0
  67. package/dist/tool-safety.js +52 -0
  68. package/dist/tui/app.js +2553 -0
  69. package/dist/tui/startup.js +56 -0
  70. package/dist/tui-input-routing.js +53 -0
  71. package/dist/tui.js +51 -0
  72. package/dist/types/app-types.js +2 -0
  73. package/dist/types.js +2 -0
  74. package/dist/ui/colors.js +204 -0
  75. package/dist/ui/downloader/downloader-state.js +302 -0
  76. package/dist/ui/downloader/downloader-ui.js +289 -0
  77. package/dist/ui/input/input-engine.js +250 -0
  78. package/dist/ui/input/tui-input-routing.js +53 -0
  79. package/dist/ui/input/vt-session.js +168 -0
  80. package/dist/ui/messages.js +59 -0
  81. package/dist/ui/model-manager/model-manager-state.js +118 -0
  82. package/dist/ui/model-manager/model-manager-ui.js +194 -0
  83. package/dist/ui/prompt/prompt-box.js +78 -0
  84. package/dist/ui/prompt/prompt-composer.js +498 -0
  85. package/dist/ui/spinner.js +76 -0
  86. package/dist/ui/title-box.js +388 -0
  87. package/dist/ui/tui/app.js +6 -0
  88. package/dist/ui/tui/autocomplete.js +85 -0
  89. package/dist/ui/tui/constants.js +18 -0
  90. package/dist/ui/tui/history.js +29 -0
  91. package/dist/ui/tui/layout.js +341 -0
  92. package/dist/ui/tui/runtime-core.js +2584 -0
  93. package/dist/ui/tui/runtime-utils.js +53 -0
  94. package/dist/ui/tui/start-tui.js +54 -0
  95. package/dist/ui/tui/startup.js +56 -0
  96. package/dist/version.js +51 -0
  97. package/dist/vt-session.js +168 -0
  98. package/install.sh +457 -0
  99. package/package.json +128 -0
package/dist/config.js ADDED
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONFIG_PATH_ENV_VAR = exports.DEFAULT_CONFIG_PATH = void 0;
4
+ exports.getDefaultConfig = getDefaultConfig;
5
+ exports.resolveConfigPath = resolveConfigPath;
6
+ exports.mergeConfig = mergeConfig;
7
+ exports.loadConfig = loadConfig;
8
+ exports.saveConfig = saveConfig;
9
+ exports.updateConfig = updateConfig;
10
+ const node_fs_1 = require("node:fs");
11
+ const promises_1 = require("node:fs/promises");
12
+ const node_os_1 = require("node:os");
13
+ const node_path_1 = require("node:path");
14
+ const SUPPORTED_BACKENDS = new Set(["llamacpp", "claude"]);
15
+ const PORT_CONFLICT_POLICIES = new Set([
16
+ "fail",
17
+ "kill-llama",
18
+ "kill-user"
19
+ ]);
20
+ const SUPPORTED_HOOKS = [
21
+ "on-session-start",
22
+ "on-session-end",
23
+ "on-file-write",
24
+ "on-file-read",
25
+ "pre-commit"
26
+ ];
27
+ const DEFAULT_HOOK_TIMEOUT_MS = 10_000;
28
+ const MAX_HOOK_TIMEOUT_MS = 120_000;
29
+ exports.DEFAULT_CONFIG_PATH = ".yips_config.json";
30
+ exports.CONFIG_PATH_ENV_VAR = "YIPS_CONFIG_PATH";
31
+ function getDefaultConfig() {
32
+ const llamaHost = "127.0.0.1";
33
+ const llamaPort = 8080;
34
+ return {
35
+ streaming: true,
36
+ verbose: false,
37
+ backend: "llamacpp",
38
+ llamaBaseUrl: buildBaseUrl(llamaHost, llamaPort),
39
+ llamaServerPath: "",
40
+ llamaModelsDir: (0, node_path_1.resolve)((0, node_os_1.homedir)(), ".yips", "models"),
41
+ llamaHost,
42
+ llamaPort,
43
+ llamaContextSize: 8192,
44
+ llamaGpuLayers: 999,
45
+ llamaAutoStart: true,
46
+ llamaPortConflictPolicy: "kill-user",
47
+ model: "default",
48
+ tokensMode: "auto",
49
+ tokensManualMax: 8192,
50
+ nicknames: {},
51
+ hooks: {}
52
+ };
53
+ }
54
+ function resolveConfigPath(configPath = exports.DEFAULT_CONFIG_PATH) {
55
+ if (configPath === exports.DEFAULT_CONFIG_PATH) {
56
+ const envPath = process.env[exports.CONFIG_PATH_ENV_VAR]?.trim();
57
+ if (envPath && envPath.length > 0) {
58
+ return (0, node_path_1.resolve)(envPath);
59
+ }
60
+ }
61
+ return (0, node_path_1.resolve)(process.cwd(), configPath);
62
+ }
63
+ function isRecord(value) {
64
+ return typeof value === "object" && value !== null;
65
+ }
66
+ function normalizeBoolean(value, fallback) {
67
+ return typeof value === "boolean" ? value : fallback;
68
+ }
69
+ function normalizeBackend(value, fallback) {
70
+ if (typeof value === "string" && SUPPORTED_BACKENDS.has(value)) {
71
+ return value;
72
+ }
73
+ return fallback;
74
+ }
75
+ function normalizeModel(value, fallback) {
76
+ if (typeof value !== "string") {
77
+ return fallback;
78
+ }
79
+ const trimmed = value.trim();
80
+ return trimmed.length > 0 ? trimmed : fallback;
81
+ }
82
+ function normalizeBaseUrl(value, fallback) {
83
+ if (typeof value !== "string") {
84
+ return fallback;
85
+ }
86
+ const trimmed = value.trim();
87
+ if (trimmed.length === 0) {
88
+ return fallback;
89
+ }
90
+ try {
91
+ const parsed = new URL(trimmed);
92
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
93
+ return fallback;
94
+ }
95
+ return parsed.toString().replace(/\/$/, "");
96
+ }
97
+ catch {
98
+ return fallback;
99
+ }
100
+ }
101
+ function parseBoolean(value, fallback) {
102
+ if (value === undefined) {
103
+ return fallback;
104
+ }
105
+ const normalized = value.trim().toLowerCase();
106
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
107
+ return true;
108
+ }
109
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
110
+ return false;
111
+ }
112
+ return fallback;
113
+ }
114
+ function normalizeString(value, fallback) {
115
+ if (typeof value !== "string") {
116
+ return fallback;
117
+ }
118
+ const trimmed = value.trim();
119
+ return trimmed.length > 0 ? trimmed : fallback;
120
+ }
121
+ function normalizeHost(value, fallback) {
122
+ return normalizeString(value, fallback);
123
+ }
124
+ function normalizePort(value, fallback) {
125
+ if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
126
+ return value;
127
+ }
128
+ if (typeof value === "string") {
129
+ const parsed = Number(value.trim());
130
+ if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
131
+ return parsed;
132
+ }
133
+ }
134
+ return fallback;
135
+ }
136
+ function normalizePositiveInt(value, fallback) {
137
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
138
+ return value;
139
+ }
140
+ if (typeof value === "string") {
141
+ const parsed = Number(value.trim());
142
+ if (Number.isInteger(parsed) && parsed > 0) {
143
+ return parsed;
144
+ }
145
+ }
146
+ return fallback;
147
+ }
148
+ function normalizePositiveIntWithMax(value, fallback, max) {
149
+ const normalized = normalizePositiveInt(value, fallback);
150
+ return Math.min(normalized, max);
151
+ }
152
+ function normalizePortConflictPolicy(value, fallback) {
153
+ if (typeof value === "string" && PORT_CONFLICT_POLICIES.has(value)) {
154
+ return value;
155
+ }
156
+ return fallback;
157
+ }
158
+ function normalizeTokensMode(value, fallback) {
159
+ if (value === "auto" || value === "manual") {
160
+ return value;
161
+ }
162
+ return fallback;
163
+ }
164
+ function buildBaseUrl(host, port) {
165
+ return `http://${host}:${port}`;
166
+ }
167
+ function parseBaseUrlHostPort(baseUrl) {
168
+ try {
169
+ const parsed = new URL(baseUrl);
170
+ if (!parsed.hostname || !parsed.port) {
171
+ return null;
172
+ }
173
+ const port = Number(parsed.port);
174
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
175
+ return null;
176
+ }
177
+ return {
178
+ host: parsed.hostname,
179
+ port
180
+ };
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ function normalizeNicknames(value, fallback) {
187
+ if (!isRecord(value)) {
188
+ return { ...fallback };
189
+ }
190
+ const next = {};
191
+ for (const [key, nickname] of Object.entries(value)) {
192
+ if (typeof key !== "string" || typeof nickname !== "string") {
193
+ continue;
194
+ }
195
+ const trimmedKey = key.trim();
196
+ const trimmedNickname = nickname.trim();
197
+ if (trimmedKey.length === 0 || trimmedNickname.length === 0) {
198
+ continue;
199
+ }
200
+ next[trimmedKey] = trimmedNickname;
201
+ }
202
+ return next;
203
+ }
204
+ function normalizeHookConfig(value) {
205
+ if (!isRecord(value)) {
206
+ return null;
207
+ }
208
+ const command = normalizeString(value.command, "");
209
+ if (command.length === 0) {
210
+ return null;
211
+ }
212
+ const timeoutMs = normalizePositiveIntWithMax(value.timeoutMs, DEFAULT_HOOK_TIMEOUT_MS, MAX_HOOK_TIMEOUT_MS);
213
+ return { command, timeoutMs };
214
+ }
215
+ function normalizeHooks(value, fallback) {
216
+ const next = {};
217
+ for (const hookName of SUPPORTED_HOOKS) {
218
+ const existing = fallback[hookName];
219
+ if (existing) {
220
+ next[hookName] = { ...existing };
221
+ }
222
+ }
223
+ if (!isRecord(value)) {
224
+ return next;
225
+ }
226
+ for (const hookName of SUPPORTED_HOOKS) {
227
+ const normalized = normalizeHookConfig(value[hookName]);
228
+ if (normalized) {
229
+ next[hookName] = normalized;
230
+ }
231
+ }
232
+ return next;
233
+ }
234
+ function applyEnvOverrides(config) {
235
+ const envHost = normalizeHost(process.env["YIPS_LLAMA_HOST"], config.llamaHost);
236
+ const envPort = normalizePort(process.env["YIPS_LLAMA_PORT"], config.llamaPort);
237
+ const envBaseUrl = normalizeBaseUrl(process.env["YIPS_LLAMA_BASE_URL"], buildBaseUrl(envHost, envPort));
238
+ const envBaseUrlHostPort = parseBaseUrlHostPort(envBaseUrl);
239
+ return {
240
+ ...config,
241
+ llamaHost: envBaseUrlHostPort?.host ?? envHost,
242
+ llamaPort: envBaseUrlHostPort?.port ?? envPort,
243
+ llamaBaseUrl: envBaseUrl,
244
+ llamaServerPath: normalizeString(process.env["YIPS_LLAMA_SERVER_PATH"], config.llamaServerPath),
245
+ llamaModelsDir: normalizeString(process.env["YIPS_LLAMA_MODELS_DIR"], config.llamaModelsDir),
246
+ llamaContextSize: normalizePositiveInt(process.env["YIPS_LLAMA_CONTEXT_SIZE"], config.llamaContextSize),
247
+ llamaGpuLayers: normalizePositiveInt(process.env["YIPS_LLAMA_GPU_LAYERS"], config.llamaGpuLayers),
248
+ llamaAutoStart: parseBoolean(process.env["YIPS_LLAMA_AUTO_START"], config.llamaAutoStart),
249
+ llamaPortConflictPolicy: normalizePortConflictPolicy(process.env["YIPS_LLAMA_PORT_CONFLICT_POLICY"], config.llamaPortConflictPolicy),
250
+ model: normalizeModel(process.env["YIPS_MODEL"], config.model),
251
+ tokensMode: normalizeTokensMode(process.env["YIPS_TOKENS_MODE"], config.tokensMode),
252
+ tokensManualMax: normalizePositiveInt(process.env["YIPS_TOKENS_MANUAL_MAX"], config.tokensManualMax),
253
+ hooks: normalizeHooks(config.hooks, config.hooks)
254
+ };
255
+ }
256
+ function mergeConfig(defaults, candidate) {
257
+ if (!isRecord(candidate)) {
258
+ return defaults;
259
+ }
260
+ const llamaHost = normalizeHost(candidate.llamaHost, defaults.llamaHost);
261
+ const llamaPort = normalizePort(candidate.llamaPort, defaults.llamaPort);
262
+ const llamaBaseUrl = normalizeBaseUrl(candidate.llamaBaseUrl, buildBaseUrl(llamaHost, llamaPort));
263
+ const baseUrlHostPort = parseBaseUrlHostPort(llamaBaseUrl);
264
+ return {
265
+ streaming: normalizeBoolean(candidate.streaming, defaults.streaming),
266
+ verbose: normalizeBoolean(candidate.verbose, defaults.verbose),
267
+ backend: normalizeBackend(candidate.backend, defaults.backend),
268
+ llamaBaseUrl,
269
+ llamaServerPath: normalizeString(candidate.llamaServerPath, defaults.llamaServerPath),
270
+ llamaModelsDir: normalizeString(candidate.llamaModelsDir, defaults.llamaModelsDir),
271
+ llamaHost: baseUrlHostPort?.host ?? llamaHost,
272
+ llamaPort: baseUrlHostPort?.port ?? llamaPort,
273
+ llamaContextSize: normalizePositiveInt(candidate.llamaContextSize, defaults.llamaContextSize),
274
+ llamaGpuLayers: normalizePositiveInt(candidate.llamaGpuLayers, defaults.llamaGpuLayers),
275
+ llamaAutoStart: normalizeBoolean(candidate.llamaAutoStart, defaults.llamaAutoStart),
276
+ llamaPortConflictPolicy: normalizePortConflictPolicy(candidate.llamaPortConflictPolicy, defaults.llamaPortConflictPolicy),
277
+ model: normalizeModel(candidate.model, defaults.model),
278
+ tokensMode: normalizeTokensMode(candidate.tokensMode, defaults.tokensMode),
279
+ tokensManualMax: normalizePositiveInt(candidate.tokensManualMax, defaults.tokensManualMax),
280
+ nicknames: normalizeNicknames(candidate.nicknames, defaults.nicknames),
281
+ hooks: normalizeHooks(candidate.hooks, defaults.hooks)
282
+ };
283
+ }
284
+ async function loadConfig(configPath = exports.DEFAULT_CONFIG_PATH) {
285
+ const defaults = getDefaultConfig();
286
+ const path = resolveConfigPath(configPath);
287
+ const legacyDefaultPath = (0, node_path_1.resolve)(process.cwd(), exports.DEFAULT_CONFIG_PATH);
288
+ const candidatePaths = [path];
289
+ if (configPath === exports.DEFAULT_CONFIG_PATH && path !== legacyDefaultPath) {
290
+ candidatePaths.push(legacyDefaultPath);
291
+ }
292
+ let readablePath = null;
293
+ for (const candidatePath of candidatePaths) {
294
+ try {
295
+ await (0, promises_1.access)(candidatePath, node_fs_1.constants.R_OK);
296
+ readablePath = candidatePath;
297
+ break;
298
+ }
299
+ catch {
300
+ // keep trying
301
+ }
302
+ }
303
+ if (!readablePath) {
304
+ return { config: applyEnvOverrides(defaults), path, source: "default" };
305
+ }
306
+ try {
307
+ const rawConfig = await (0, promises_1.readFile)(readablePath, "utf8");
308
+ const parsedConfig = JSON.parse(rawConfig);
309
+ return {
310
+ config: applyEnvOverrides(mergeConfig(defaults, parsedConfig)),
311
+ path: readablePath,
312
+ source: "file"
313
+ };
314
+ }
315
+ catch (error) {
316
+ const message = error instanceof Error ? error.message : String(error);
317
+ return {
318
+ config: applyEnvOverrides(defaults),
319
+ path: readablePath,
320
+ source: "default",
321
+ warning: `Failed to load config at ${readablePath}: ${message}`
322
+ };
323
+ }
324
+ }
325
+ async function saveConfig(config, configPath = exports.DEFAULT_CONFIG_PATH) {
326
+ const path = resolveConfigPath(configPath);
327
+ const normalized = mergeConfig(getDefaultConfig(), config);
328
+ await (0, promises_1.writeFile)(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
329
+ }
330
+ async function updateConfig(patch, configPath = exports.DEFAULT_CONFIG_PATH) {
331
+ const loaded = await loadConfig(configPath);
332
+ const merged = mergeConfig(getDefaultConfig(), { ...loaded.config, ...patch });
333
+ await saveConfig(merged, configPath);
334
+ return merged;
335
+ }
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DOWNLOADER_TABS = void 0;
4
+ exports.tabToSort = tabToSort;
5
+ exports.createDownloaderState = createDownloaderState;
6
+ exports.cycleTab = cycleTab;
7
+ exports.setModels = setModels;
8
+ exports.setFiles = setFiles;
9
+ exports.setLoading = setLoading;
10
+ exports.setLoadingModels = setLoadingModels;
11
+ exports.setLoadingFiles = setLoadingFiles;
12
+ exports.startDownload = startDownload;
13
+ exports.updateDownloadProgress = updateDownloadProgress;
14
+ exports.finishDownload = finishDownload;
15
+ exports.setPreloadingTabs = setPreloadingTabs;
16
+ exports.setCachedModels = setCachedModels;
17
+ exports.getCachedModels = getCachedModels;
18
+ exports.resetModelCache = resetModelCache;
19
+ exports.setError = setError;
20
+ exports.setDownloaderError = setDownloaderError;
21
+ exports.closeFileView = closeFileView;
22
+ exports.openCancelConfirm = openCancelConfirm;
23
+ exports.closeCancelConfirm = closeCancelConfirm;
24
+ exports.moveModelSelection = moveModelSelection;
25
+ exports.moveFileSelection = moveFileSelection;
26
+ exports.DOWNLOADER_TABS = ["Most Downloaded", "Top Rated", "Newest"];
27
+ function tabToSort(tab) {
28
+ if (tab === "Top Rated") {
29
+ return "trendingScore";
30
+ }
31
+ if (tab === "Newest") {
32
+ return "lastModified";
33
+ }
34
+ return "downloads";
35
+ }
36
+ function createDownloaderState(memory) {
37
+ return {
38
+ isOpen: true,
39
+ view: "models",
40
+ tab: "Most Downloaded",
41
+ searchQuery: "",
42
+ models: [],
43
+ files: [],
44
+ selectedModelIndex: 0,
45
+ selectedFileIndex: 0,
46
+ modelScrollOffset: 0,
47
+ fileScrollOffset: 0,
48
+ phase: "idle",
49
+ loading: false,
50
+ loadingMessage: "Loading models...",
51
+ errorMessage: "",
52
+ download: null,
53
+ selectedRepoId: "",
54
+ ramGb: memory.ramGb,
55
+ vramGb: memory.vramGb,
56
+ totalMemoryGb: memory.totalMemoryGb,
57
+ diskFreeGb: memory.diskFreeGb ?? 0,
58
+ cacheQuery: "",
59
+ modelCacheByTab: {},
60
+ preloadingTabs: false,
61
+ cancelConfirmOpen: false
62
+ };
63
+ }
64
+ function cycleTab(state, direction) {
65
+ const currentIndex = exports.DOWNLOADER_TABS.indexOf(state.tab);
66
+ const nextIndex = (currentIndex + direction + exports.DOWNLOADER_TABS.length) % exports.DOWNLOADER_TABS.length;
67
+ return {
68
+ ...state,
69
+ tab: exports.DOWNLOADER_TABS[nextIndex] ?? "Most Downloaded",
70
+ selectedModelIndex: 0,
71
+ modelScrollOffset: 0
72
+ };
73
+ }
74
+ function setModels(state, models) {
75
+ return {
76
+ ...state,
77
+ models,
78
+ selectedModelIndex: models.length > 0 ? Math.min(state.selectedModelIndex, models.length - 1) : 0,
79
+ modelScrollOffset: 0,
80
+ phase: "idle",
81
+ loading: false,
82
+ errorMessage: "",
83
+ download: null,
84
+ cancelConfirmOpen: false
85
+ };
86
+ }
87
+ function setFiles(state, repoId, files) {
88
+ return {
89
+ ...state,
90
+ selectedRepoId: repoId,
91
+ view: "files",
92
+ files,
93
+ selectedFileIndex: 0,
94
+ fileScrollOffset: 0,
95
+ phase: "idle",
96
+ loading: false,
97
+ errorMessage: "",
98
+ download: null,
99
+ cancelConfirmOpen: false
100
+ };
101
+ }
102
+ function setLoading(state, message) {
103
+ return {
104
+ ...state,
105
+ phase: "loading-models",
106
+ loading: true,
107
+ loadingMessage: message,
108
+ errorMessage: "",
109
+ download: null,
110
+ cancelConfirmOpen: false
111
+ };
112
+ }
113
+ function setLoadingModels(state, message) {
114
+ return {
115
+ ...state,
116
+ phase: "loading-models",
117
+ loading: true,
118
+ loadingMessage: message,
119
+ errorMessage: "",
120
+ download: null,
121
+ cancelConfirmOpen: false
122
+ };
123
+ }
124
+ function setLoadingFiles(state, message) {
125
+ return {
126
+ ...state,
127
+ phase: "loading-files",
128
+ loading: true,
129
+ loadingMessage: message,
130
+ errorMessage: "",
131
+ download: null,
132
+ cancelConfirmOpen: false
133
+ };
134
+ }
135
+ function startDownload(state, repoId, filename, statusText) {
136
+ const now = Date.now();
137
+ return {
138
+ ...state,
139
+ phase: "downloading",
140
+ loading: true,
141
+ loadingMessage: statusText,
142
+ errorMessage: "",
143
+ download: {
144
+ repoId,
145
+ filename,
146
+ bytesDownloaded: 0,
147
+ totalBytes: null,
148
+ startedAtMs: now,
149
+ lastUpdateAtMs: now,
150
+ statusText
151
+ },
152
+ cancelConfirmOpen: false
153
+ };
154
+ }
155
+ function updateDownloadProgress(state, update) {
156
+ if (state.phase !== "downloading" || !state.download) {
157
+ return state;
158
+ }
159
+ return {
160
+ ...state,
161
+ loadingMessage: update.statusText,
162
+ download: {
163
+ ...state.download,
164
+ bytesDownloaded: Math.max(0, update.bytesDownloaded),
165
+ totalBytes: update.totalBytes,
166
+ lastUpdateAtMs: Date.now(),
167
+ statusText: update.statusText
168
+ }
169
+ };
170
+ }
171
+ function finishDownload(state) {
172
+ return {
173
+ ...state,
174
+ phase: "idle",
175
+ loading: false,
176
+ loadingMessage: "",
177
+ errorMessage: "",
178
+ download: null,
179
+ cancelConfirmOpen: false
180
+ };
181
+ }
182
+ function setPreloadingTabs(state, preloadingTabs) {
183
+ return {
184
+ ...state,
185
+ preloadingTabs
186
+ };
187
+ }
188
+ function setCachedModels(state, tab, query, models) {
189
+ const normalizedQuery = query.trim();
190
+ const cache = state.cacheQuery === normalizedQuery ? state.modelCacheByTab : {};
191
+ return {
192
+ ...state,
193
+ cacheQuery: normalizedQuery,
194
+ modelCacheByTab: {
195
+ ...cache,
196
+ [tab]: models
197
+ }
198
+ };
199
+ }
200
+ function getCachedModels(state, tab, query) {
201
+ if (state.cacheQuery !== query.trim()) {
202
+ return null;
203
+ }
204
+ return state.modelCacheByTab[tab] ?? null;
205
+ }
206
+ function resetModelCache(state, query) {
207
+ return {
208
+ ...state,
209
+ cacheQuery: query.trim(),
210
+ modelCacheByTab: {}
211
+ };
212
+ }
213
+ function setError(state, message) {
214
+ return {
215
+ ...state,
216
+ phase: "error",
217
+ loading: false,
218
+ errorMessage: message,
219
+ download: null,
220
+ cancelConfirmOpen: false
221
+ };
222
+ }
223
+ function setDownloaderError(state, message) {
224
+ return {
225
+ ...state,
226
+ phase: "error",
227
+ loading: false,
228
+ errorMessage: message,
229
+ download: null,
230
+ cancelConfirmOpen: false
231
+ };
232
+ }
233
+ function closeFileView(state) {
234
+ return {
235
+ ...state,
236
+ view: "models",
237
+ files: [],
238
+ selectedFileIndex: 0,
239
+ fileScrollOffset: 0,
240
+ phase: "idle",
241
+ loading: false,
242
+ errorMessage: "",
243
+ download: null,
244
+ cancelConfirmOpen: false
245
+ };
246
+ }
247
+ function openCancelConfirm(state) {
248
+ if (state.phase !== "downloading" || !state.download) {
249
+ return state;
250
+ }
251
+ return {
252
+ ...state,
253
+ cancelConfirmOpen: true
254
+ };
255
+ }
256
+ function closeCancelConfirm(state) {
257
+ if (!state.cancelConfirmOpen) {
258
+ return state;
259
+ }
260
+ return {
261
+ ...state,
262
+ cancelConfirmOpen: false
263
+ };
264
+ }
265
+ function moveModelSelection(state, delta, windowSize) {
266
+ if (state.models.length === 0) {
267
+ return state;
268
+ }
269
+ const max = state.models.length - 1;
270
+ const nextIndex = Math.max(0, Math.min(max, state.selectedModelIndex + delta));
271
+ let nextOffset = state.modelScrollOffset;
272
+ if (nextIndex < nextOffset) {
273
+ nextOffset = nextIndex;
274
+ }
275
+ if (nextIndex >= nextOffset + windowSize) {
276
+ nextOffset = nextIndex - windowSize + 1;
277
+ }
278
+ return {
279
+ ...state,
280
+ selectedModelIndex: nextIndex,
281
+ modelScrollOffset: Math.max(0, nextOffset)
282
+ };
283
+ }
284
+ function moveFileSelection(state, delta, windowSize) {
285
+ if (state.files.length === 0) {
286
+ return state;
287
+ }
288
+ const max = state.files.length - 1;
289
+ const nextIndex = Math.max(0, Math.min(max, state.selectedFileIndex + delta));
290
+ let nextOffset = state.fileScrollOffset;
291
+ if (nextIndex < nextOffset) {
292
+ nextOffset = nextIndex;
293
+ }
294
+ if (nextIndex >= nextOffset + windowSize) {
295
+ nextOffset = nextIndex - windowSize + 1;
296
+ }
297
+ return {
298
+ ...state,
299
+ selectedFileIndex: nextIndex,
300
+ fileScrollOffset: Math.max(0, nextOffset)
301
+ };
302
+ }