@pellux/goodvibes-tui 0.19.33 → 0.19.34
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.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +343 -0
- package/src/input/commands/tts-runtime.ts +93 -10
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +1 -0
- package/src/input/handler-feed.ts +6 -0
- package/src/input/handler-modal-routes.ts +23 -10
- package/src/input/handler-modal-token-routes.ts +9 -0
- package/src/input/handler-onboarding-cloudflare.ts +391 -0
- package/src/input/handler-onboarding.ts +33 -0
- package/src/input/handler-picker-routes.ts +1 -1
- package/src/input/handler.ts +4 -1
- package/src/input/model-picker-types.ts +125 -0
- package/src/input/model-picker.ts +144 -135
- package/src/input/onboarding/onboarding-wizard-apply.ts +81 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +449 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +199 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +7 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +8 -0
- package/src/input/settings-modal-types.ts +2 -1
- package/src/input/settings-modal.ts +30 -8
- package/src/renderer/buffer.ts +40 -2
- package/src/renderer/compositor.ts +25 -17
- package/src/renderer/model-picker-overlay.ts +70 -0
- package/src/renderer/settings-modal-helpers.ts +1 -0
- package/src/runtime/cloudflare-control-plane.ts +328 -0
- package/src/runtime/onboarding/derivation.ts +25 -0
- package/src/runtime/onboarding/snapshot.ts +2 -0
- package/src/runtime/onboarding/types.ts +5 -1
- package/src/shell/ui-openers.ts +10 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers/registry';
|
|
2
|
+
|
|
3
|
+
export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Which config keys the model picker writes to on commit.
|
|
7
|
+
* 'main' -> provider.provider + provider.model (default)
|
|
8
|
+
* 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
|
|
9
|
+
* 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
|
|
10
|
+
* 'tts' -> tts.llmProvider + tts.llmModel
|
|
11
|
+
*/
|
|
12
|
+
export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pricing tier filter.
|
|
16
|
+
* 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
|
|
17
|
+
* with future CatalogModel tiers ('free' | 'paid' | 'subscription').
|
|
18
|
+
*/
|
|
19
|
+
export type CategoryFilter = 'all' | 'free' | 'paid' | 'subscription';
|
|
20
|
+
|
|
21
|
+
export type ModelFamily =
|
|
22
|
+
| 'GPT'
|
|
23
|
+
| 'Claude'
|
|
24
|
+
| 'Gemini'
|
|
25
|
+
| 'Llama'
|
|
26
|
+
| 'Qwen'
|
|
27
|
+
| 'GLM'
|
|
28
|
+
| 'MiniMax'
|
|
29
|
+
| 'DeepSeek'
|
|
30
|
+
| 'Mistral'
|
|
31
|
+
| 'Command'
|
|
32
|
+
| 'Grok'
|
|
33
|
+
| 'Kimi'
|
|
34
|
+
| 'Other';
|
|
35
|
+
|
|
36
|
+
export type CapabilityFilter = 'reasoning' | 'toolUse' | 'multimodal' | 'none';
|
|
37
|
+
export type BenchmarkSort = 'none' | 'composite' | 'swe' | 'gpqa';
|
|
38
|
+
export type GroupByMode = 'provider' | 'family' | 'pricingTier' | 'qualityTier';
|
|
39
|
+
|
|
40
|
+
const FAMILY_PATTERNS: Array<{ pattern: RegExp; family: ModelFamily }> = [
|
|
41
|
+
{ pattern: /claude/i, family: 'Claude' },
|
|
42
|
+
{ pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
|
|
43
|
+
{ pattern: /gemini/i, family: 'Gemini' },
|
|
44
|
+
{ pattern: /llama/i, family: 'Llama' },
|
|
45
|
+
{ pattern: /qwen/i, family: 'Qwen' },
|
|
46
|
+
{ pattern: /glm|chatglm/i, family: 'GLM' },
|
|
47
|
+
{ pattern: /minimax|abab/i, family: 'MiniMax' },
|
|
48
|
+
{ pattern: /deepseek/i, family: 'DeepSeek' },
|
|
49
|
+
{ pattern: /mistral|mixtral/i, family: 'Mistral' },
|
|
50
|
+
{ pattern: /command|cohere/i, family: 'Command' },
|
|
51
|
+
{ pattern: /grok/i, family: 'Grok' },
|
|
52
|
+
{ pattern: /kimi|moonshot/i, family: 'Kimi' },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export function detectFamily(model: ModelDefinition): ModelFamily {
|
|
56
|
+
const haystack = `${model.id} ${model.displayName}`;
|
|
57
|
+
for (const { pattern, family } of FAMILY_PATTERNS) {
|
|
58
|
+
if (pattern.test(haystack)) return family;
|
|
59
|
+
}
|
|
60
|
+
return 'Other';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function tierToCategoryFilter(tier: string | undefined): CategoryFilter {
|
|
64
|
+
if (tier === 'free') return 'free';
|
|
65
|
+
if (tier === 'subscription') return 'subscription';
|
|
66
|
+
return 'paid';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PickerItem {
|
|
70
|
+
id: string;
|
|
71
|
+
label: string;
|
|
72
|
+
detail?: string;
|
|
73
|
+
isGroupHeader?: boolean;
|
|
74
|
+
qualityTier?: string;
|
|
75
|
+
isPinned?: boolean;
|
|
76
|
+
isFree?: boolean;
|
|
77
|
+
isConfigured?: boolean;
|
|
78
|
+
configuredVia?: 'env' | 'secrets' | 'subscription' | 'anonymous';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const POPULAR_PROVIDERS: ReadonlySet<string> = new Set([
|
|
82
|
+
'anthropic',
|
|
83
|
+
'google',
|
|
84
|
+
'groq',
|
|
85
|
+
'mistral',
|
|
86
|
+
'nvidia',
|
|
87
|
+
'ollama',
|
|
88
|
+
'openai',
|
|
89
|
+
'openrouter',
|
|
90
|
+
'synthetic',
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
export interface FilteredModelsCache {
|
|
94
|
+
readonly modelsRef: ModelDefinition[];
|
|
95
|
+
readonly configuredProvidersKey: string;
|
|
96
|
+
readonly pinnedIdsKey: string;
|
|
97
|
+
readonly recentIdsKey: string;
|
|
98
|
+
readonly query: string;
|
|
99
|
+
readonly categoryFilter: CategoryFilter;
|
|
100
|
+
readonly capabilityFilter: CapabilityFilter;
|
|
101
|
+
readonly availableOnly: boolean;
|
|
102
|
+
readonly benchmarkSort: BenchmarkSort;
|
|
103
|
+
readonly groupBy: GroupByMode;
|
|
104
|
+
readonly result: ModelDefinition[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface FilteredProvidersCache {
|
|
108
|
+
readonly providersRef: string[];
|
|
109
|
+
readonly query: string;
|
|
110
|
+
readonly result: string[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ModelItemsCache {
|
|
114
|
+
readonly filteredModelsRef: ModelDefinition[];
|
|
115
|
+
readonly pinnedIdsKey: string;
|
|
116
|
+
readonly groupBy: GroupByMode;
|
|
117
|
+
readonly result: PickerItem[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ProviderItemsCache {
|
|
121
|
+
readonly filteredProvidersRef: string[];
|
|
122
|
+
readonly configuredProvidersKey: string;
|
|
123
|
+
readonly configuredViaKey: string;
|
|
124
|
+
readonly result: PickerItem[];
|
|
125
|
+
}
|
|
@@ -4,118 +4,11 @@ import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers/ef
|
|
|
4
4
|
import { getQualityTier, getQualityTierFromScore, compositeScore, A_TIER_THRESHOLD } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
|
|
5
5
|
import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
|
|
6
6
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/registry';
|
|
7
|
+
import { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
|
|
8
|
+
import type { BenchmarkSort, CapabilityFilter, CategoryFilter, FilteredModelsCache, FilteredProvidersCache, GroupByMode, ModelItemsCache, ModelPickerTarget, PickerItem, PickerMode, ProviderItemsCache } from './model-picker-types.ts';
|
|
7
9
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Which config keys the model picker writes to on commit.
|
|
12
|
-
* 'main' → provider.provider + provider.model (default)
|
|
13
|
-
* 'helper' → helper.globalProvider + helper.globalModel (+ helper.enabled: true)
|
|
14
|
-
* 'tool' → tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
|
|
15
|
-
* 'tts' → tts.llmProvider + tts.llmModel
|
|
16
|
-
*/
|
|
17
|
-
export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Pricing tier filter.
|
|
21
|
-
* 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
|
|
22
|
-
* with future CatalogModel tiers ('free' | 'paid' | 'subscription').
|
|
23
|
-
*/
|
|
24
|
-
export type CategoryFilter = 'all' | 'free' | 'paid' | 'subscription';
|
|
25
|
-
|
|
26
|
-
/** Model family grouping names. */
|
|
27
|
-
export type ModelFamily =
|
|
28
|
-
| 'GPT'
|
|
29
|
-
| 'Claude'
|
|
30
|
-
| 'Gemini'
|
|
31
|
-
| 'Llama'
|
|
32
|
-
| 'Qwen'
|
|
33
|
-
| 'GLM'
|
|
34
|
-
| 'MiniMax'
|
|
35
|
-
| 'DeepSeek'
|
|
36
|
-
| 'Mistral'
|
|
37
|
-
| 'Command'
|
|
38
|
-
| 'Grok'
|
|
39
|
-
| 'Kimi'
|
|
40
|
-
| 'Other';
|
|
41
|
-
|
|
42
|
-
/** Capability filter — subset of ModelDefinition capabilities. */
|
|
43
|
-
export type CapabilityFilter = 'reasoning' | 'toolUse' | 'multimodal' | 'none';
|
|
44
|
-
|
|
45
|
-
/** Benchmark score sort order. */
|
|
46
|
-
export type BenchmarkSort = 'none' | 'composite' | 'swe' | 'gpqa';
|
|
47
|
-
|
|
48
|
-
/** Group-by cycling order. */
|
|
49
|
-
export type GroupByMode = 'provider' | 'family' | 'pricingTier' | 'qualityTier';
|
|
50
|
-
|
|
51
|
-
// ── Family detection helpers ──────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
/** Patterns for detecting model family from id/displayName. */
|
|
54
|
-
const FAMILY_PATTERNS: Array<{ pattern: RegExp; family: ModelFamily }> = [
|
|
55
|
-
{ pattern: /claude/i, family: 'Claude' },
|
|
56
|
-
{ pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
|
|
57
|
-
{ pattern: /gemini/i, family: 'Gemini' },
|
|
58
|
-
{ pattern: /llama/i, family: 'Llama' },
|
|
59
|
-
{ pattern: /qwen/i, family: 'Qwen' },
|
|
60
|
-
{ pattern: /glm|chatglm/i, family: 'GLM' },
|
|
61
|
-
{ pattern: /minimax|abab/i, family: 'MiniMax' },
|
|
62
|
-
{ pattern: /deepseek/i, family: 'DeepSeek' },
|
|
63
|
-
{ pattern: /mistral|mixtral/i, family: 'Mistral' },
|
|
64
|
-
{ pattern: /command|cohere/i, family: 'Command' },
|
|
65
|
-
{ pattern: /grok/i, family: 'Grok' },
|
|
66
|
-
{ pattern: /kimi|moonshot/i, family: 'Kimi' },
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
/** Detect the model family from id and displayName. */
|
|
70
|
-
export function detectFamily(model: ModelDefinition): ModelFamily {
|
|
71
|
-
const haystack = `${model.id} ${model.displayName}`;
|
|
72
|
-
for (const { pattern, family } of FAMILY_PATTERNS) {
|
|
73
|
-
if (pattern.test(haystack)) return family;
|
|
74
|
-
}
|
|
75
|
-
return 'Other';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Map ModelDefinition tier to CategoryFilter bucket.
|
|
80
|
-
* 'standard' and 'premium' both map to 'paid' for forward-compat.
|
|
81
|
-
*/
|
|
82
|
-
export function tierToCategoryFilter(tier: string | undefined): CategoryFilter {
|
|
83
|
-
if (tier === 'free') return 'free';
|
|
84
|
-
if (tier === 'subscription') return 'subscription';
|
|
85
|
-
return 'paid';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** A generic selectable item for non-model modes. */
|
|
89
|
-
export interface PickerItem {
|
|
90
|
-
id: string;
|
|
91
|
-
label: string;
|
|
92
|
-
detail?: string;
|
|
93
|
-
/** If true, this item is a group header (not selectable). */
|
|
94
|
-
isGroupHeader?: boolean;
|
|
95
|
-
/** Quality tier badge for model items: S/A/B/C. */
|
|
96
|
-
qualityTier?: string;
|
|
97
|
-
/** Whether this model is pinned/favorited. */
|
|
98
|
-
isPinned?: boolean;
|
|
99
|
-
/** True when model tier is free. */
|
|
100
|
-
isFree?: boolean;
|
|
101
|
-
/** True when this provider item has a configured API key. */
|
|
102
|
-
isConfigured?: boolean;
|
|
103
|
-
/** How the provider is configured — shown as a badge in provider mode. */
|
|
104
|
-
configuredVia?: 'env' | 'secrets' | 'subscription' | 'anonymous';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Provider IDs treated as "Popular" in the provider picker. */
|
|
108
|
-
export const POPULAR_PROVIDERS: ReadonlySet<string> = new Set([
|
|
109
|
-
'anthropic',
|
|
110
|
-
'google',
|
|
111
|
-
'groq',
|
|
112
|
-
'mistral',
|
|
113
|
-
'nvidia',
|
|
114
|
-
'ollama',
|
|
115
|
-
'openai',
|
|
116
|
-
'openrouter',
|
|
117
|
-
'synthetic',
|
|
118
|
-
]);
|
|
10
|
+
export { detectFamily, POPULAR_PROVIDERS, tierToCategoryFilter } from './model-picker-types.ts';
|
|
11
|
+
export type { BenchmarkSort, CapabilityFilter, CategoryFilter, GroupByMode, ModelFamily, ModelPickerTarget, PickerItem, PickerMode } from './model-picker-types.ts';
|
|
119
12
|
|
|
120
13
|
/**
|
|
121
14
|
* ModelPickerModal - Multi-step interactive picker for model, provider, and effort.
|
|
@@ -180,12 +73,18 @@ export class ModelPickerModal {
|
|
|
180
73
|
/** Current group-by mode. */
|
|
181
74
|
public groupBy: GroupByMode = 'provider';
|
|
182
75
|
|
|
76
|
+
private filteredModelsCache: FilteredModelsCache | null = null;
|
|
77
|
+
private filteredProvidersCache: FilteredProvidersCache | null = null;
|
|
78
|
+
private modelItemsCache: ModelItemsCache | null = null;
|
|
79
|
+
private providerItemsCache: ProviderItemsCache | null = null;
|
|
80
|
+
|
|
183
81
|
// ── Category filter cycling ───────────────────────────────────────────────
|
|
184
82
|
private static readonly CATEGORY_CYCLE: CategoryFilter[] = ['all', 'free', 'paid', 'subscription'];
|
|
185
83
|
/** Cycle to next pricing tier filter. */
|
|
186
84
|
cycleCategory(): void {
|
|
187
85
|
const idx = ModelPickerModal.CATEGORY_CYCLE.indexOf(this.categoryFilter);
|
|
188
86
|
this.categoryFilter = ModelPickerModal.CATEGORY_CYCLE[(idx + 1) % ModelPickerModal.CATEGORY_CYCLE.length];
|
|
87
|
+
this.clearFilteredCaches();
|
|
189
88
|
this._clampSelection();
|
|
190
89
|
}
|
|
191
90
|
|
|
@@ -195,6 +94,7 @@ export class ModelPickerModal {
|
|
|
195
94
|
cycleGroupBy(): void {
|
|
196
95
|
const idx = ModelPickerModal.GROUP_BY_CYCLE.indexOf(this.groupBy);
|
|
197
96
|
this.groupBy = ModelPickerModal.GROUP_BY_CYCLE[(idx + 1) % ModelPickerModal.GROUP_BY_CYCLE.length];
|
|
97
|
+
this.clearFilteredCaches();
|
|
198
98
|
this._clampSelection();
|
|
199
99
|
}
|
|
200
100
|
|
|
@@ -204,6 +104,7 @@ export class ModelPickerModal {
|
|
|
204
104
|
cycleBenchmarkSort(): void {
|
|
205
105
|
const idx = ModelPickerModal.BENCHMARK_SORT_CYCLE.indexOf(this.benchmarkSort);
|
|
206
106
|
this.benchmarkSort = ModelPickerModal.BENCHMARK_SORT_CYCLE[(idx + 1) % ModelPickerModal.BENCHMARK_SORT_CYCLE.length];
|
|
107
|
+
this.clearFilteredCaches();
|
|
207
108
|
this._clampSelection();
|
|
208
109
|
}
|
|
209
110
|
|
|
@@ -266,8 +167,9 @@ export class ModelPickerModal {
|
|
|
266
167
|
this.query = '';
|
|
267
168
|
this.categoryFilter = 'all';
|
|
268
169
|
this.capabilityFilter = 'none';
|
|
269
|
-
|
|
270
|
-
|
|
170
|
+
const filtered = this.getFilteredProviders();
|
|
171
|
+
const currentIndex = filtered.findIndex((provider) => provider === currentProvider);
|
|
172
|
+
this.selectedIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
271
173
|
this.scrollOffset = 0;
|
|
272
174
|
}
|
|
273
175
|
|
|
@@ -315,6 +217,7 @@ export class ModelPickerModal {
|
|
|
315
217
|
this.query = '';
|
|
316
218
|
this.categoryFilter = 'all';
|
|
317
219
|
this.capabilityFilter = 'none';
|
|
220
|
+
this.clearCaches();
|
|
318
221
|
}
|
|
319
222
|
|
|
320
223
|
// ── Search helpers ─────────────────────────────────────────────────────────
|
|
@@ -322,6 +225,7 @@ export class ModelPickerModal {
|
|
|
322
225
|
/** Append a character to the search query and clamp selectedIndex. */
|
|
323
226
|
appendChar(ch: string): void {
|
|
324
227
|
this.query += ch;
|
|
228
|
+
this.clearFilteredCaches();
|
|
325
229
|
this._clampSelection();
|
|
326
230
|
}
|
|
327
231
|
|
|
@@ -329,6 +233,7 @@ export class ModelPickerModal {
|
|
|
329
233
|
deleteChar(): void {
|
|
330
234
|
if (this.query.length > 0) {
|
|
331
235
|
this.query = this.query.slice(0, -1);
|
|
236
|
+
this.clearFilteredCaches();
|
|
332
237
|
this._clampSelection();
|
|
333
238
|
}
|
|
334
239
|
}
|
|
@@ -336,6 +241,7 @@ export class ModelPickerModal {
|
|
|
336
241
|
/** Clear the search query and clamp selectedIndex. */
|
|
337
242
|
clearQuery(): void {
|
|
338
243
|
this.query = '';
|
|
244
|
+
this.clearFilteredCaches();
|
|
339
245
|
this._clampSelection();
|
|
340
246
|
}
|
|
341
247
|
|
|
@@ -354,18 +260,21 @@ export class ModelPickerModal {
|
|
|
354
260
|
/** Set category filter and clamp selectedIndex. */
|
|
355
261
|
setCategoryFilter(filter: CategoryFilter): void {
|
|
356
262
|
this.categoryFilter = filter;
|
|
263
|
+
this.clearFilteredCaches();
|
|
357
264
|
this._clampSelection();
|
|
358
265
|
}
|
|
359
266
|
|
|
360
267
|
/** Set capability filter and clamp selectedIndex. */
|
|
361
268
|
setCapabilityFilter(filter: CapabilityFilter): void {
|
|
362
269
|
this.capabilityFilter = filter;
|
|
270
|
+
this.clearFilteredCaches();
|
|
363
271
|
this._clampSelection();
|
|
364
272
|
}
|
|
365
273
|
|
|
366
274
|
/** Toggle the available-only filter. */
|
|
367
275
|
toggleAvailableOnly(): void {
|
|
368
276
|
this.availableOnly = !this.availableOnly;
|
|
277
|
+
this.clearFilteredCaches();
|
|
369
278
|
this._clampSelection();
|
|
370
279
|
}
|
|
371
280
|
|
|
@@ -396,15 +305,50 @@ export class ModelPickerModal {
|
|
|
396
305
|
|
|
397
306
|
/** Return providers matching the current query (case-insensitive substring), in grouped order. */
|
|
398
307
|
getFilteredProviders(): string[] {
|
|
308
|
+
const cached = this.filteredProvidersCache;
|
|
309
|
+
if (
|
|
310
|
+
cached !== null
|
|
311
|
+
&& cached.providersRef === this.providers
|
|
312
|
+
&& cached.query === this.query
|
|
313
|
+
) {
|
|
314
|
+
return cached.result;
|
|
315
|
+
}
|
|
316
|
+
|
|
399
317
|
const { popular, all } = this.getGroupedProviders();
|
|
400
318
|
const ordered = [...popular, ...all];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
319
|
+
const result = this.query.trim().length === 0
|
|
320
|
+
? ordered
|
|
321
|
+
: ordered.filter(p => p.toLowerCase().includes(this.query.toLowerCase()));
|
|
322
|
+
this.filteredProvidersCache = {
|
|
323
|
+
providersRef: this.providers,
|
|
324
|
+
query: this.query,
|
|
325
|
+
result,
|
|
326
|
+
};
|
|
327
|
+
return result;
|
|
404
328
|
}
|
|
405
329
|
|
|
406
330
|
/** Return models matching all current filters, sorted per benchmarkSort. */
|
|
407
331
|
getFilteredModels(): ModelDefinition[] {
|
|
332
|
+
const configuredProvidersKey = setKey(this.configuredProviders);
|
|
333
|
+
const pinnedIdsKey = setKey(this.pinnedIds);
|
|
334
|
+
const recentIdsKey = orderedListKey(this.recentIds);
|
|
335
|
+
const cached = this.filteredModelsCache;
|
|
336
|
+
if (
|
|
337
|
+
cached !== null
|
|
338
|
+
&& cached.modelsRef === this.models
|
|
339
|
+
&& cached.configuredProvidersKey === configuredProvidersKey
|
|
340
|
+
&& cached.pinnedIdsKey === pinnedIdsKey
|
|
341
|
+
&& cached.recentIdsKey === recentIdsKey
|
|
342
|
+
&& cached.query === this.query
|
|
343
|
+
&& cached.categoryFilter === this.categoryFilter
|
|
344
|
+
&& cached.capabilityFilter === this.capabilityFilter
|
|
345
|
+
&& cached.availableOnly === this.availableOnly
|
|
346
|
+
&& cached.benchmarkSort === this.benchmarkSort
|
|
347
|
+
&& cached.groupBy === this.groupBy
|
|
348
|
+
) {
|
|
349
|
+
return cached.result;
|
|
350
|
+
}
|
|
351
|
+
|
|
408
352
|
let result = this.models;
|
|
409
353
|
|
|
410
354
|
// Available-only filter
|
|
@@ -520,6 +464,20 @@ export class ModelPickerModal {
|
|
|
520
464
|
result = [...recent, ...rest];
|
|
521
465
|
}
|
|
522
466
|
|
|
467
|
+
this.filteredModelsCache = {
|
|
468
|
+
modelsRef: this.models,
|
|
469
|
+
configuredProvidersKey,
|
|
470
|
+
pinnedIdsKey,
|
|
471
|
+
recentIdsKey,
|
|
472
|
+
query: this.query,
|
|
473
|
+
categoryFilter: this.categoryFilter,
|
|
474
|
+
capabilityFilter: this.capabilityFilter,
|
|
475
|
+
availableOnly: this.availableOnly,
|
|
476
|
+
benchmarkSort: this.benchmarkSort,
|
|
477
|
+
groupBy: this.groupBy,
|
|
478
|
+
result,
|
|
479
|
+
};
|
|
480
|
+
this.modelItemsCache = null;
|
|
523
481
|
return result;
|
|
524
482
|
}
|
|
525
483
|
|
|
@@ -529,6 +487,7 @@ export class ModelPickerModal {
|
|
|
529
487
|
*/
|
|
530
488
|
async loadRecentModels(n = 10): Promise<void> {
|
|
531
489
|
this.recentIds = await this.favoritesStore.getRecentModels(n);
|
|
490
|
+
this.clearFilteredCaches();
|
|
532
491
|
}
|
|
533
492
|
|
|
534
493
|
/**
|
|
@@ -574,6 +533,16 @@ export class ModelPickerModal {
|
|
|
574
533
|
getItems(): PickerItem[] {
|
|
575
534
|
if (this.mode === 'model') {
|
|
576
535
|
const filtered = this.getFilteredModels();
|
|
536
|
+
const pinnedIdsKey = setKey(this.pinnedIds);
|
|
537
|
+
const cached = this.modelItemsCache;
|
|
538
|
+
if (
|
|
539
|
+
cached !== null
|
|
540
|
+
&& cached.filteredModelsRef === filtered
|
|
541
|
+
&& cached.pinnedIdsKey === pinnedIdsKey
|
|
542
|
+
&& cached.groupBy === this.groupBy
|
|
543
|
+
) {
|
|
544
|
+
return cached.result;
|
|
545
|
+
}
|
|
577
546
|
|
|
578
547
|
// Separate pinned and unpinned
|
|
579
548
|
const pinned = filtered.filter(m => this.pinnedIds.has(m.id));
|
|
@@ -600,33 +569,45 @@ export class ModelPickerModal {
|
|
|
600
569
|
items.push(this._modelToItem(m, false));
|
|
601
570
|
}
|
|
602
571
|
|
|
572
|
+
this.modelItemsCache = {
|
|
573
|
+
filteredModelsRef: filtered,
|
|
574
|
+
pinnedIdsKey,
|
|
575
|
+
groupBy: this.groupBy,
|
|
576
|
+
result: items,
|
|
577
|
+
};
|
|
603
578
|
return items;
|
|
604
579
|
}
|
|
605
580
|
if (this.mode === 'provider') {
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
581
|
+
const filteredProviders = this.getFilteredProviders();
|
|
582
|
+
const configuredProvidersKey = setKey(this.configuredProviders);
|
|
583
|
+
const configuredViaKey = mapKey(this.configuredViaMap);
|
|
584
|
+
const cached = this.providerItemsCache;
|
|
585
|
+
if (
|
|
586
|
+
cached !== null
|
|
587
|
+
&& cached.filteredProvidersRef === filteredProviders
|
|
588
|
+
&& cached.configuredProvidersKey === configuredProvidersKey
|
|
589
|
+
&& cached.configuredViaKey === configuredViaKey
|
|
590
|
+
) {
|
|
591
|
+
return cached.result;
|
|
592
|
+
}
|
|
614
593
|
|
|
615
594
|
const providerItems: PickerItem[] = [];
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
providerItems.push({ id:
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
if (filteredAll.length > 0) {
|
|
624
|
-
providerItems.push({ id: '__header__all', label: 'All Providers', isGroupHeader: true });
|
|
625
|
-
for (const p of filteredAll) {
|
|
626
|
-
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
595
|
+
let currentGroup: 'Popular' | 'All Providers' | null = null;
|
|
596
|
+
for (const p of filteredProviders) {
|
|
597
|
+
const group: 'Popular' | 'All Providers' = POPULAR_PROVIDERS.has(p.toLowerCase()) ? 'Popular' : 'All Providers';
|
|
598
|
+
if (group !== currentGroup) {
|
|
599
|
+
providerItems.push({ id: `__header__${group}`, label: group, isGroupHeader: true });
|
|
600
|
+
currentGroup = group;
|
|
627
601
|
}
|
|
602
|
+
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
628
603
|
}
|
|
629
604
|
|
|
605
|
+
this.providerItemsCache = {
|
|
606
|
+
filteredProvidersRef: filteredProviders,
|
|
607
|
+
configuredProvidersKey,
|
|
608
|
+
configuredViaKey,
|
|
609
|
+
result: providerItems,
|
|
610
|
+
};
|
|
630
611
|
return providerItems;
|
|
631
612
|
}
|
|
632
613
|
// effort mode
|
|
@@ -743,4 +724,32 @@ export class ModelPickerModal {
|
|
|
743
724
|
getBenchmarkEntry(model: ModelDefinition) {
|
|
744
725
|
return this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
|
|
745
726
|
}
|
|
727
|
+
|
|
728
|
+
private clearFilteredCaches(): void {
|
|
729
|
+
this.filteredModelsCache = null;
|
|
730
|
+
this.filteredProvidersCache = null;
|
|
731
|
+
this.modelItemsCache = null;
|
|
732
|
+
this.providerItemsCache = null;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private clearCaches(): void {
|
|
736
|
+
this.clearFilteredCaches();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function setKey(values: ReadonlySet<string>): string {
|
|
741
|
+
if (values.size === 0) return '';
|
|
742
|
+
return [...values].sort().join('\u001f');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function orderedListKey(values: readonly string[]): string {
|
|
746
|
+
return values.length === 0 ? '' : values.join('\u001f');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function mapKey(values: ReadonlyMap<string, string | undefined>): string {
|
|
750
|
+
if (values.size === 0) return '';
|
|
751
|
+
return [...values.entries()]
|
|
752
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
753
|
+
.map(([key, value]) => `${key}\u001e${value ?? ''}`)
|
|
754
|
+
.join('\u001f');
|
|
746
755
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { OnboardingAcknowledgementTarget, OnboardingApplyOperation, OnboardingApplyRequest } from '../../runtime/onboarding/index.ts';
|
|
2
2
|
import { getServerSurfaceFeatureFlags } from '../../runtime/surface-feature-flags.ts';
|
|
3
|
+
import {
|
|
4
|
+
buildCloudflareApiTokenRef,
|
|
5
|
+
buildCloudflareOperationalTokenRef,
|
|
6
|
+
getCloudflareBatchMode,
|
|
7
|
+
getCloudflareComponentSelection,
|
|
8
|
+
getCloudflareSetupSource,
|
|
9
|
+
shouldShowCloudflareStep,
|
|
10
|
+
} from './onboarding-wizard-cloudflare.ts';
|
|
3
11
|
import {
|
|
4
12
|
EXTERNAL_SURFACE_SPECS,
|
|
5
13
|
getExternalSurfaceAutoStartDefaultValue,
|
|
@@ -111,6 +119,10 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
111
119
|
|
|
112
120
|
setSecret('OPENAI_API_KEY', controller.getStringFieldValue('providers.openai-api-key', ''));
|
|
113
121
|
|
|
122
|
+
if (shouldShowCloudflareStep(controller)) {
|
|
123
|
+
addCloudflareOperations(controller, operations, setSecret);
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
const externalIntegrations = controller.isCapabilitySelected('external-integrations');
|
|
115
127
|
const enabledExternalSurfaceIds: string[] = [];
|
|
116
128
|
for (const surface of EXTERNAL_SURFACE_SPECS) {
|
|
@@ -165,6 +177,75 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
function addCloudflareOperations(
|
|
181
|
+
controller: OnboardingWizardController,
|
|
182
|
+
operations: OnboardingApplyOperation[],
|
|
183
|
+
setSecret: (key: string, value: string) => void,
|
|
184
|
+
): void {
|
|
185
|
+
const setConfig = (
|
|
186
|
+
key: Extract<OnboardingApplyOperation, { kind: 'set-config' }>['key'],
|
|
187
|
+
value: unknown,
|
|
188
|
+
): void => {
|
|
189
|
+
operations.push({ kind: 'set-config', key, value });
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const config = controller.runtimeSnapshot?.config.cloudflare;
|
|
193
|
+
const enabledDefault = controller.isCapabilitySelected('cloudflare-batch') || config?.enabled === true;
|
|
194
|
+
const enabled = controller.getBooleanFieldValue('cloudflare.enabled', enabledDefault);
|
|
195
|
+
const components = getCloudflareComponentSelection(controller);
|
|
196
|
+
const batchMode = enabled ? getCloudflareBatchMode(controller) : 'off';
|
|
197
|
+
const setupSource = getCloudflareSetupSource(controller);
|
|
198
|
+
const existingApiTokenRef = config?.apiTokenRef ?? '';
|
|
199
|
+
let apiTokenRef = existingApiTokenRef;
|
|
200
|
+
|
|
201
|
+
if (!enabled) {
|
|
202
|
+
setConfig('cloudflare.enabled', false);
|
|
203
|
+
setConfig('batch.mode', 'off');
|
|
204
|
+
setConfig('batch.queueBackend', 'local');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (setupSource === 'operational-env') {
|
|
209
|
+
apiTokenRef = buildCloudflareApiTokenRef(
|
|
210
|
+
controller.getStringFieldValue('cloudflare.operational-env-name', 'CLOUDFLARE_API_TOKEN'),
|
|
211
|
+
);
|
|
212
|
+
} else if (setupSource === 'operational-token') {
|
|
213
|
+
const token = controller.getStringFieldValue('cloudflare.operational-token', '');
|
|
214
|
+
if (token.length > 0) {
|
|
215
|
+
setSecret('CLOUDFLARE_API_TOKEN', token);
|
|
216
|
+
apiTokenRef = buildCloudflareOperationalTokenRef();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
setConfig('cloudflare.enabled', true);
|
|
221
|
+
setConfig('cloudflare.freeTierMode', controller.getStringFieldValue('cloudflare.free-tier-mode', config?.freeTierMode === false ? 'no' : 'yes') === 'yes');
|
|
222
|
+
setConfig('cloudflare.accountId', controller.getStringFieldValue('cloudflare.account-id', config?.accountId ?? ''));
|
|
223
|
+
setConfig('cloudflare.apiTokenRef', apiTokenRef);
|
|
224
|
+
setConfig('cloudflare.zoneId', controller.getStringFieldValue('cloudflare.zone-id', config?.zoneId ?? ''));
|
|
225
|
+
setConfig('cloudflare.zoneName', controller.getStringFieldValue('cloudflare.zone-name', config?.zoneName ?? ''));
|
|
226
|
+
setConfig('cloudflare.workerName', controller.getStringFieldValue('cloudflare.worker-name', config?.workerName ?? 'goodvibes-batch-worker'));
|
|
227
|
+
setConfig('cloudflare.workerSubdomain', controller.getStringFieldValue('cloudflare.worker-subdomain', config?.workerSubdomain ?? ''));
|
|
228
|
+
setConfig('cloudflare.workerHostname', controller.getStringFieldValue('cloudflare.worker-hostname', config?.workerHostname ?? ''));
|
|
229
|
+
setConfig('cloudflare.workerBaseUrl', controller.getStringFieldValue('cloudflare.worker-base-url', config?.workerBaseUrl ?? ''));
|
|
230
|
+
setConfig('cloudflare.daemonBaseUrl', controller.getStringFieldValue('cloudflare.daemon-base-url', config?.daemonBaseUrl ?? ''));
|
|
231
|
+
setConfig('cloudflare.daemonHostname', controller.getStringFieldValue('cloudflare.daemon-hostname', config?.daemonHostname ?? ''));
|
|
232
|
+
setConfig('cloudflare.workerCron', controller.getStringFieldValue('cloudflare.worker-cron', config?.workerCron ?? '*/5 * * * *'));
|
|
233
|
+
setConfig('cloudflare.queueName', controller.getStringFieldValue('cloudflare.queue-name', config?.queueName ?? 'goodvibes-batch'));
|
|
234
|
+
setConfig('cloudflare.deadLetterQueueName', controller.getStringFieldValue('cloudflare.dead-letter-queue-name', config?.deadLetterQueueName ?? 'goodvibes-batch-dlq'));
|
|
235
|
+
setConfig('cloudflare.tunnelName', controller.getStringFieldValue('cloudflare.tunnel-name', config?.tunnelName ?? 'goodvibes-daemon'));
|
|
236
|
+
setConfig('cloudflare.tunnelId', controller.getStringFieldValue('cloudflare.tunnel-id', config?.tunnelId ?? ''));
|
|
237
|
+
setConfig('cloudflare.kvNamespaceName', controller.getStringFieldValue('cloudflare.kv-namespace-name', config?.kvNamespaceName ?? 'goodvibes-runtime'));
|
|
238
|
+
setConfig('cloudflare.kvNamespaceId', controller.getStringFieldValue('cloudflare.kv-namespace-id', config?.kvNamespaceId ?? ''));
|
|
239
|
+
setConfig('cloudflare.durableObjectNamespaceName', controller.getStringFieldValue('cloudflare.do-namespace-name', config?.durableObjectNamespaceName ?? 'GoodVibesCoordinator'));
|
|
240
|
+
setConfig('cloudflare.durableObjectNamespaceId', controller.getStringFieldValue('cloudflare.do-namespace-id', config?.durableObjectNamespaceId ?? ''));
|
|
241
|
+
setConfig('cloudflare.r2BucketName', controller.getStringFieldValue('cloudflare.r2-bucket-name', config?.r2BucketName ?? 'goodvibes-artifacts'));
|
|
242
|
+
setConfig('cloudflare.secretsStoreName', controller.getStringFieldValue('cloudflare.secrets-store-name', config?.secretsStoreName ?? 'goodvibes'));
|
|
243
|
+
setConfig('cloudflare.secretsStoreId', controller.getStringFieldValue('cloudflare.secrets-store-id', config?.secretsStoreId ?? ''));
|
|
244
|
+
setConfig('cloudflare.maxQueueOpsPerDay', controller.getNumberFieldValue('cloudflare.max-queue-ops-per-day', config?.maxQueueOpsPerDay ?? 10000, 1));
|
|
245
|
+
setConfig('batch.mode', batchMode);
|
|
246
|
+
setConfig('batch.queueBackend', batchMode !== 'off' && components.queues ? 'cloudflare' : 'local');
|
|
247
|
+
}
|
|
248
|
+
|
|
168
249
|
export function addNetworkOperations(
|
|
169
250
|
controller: OnboardingWizardController,
|
|
170
251
|
operations: OnboardingApplyOperation[],
|