@happyvertical/smrt-inference 0.0.0-stage → 0.60.15

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 (107) hide show
  1. package/AGENTS.md +201 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +73 -2
  5. package/agents/fusions.md +92 -0
  6. package/agents/hosted.md +145 -0
  7. package/agents/resolver.md +65 -0
  8. package/agents/settings.md +84 -0
  9. package/dist/__smrt-register__.d.ts +2 -0
  10. package/dist/__smrt-register__.d.ts.map +1 -0
  11. package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js +398 -0
  12. package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js.map +1 -0
  13. package/dist/chunks/provider-config-4Bx8N52X.js +1847 -0
  14. package/dist/chunks/provider-config-4Bx8N52X.js.map +1 -0
  15. package/dist/classify.d.ts +32 -0
  16. package/dist/classify.d.ts.map +1 -0
  17. package/dist/config.d.ts +89 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/errors.d.ts +51 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/executor.d.ts +45 -0
  22. package/dist/executor.d.ts.map +1 -0
  23. package/dist/fusion-store.d.ts +16 -0
  24. package/dist/fusion-store.d.ts.map +1 -0
  25. package/dist/fusion.d.ts +155 -0
  26. package/dist/fusion.d.ts.map +1 -0
  27. package/dist/gate.d.ts +143 -0
  28. package/dist/gate.d.ts.map +1 -0
  29. package/dist/gate.js +94 -0
  30. package/dist/gate.js.map +1 -0
  31. package/dist/hosted.d.ts +454 -0
  32. package/dist/hosted.d.ts.map +1 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +8 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/manifest.json +453 -0
  38. package/dist/models/InferenceOverride.d.ts +38 -0
  39. package/dist/models/InferenceOverride.d.ts.map +1 -0
  40. package/dist/models/InferenceOverrideCollection.d.ts +35 -0
  41. package/dist/models/InferenceOverrideCollection.d.ts.map +1 -0
  42. package/dist/models.d.ts +11 -0
  43. package/dist/models.d.ts.map +1 -0
  44. package/dist/models.js +2 -0
  45. package/dist/node.d.ts +110 -0
  46. package/dist/node.d.ts.map +1 -0
  47. package/dist/node.js +1361 -0
  48. package/dist/node.js.map +1 -0
  49. package/dist/provider-config.d.ts +113 -0
  50. package/dist/provider-config.d.ts.map +1 -0
  51. package/dist/register.d.ts +30 -0
  52. package/dist/register.d.ts.map +1 -0
  53. package/dist/resolver.d.ts +166 -0
  54. package/dist/resolver.d.ts.map +1 -0
  55. package/dist/resolver.js +288 -0
  56. package/dist/resolver.js.map +1 -0
  57. package/dist/server.d.ts +24 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +985 -0
  60. package/dist/server.js.map +1 -0
  61. package/dist/settings/cache.d.ts +5 -0
  62. package/dist/settings/cache.d.ts.map +1 -0
  63. package/dist/settings/keys.d.ts +70 -0
  64. package/dist/settings/keys.d.ts.map +1 -0
  65. package/dist/settings/layers.d.ts +54 -0
  66. package/dist/settings/layers.d.ts.map +1 -0
  67. package/dist/settings/override-service.d.ts +119 -0
  68. package/dist/settings/override-service.d.ts.map +1 -0
  69. package/dist/settings/preferences.d.ts +137 -0
  70. package/dist/settings/preferences.d.ts.map +1 -0
  71. package/dist/settings/resolver.d.ts +34 -0
  72. package/dist/settings/resolver.d.ts.map +1 -0
  73. package/dist/settings/settings-service.d.ts +282 -0
  74. package/dist/settings/settings-service.d.ts.map +1 -0
  75. package/dist/settings/targets.d.ts +93 -0
  76. package/dist/settings/targets.d.ts.map +1 -0
  77. package/dist/smrt-knowledge.json +335 -0
  78. package/dist/smrt-knowledge.okf.md +80 -0
  79. package/dist/svelte/components/InferenceAliasEditor.svelte +210 -0
  80. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts +13 -0
  81. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts.map +1 -0
  82. package/dist/svelte/components/InferenceDefaultView.svelte +330 -0
  83. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts +15 -0
  84. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts.map +1 -0
  85. package/dist/svelte/components/InferenceFusionList.svelte +135 -0
  86. package/dist/svelte/components/InferenceFusionList.svelte.d.ts +16 -0
  87. package/dist/svelte/components/InferenceFusionList.svelte.d.ts.map +1 -0
  88. package/dist/svelte/components/InferenceSettingsPanel.svelte +345 -0
  89. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts +65 -0
  90. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts.map +1 -0
  91. package/dist/svelte/components/InferenceTierRow.svelte +199 -0
  92. package/dist/svelte/components/InferenceTierRow.svelte.d.ts +14 -0
  93. package/dist/svelte/components/InferenceTierRow.svelte.d.ts.map +1 -0
  94. package/dist/svelte/components/InferenceUsageTable.svelte +127 -0
  95. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts +15 -0
  96. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts.map +1 -0
  97. package/dist/svelte/index.d.ts +17 -0
  98. package/dist/svelte/index.d.ts.map +1 -0
  99. package/dist/svelte/index.js +14 -0
  100. package/dist/svelte/types.d.ts +197 -0
  101. package/dist/svelte/types.d.ts.map +1 -0
  102. package/dist/svelte/types.js +96 -0
  103. package/dist/types.d.ts +47 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +13 -0
  106. package/dist/types.js.map +1 -0
  107. package/package.json +105 -3
@@ -0,0 +1,210 @@
1
+ <script lang="ts">
2
+ /**
3
+ * One alias: its ordered candidates, a per-candidate test, and (when
4
+ * editable) reorder, remove, add and save. Candidates name `apiKeyEnv`
5
+ * (a variable) when they need their own key; the editor never takes a key.
6
+ */
7
+ import { untrack } from 'svelte';
8
+ import type {
9
+ InferenceAliasChange,
10
+ InferenceAliasView,
11
+ InferenceCandidateView,
12
+ InferenceTestResultView,
13
+ } from '../types.js';
14
+ import { candidateViewLabel } from '../types.js';
15
+
16
+ interface InferenceAliasEditorProps {
17
+ alias: InferenceAliasView;
18
+ editable: boolean;
19
+ busy: boolean;
20
+ isNew?: boolean;
21
+ onSaveAlias?: (change: InferenceAliasChange) => void;
22
+ onTestCandidate?: (
23
+ candidate: InferenceCandidateView,
24
+ ) => Promise<InferenceTestResultView>;
25
+ }
26
+
27
+ const {
28
+ alias,
29
+ editable,
30
+ busy,
31
+ isNew = false,
32
+ onSaveAlias,
33
+ onTestCandidate,
34
+ }: InferenceAliasEditorProps = $props();
35
+
36
+ let name = $state(untrack(() => alias.name));
37
+ let candidates = $state<InferenceCandidateView[]>(
38
+ untrack(() => alias.candidates.map((candidate) => ({ ...candidate }))),
39
+ );
40
+ let results = $state<Record<number, InferenceTestResultView | 'pending'>>({});
41
+ let draft = $state<InferenceCandidateView>({});
42
+
43
+ function move(index: number, by: number): void {
44
+ const next = index + by;
45
+ if (next < 0 || next >= candidates.length) return;
46
+ const copy = [...candidates];
47
+ [copy[index], copy[next]] = [
48
+ copy[next] as InferenceCandidateView,
49
+ copy[index] as InferenceCandidateView,
50
+ ];
51
+ candidates = copy;
52
+ results = {};
53
+ }
54
+
55
+ function remove(index: number): void {
56
+ candidates = candidates.filter((_, at) => at !== index);
57
+ results = {};
58
+ }
59
+
60
+ function add(): void {
61
+ const candidate: InferenceCandidateView = {};
62
+ for (const member of [
63
+ 'provider',
64
+ 'preset',
65
+ 'alias',
66
+ 'model',
67
+ 'baseUrl',
68
+ 'apiKeyEnv',
69
+ ] as const) {
70
+ const value = draft[member]?.trim();
71
+ if (value) candidate[member] = value;
72
+ }
73
+ if (Object.keys(candidate).length === 0) return;
74
+ candidates = [...candidates, candidate];
75
+ draft = {};
76
+ }
77
+
78
+ async function test(index: number): Promise<void> {
79
+ const candidate = candidates[index];
80
+ if (!onTestCandidate || !candidate) return;
81
+ results = { ...results, [index]: 'pending' };
82
+ try {
83
+ const result = await onTestCandidate(
84
+ $state.snapshot(candidate) as InferenceCandidateView,
85
+ );
86
+ results = { ...results, [index]: result };
87
+ } catch {
88
+ results = {
89
+ ...results,
90
+ [index]: { reachable: false, detail: 'The test could not run.' },
91
+ };
92
+ }
93
+ }
94
+
95
+ function save(event: SubmitEvent): void {
96
+ event.preventDefault();
97
+ if (!onSaveAlias || !name.trim()) return;
98
+ const change: InferenceAliasChange = {
99
+ name: name.trim(),
100
+ candidates: $state.snapshot(candidates) as InferenceCandidateView[],
101
+ };
102
+ if (alias.description) change.description = alias.description;
103
+ onSaveAlias(change);
104
+ }
105
+ </script>
106
+
107
+ <form class="iae" onsubmit={save} data-testid={`inference-alias-${isNew ? 'new' : alias.name}`}>
108
+ <div class="iae-head">
109
+ {#if isNew}
110
+ <label>
111
+ <span class="iae-label">New alias name</span>
112
+ <input name="aliasName" bind:value={name} required disabled={busy} />
113
+ </label>
114
+ {:else}
115
+ <strong>{alias.name}</strong>
116
+ <span class="iae-label">{alias.source === 'app' ? 'App' : 'Config'}</span>
117
+ {#if alias.description}<span class="iae-label">{alias.description}</span>{/if}
118
+ {/if}
119
+ </div>
120
+ <ol class="iae-list">
121
+ {#each candidates as candidate, index (index)}
122
+ {@const result = results[index]}
123
+ <li class="iae-candidate">
124
+ <code>{candidateViewLabel(candidate)}</code>
125
+ {#if candidate.apiKeyEnv}<span class="iae-label">key from {candidate.apiKeyEnv}</span>{/if}
126
+ {#if onTestCandidate && !candidate.alias}
127
+ <button type="button" onclick={() => test(index)} disabled={busy || result === 'pending'}>
128
+ Test
129
+ </button>
130
+ {/if}
131
+ {#if editable}
132
+ <button type="button" aria-label={`Move ${candidateViewLabel(candidate)} up`} onclick={() => move(index, -1)} disabled={busy || index === 0}>↑</button>
133
+ <button type="button" aria-label={`Move ${candidateViewLabel(candidate)} down`} onclick={() => move(index, 1)} disabled={busy || index === candidates.length - 1}>↓</button>
134
+ <button type="button" aria-label={`Remove ${candidateViewLabel(candidate)}`} onclick={() => remove(index)} disabled={busy}>Remove</button>
135
+ {/if}
136
+ {#if result && result !== 'pending'}
137
+ <span class="iae-label" role="status">
138
+ {result.reachable ? 'Reachable' : result.skipped ? 'Not tested' : 'Failed'}: {result.detail}
139
+ </span>
140
+ {/if}
141
+ </li>
142
+ {:else}
143
+ <li class="iae-label">No candidates yet.</li>
144
+ {/each}
145
+ </ol>
146
+ {#if editable}
147
+ <fieldset class="iae-add" disabled={busy}>
148
+ <legend class="iae-label">Add candidate</legend>
149
+ <input aria-label="Provider" placeholder="provider" bind:value={draft.provider} />
150
+ <input aria-label="Preset" placeholder="preset" bind:value={draft.preset} />
151
+ <input aria-label="Nested alias" placeholder="alias" bind:value={draft.alias} />
152
+ <input aria-label="Model" placeholder="model" bind:value={draft.model} />
153
+ <input aria-label="Base URL" placeholder="base URL" bind:value={draft.baseUrl} />
154
+ <input aria-label="Key variable" placeholder="key variable (e.g. OPENROUTER_API_KEY)" bind:value={draft.apiKeyEnv} />
155
+ <button type="button" onclick={add}>Add</button>
156
+ </fieldset>
157
+ <div class="iae-actions">
158
+ <button type="submit" disabled={busy || !onSaveAlias || candidates.length === 0}>Save alias</button>
159
+ {#if !isNew && alias.source === 'app'}
160
+ <button
161
+ type="button"
162
+ disabled={busy || !onSaveAlias}
163
+ onclick={() => onSaveAlias?.({ name: alias.name, candidates: null })}
164
+ >
165
+ {alias.inConfig ? 'Revert to config' : 'Remove alias'}
166
+ </button>
167
+ {/if}
168
+ </div>
169
+ {/if}
170
+ </form>
171
+
172
+ <style>
173
+ @layer smrt.reset, smrt.structure, smrt.theme;
174
+ @layer smrt.theme {
175
+ .iae {
176
+ display: flex;
177
+ flex-direction: column;
178
+ gap: 0.375rem;
179
+ padding: 0.5rem 0;
180
+ border-bottom: var(--smrt-hairline);
181
+ }
182
+ .iae-head,
183
+ .iae-candidate,
184
+ .iae-actions {
185
+ display: flex;
186
+ gap: 0.5rem;
187
+ align-items: center;
188
+ flex-wrap: wrap;
189
+ }
190
+ .iae-list {
191
+ margin: 0;
192
+ padding-left: 1.25rem;
193
+ display: flex;
194
+ flex-direction: column;
195
+ gap: 0.25rem;
196
+ }
197
+ .iae-add {
198
+ display: flex;
199
+ flex-wrap: wrap;
200
+ gap: 0.375rem;
201
+ border: none;
202
+ padding: 0;
203
+ margin: 0;
204
+ }
205
+ .iae-label {
206
+ font-size: 0.8125rem;
207
+ color: var(--smrt-color-on-surface-variant);
208
+ }
209
+ }
210
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { InferenceAliasChange, InferenceAliasView, InferenceCandidateView, InferenceTestResultView } from '../types.js';
2
+ interface InferenceAliasEditorProps {
3
+ alias: InferenceAliasView;
4
+ editable: boolean;
5
+ busy: boolean;
6
+ isNew?: boolean;
7
+ onSaveAlias?: (change: InferenceAliasChange) => void;
8
+ onTestCandidate?: (candidate: InferenceCandidateView) => Promise<InferenceTestResultView>;
9
+ }
10
+ declare const InferenceAliasEditor: import("svelte").Component<InferenceAliasEditorProps, {}, "">;
11
+ type InferenceAliasEditor = ReturnType<typeof InferenceAliasEditor>;
12
+ export default InferenceAliasEditor;
13
+ //# sourceMappingURL=InferenceAliasEditor.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InferenceAliasEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InferenceAliasEditor.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAIrB,UAAU,yBAAyB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACrD,eAAe,CAAC,EAAE,CAChB,SAAS,EAAE,sBAAsB,KAC9B,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACvC;AAyJD,QAAA,MAAM,oBAAoB,+DAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,330 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The default view of `InferenceSettingsPanel`: the whole screen for a
4
+ * one-provider app. Mode, provider, base URL, key (set / not set, replace),
5
+ * model (with local discovery), Test connection and the default tier.
6
+ *
7
+ * The key lives in its own `<form>`: it is never part of the choices form's
8
+ * data, never held in component state, and the input is cleared as soon as
9
+ * it is handed to `onSaveKey`. The host stores it through smrt-secrets and
10
+ * only ever returns `keySet`.
11
+ */
12
+ import { untrack } from 'svelte';
13
+ import type {
14
+ InferenceConnectionRequest,
15
+ InferenceDefaultsChange,
16
+ InferenceDefaultsView,
17
+ InferenceDiscoveryView,
18
+ InferenceKeyChange,
19
+ InferenceProviderView,
20
+ InferenceSettingsMode,
21
+ InferenceSettingsTier,
22
+ InferenceTestResultView,
23
+ } from '../types.js';
24
+ import {
25
+ INFERENCE_SETTINGS_MODES,
26
+ INFERENCE_SETTINGS_TIERS,
27
+ } from '../types.js';
28
+
29
+ interface InferenceDefaultViewProps {
30
+ defaults: InferenceDefaultsView;
31
+ providers: InferenceProviderView[];
32
+ discoveredModels: InferenceDiscoveryView[];
33
+ busy: boolean;
34
+ onSaveDefaults?: (change: InferenceDefaultsChange) => void;
35
+ onSaveKey?: (change: InferenceKeyChange) => void | Promise<void>;
36
+ onTestConnection?: (
37
+ request: InferenceConnectionRequest,
38
+ ) => Promise<InferenceTestResultView>;
39
+ onDiscoverModels?: () => void;
40
+ }
41
+
42
+ const {
43
+ defaults,
44
+ providers,
45
+ discoveredModels,
46
+ busy,
47
+ onSaveDefaults,
48
+ onSaveKey,
49
+ onTestConnection,
50
+ onDiscoverModels,
51
+ }: InferenceDefaultViewProps = $props();
52
+
53
+ const MODE_LABELS: Record<InferenceSettingsMode, string> = {
54
+ byo: 'Bring your own key',
55
+ local: 'Local model server',
56
+ hosted: 'Hosted by this app',
57
+ off: 'Off',
58
+ };
59
+
60
+ let mode = $state<InferenceSettingsMode>(untrack(() => defaults.mode ?? 'byo'));
61
+ let provider = $state(untrack(() => defaults.provider ?? ''));
62
+ let baseUrl = $state(untrack(() => defaults.baseUrl ?? ''));
63
+ let model = $state(untrack(() => defaults.model ?? ''));
64
+ let defaultTier = $state<InferenceSettingsTier>(
65
+ untrack(() => defaults.defaultTier ?? 'good'),
66
+ );
67
+ let keySavedFor = $state<string | null>(null);
68
+ let keyError = $state<string | null>(null);
69
+ let testing = $state(false);
70
+ let testResult = $state<InferenceTestResultView | null>(null);
71
+
72
+ const needsProvider = $derived(mode === 'byo' || mode === 'local');
73
+ const visibleProviders = $derived(
74
+ mode === 'local' ? providers.filter((option) => option.local) : providers,
75
+ );
76
+ const selected = $derived(providers.find((option) => option.id === provider));
77
+ const keySet = $derived(
78
+ keySavedFor === provider ||
79
+ (defaults.keySet === true && (defaults.provider ?? '') === provider),
80
+ );
81
+ const discovered = $derived(
82
+ discoveredModels.flatMap((entry) =>
83
+ entry.models.map((id) => ({ id, service: entry.service })),
84
+ ),
85
+ );
86
+
87
+ function connectionRequest(): InferenceConnectionRequest {
88
+ const request: InferenceConnectionRequest = {};
89
+ if (selected?.kind === 'preset') request.preset = selected.id;
90
+ else if (provider) request.provider = provider;
91
+ if (baseUrl.trim()) request.baseUrl = baseUrl.trim();
92
+ if (model.trim()) request.model = model.trim();
93
+ return request;
94
+ }
95
+
96
+ function saveChoices(event: SubmitEvent): void {
97
+ event.preventDefault();
98
+ if (!onSaveDefaults) return;
99
+ const change: InferenceDefaultsChange = { mode, defaultTier };
100
+ if (needsProvider) {
101
+ if (provider) change.provider = provider;
102
+ if (baseUrl.trim()) change.baseUrl = baseUrl.trim();
103
+ if (model.trim()) change.model = model.trim();
104
+ }
105
+ onSaveDefaults(change);
106
+ }
107
+
108
+ async function saveKey(event: SubmitEvent): Promise<void> {
109
+ event.preventDefault();
110
+ const form = event.currentTarget as HTMLFormElement;
111
+ const input = form.elements.namedItem('apiKey') as HTMLInputElement | null;
112
+ const key = input?.value ?? '';
113
+ // Clear first: the value must not survive in the DOM whatever happens next.
114
+ if (input) input.value = '';
115
+ keyError = null;
116
+ if (!onSaveKey || !provider || !key.trim()) return;
117
+ const savedFor = provider;
118
+ const change: InferenceKeyChange = { provider: savedFor, key };
119
+ if (selected?.kind === 'preset') change.preset = selected.id;
120
+ if (baseUrl.trim()) change.baseUrl = baseUrl.trim();
121
+ try {
122
+ await onSaveKey(change);
123
+ keySavedFor = savedFor;
124
+ } catch {
125
+ keyError = 'The key was not saved.';
126
+ }
127
+ }
128
+
129
+ async function testConnection(): Promise<void> {
130
+ if (!onTestConnection) return;
131
+ testing = true;
132
+ testResult = null;
133
+ try {
134
+ testResult = await onTestConnection(connectionRequest());
135
+ } catch {
136
+ testResult = { reachable: false, detail: 'The test could not run.' };
137
+ } finally {
138
+ testing = false;
139
+ }
140
+ }
141
+ </script>
142
+
143
+ <div class="isp-default" data-testid="inference-default-view">
144
+ <form class="isp-form" onsubmit={saveChoices} data-testid="inference-choices-form">
145
+ <fieldset class="isp-modes" disabled={busy}>
146
+ <legend>Inference</legend>
147
+ {#each INFERENCE_SETTINGS_MODES as option (option)}
148
+ <label class="isp-radio">
149
+ <input type="radio" name="mode" value={option} bind:group={mode} />
150
+ {MODE_LABELS[option]}
151
+ </label>
152
+ {/each}
153
+ </fieldset>
154
+
155
+ {#if needsProvider}
156
+ <label class="isp-field">
157
+ <span>Provider</span>
158
+ <select name="provider" bind:value={provider} disabled={busy}>
159
+ <option value="">Choose a provider</option>
160
+ {#each visibleProviders as option (option.id)}
161
+ <option value={option.id}>{option.label}</option>
162
+ {/each}
163
+ </select>
164
+ </label>
165
+
166
+ <label class="isp-field">
167
+ <span>Base URL</span>
168
+ <input
169
+ name="baseUrl"
170
+ type="url"
171
+ bind:value={baseUrl}
172
+ placeholder={selected?.baseUrl ?? 'Provider default'}
173
+ disabled={busy}
174
+ />
175
+ </label>
176
+
177
+ <label class="isp-field">
178
+ <span>Model</span>
179
+ <input
180
+ name="model"
181
+ bind:value={model}
182
+ list="isp-discovered-models"
183
+ disabled={busy}
184
+ />
185
+ <datalist id="isp-discovered-models">
186
+ {#each discovered as found (`${found.service}:${found.id}`)}
187
+ <option value={found.id}>{found.service}</option>
188
+ {/each}
189
+ </datalist>
190
+ </label>
191
+ {#if onDiscoverModels}
192
+ <button type="button" class="isp-link" onclick={() => onDiscoverModels?.()} disabled={busy}>
193
+ Find local models
194
+ </button>
195
+ {/if}
196
+ {/if}
197
+
198
+ <label class="isp-field">
199
+ <span>Default tier</span>
200
+ <select name="defaultTier" bind:value={defaultTier} disabled={busy}>
201
+ {#each INFERENCE_SETTINGS_TIERS as tier (tier)}
202
+ <option value={tier}>{tier}</option>
203
+ {/each}
204
+ </select>
205
+ </label>
206
+
207
+ <div class="isp-actions">
208
+ <button type="submit" disabled={busy || !onSaveDefaults}>Save</button>
209
+ {#if needsProvider && onTestConnection}
210
+ <button
211
+ type="button"
212
+ onclick={testConnection}
213
+ disabled={busy || testing || !provider}
214
+ >
215
+ {testing ? 'Testing…' : 'Test connection'}
216
+ </button>
217
+ {/if}
218
+ </div>
219
+ {#if testResult}
220
+ <p
221
+ class="isp-result"
222
+ class:isp-result-ok={testResult.reachable}
223
+ role="status"
224
+ data-testid="inference-test-result"
225
+ >
226
+ {testResult.reachable ? 'Connected' : testResult.skipped ? 'Not tested' : 'Failed'}:
227
+ {testResult.detail}
228
+ </p>
229
+ {/if}
230
+ </form>
231
+
232
+ {#if needsProvider && provider && (selected?.requiresApiKey ?? true)}
233
+ <!-- Remount on a provider or endpoint change: a typed key never carries
234
+ over to another endpoint. -->
235
+ {#key `${provider}\n${baseUrl.trim()}`}
236
+ <form class="isp-key" onsubmit={saveKey} autocomplete="off" data-testid="inference-key-form">
237
+ <span class="isp-key-status" data-testid="inference-key-status">
238
+ API key: {keySet ? 'set' : 'not set'}
239
+ </span>
240
+ <label class="isp-field">
241
+ <span>{keySet ? 'Replace key' : 'Add key'}</span>
242
+ <input
243
+ name="apiKey"
244
+ type="password"
245
+ autocomplete="off"
246
+ spellcheck="false"
247
+ disabled={busy || !onSaveKey}
248
+ />
249
+ </label>
250
+ <button type="submit" disabled={busy || !onSaveKey}>
251
+ {keySet ? 'Replace key' : 'Save key'}
252
+ </button>
253
+ <p class="isp-hint">
254
+ The key is stored on the server and only ever sent to this provider's endpoint.
255
+ </p>
256
+ {#if keyError}
257
+ <p class="isp-result" role="alert">{keyError}</p>
258
+ {/if}
259
+ </form>
260
+ {/key}
261
+ {/if}
262
+ </div>
263
+
264
+ <style>
265
+ @layer smrt.reset, smrt.structure, smrt.theme;
266
+ @layer smrt.theme {
267
+ .isp-default {
268
+ display: flex;
269
+ flex-direction: column;
270
+ gap: 1rem;
271
+ }
272
+ .isp-form,
273
+ .isp-key {
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 0.75rem;
277
+ }
278
+ .isp-modes {
279
+ display: flex;
280
+ flex-wrap: wrap;
281
+ gap: 0.75rem;
282
+ border: none;
283
+ padding: 0;
284
+ margin: 0;
285
+ }
286
+ .isp-modes legend {
287
+ font-weight: 600;
288
+ margin-bottom: 0.25rem;
289
+ }
290
+ .isp-radio {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 0.375rem;
294
+ }
295
+ .isp-field {
296
+ display: flex;
297
+ flex-direction: column;
298
+ gap: 0.25rem;
299
+ font-size: 0.875rem;
300
+ }
301
+ .isp-field input,
302
+ .isp-field select {
303
+ padding: 0.4rem 0.5rem;
304
+ border: var(--smrt-hairline);
305
+ border-radius: 0.375rem;
306
+ background: var(--smrt-color-surface);
307
+ color: inherit;
308
+ font: inherit;
309
+ }
310
+ .isp-actions {
311
+ display: flex;
312
+ gap: 0.5rem;
313
+ }
314
+ .isp-link {
315
+ align-self: flex-start;
316
+ }
317
+ .isp-key-status {
318
+ font-weight: 600;
319
+ }
320
+ .isp-hint,
321
+ .isp-result {
322
+ margin: 0;
323
+ font-size: 0.8125rem;
324
+ color: var(--smrt-color-on-surface-variant);
325
+ }
326
+ .isp-result-ok {
327
+ color: var(--smrt-color-success, inherit);
328
+ }
329
+ }
330
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { InferenceConnectionRequest, InferenceDefaultsChange, InferenceDefaultsView, InferenceDiscoveryView, InferenceKeyChange, InferenceProviderView, InferenceTestResultView } from '../types.js';
2
+ interface InferenceDefaultViewProps {
3
+ defaults: InferenceDefaultsView;
4
+ providers: InferenceProviderView[];
5
+ discoveredModels: InferenceDiscoveryView[];
6
+ busy: boolean;
7
+ onSaveDefaults?: (change: InferenceDefaultsChange) => void;
8
+ onSaveKey?: (change: InferenceKeyChange) => void | Promise<void>;
9
+ onTestConnection?: (request: InferenceConnectionRequest) => Promise<InferenceTestResultView>;
10
+ onDiscoverModels?: () => void;
11
+ }
12
+ declare const InferenceDefaultView: import("svelte").Component<InferenceDefaultViewProps, {}, "">;
13
+ type InferenceDefaultView = ReturnType<typeof InferenceDefaultView>;
14
+ export default InferenceDefaultView;
15
+ //# sourceMappingURL=InferenceDefaultView.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InferenceDefaultView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InferenceDefaultView.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EAGrB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAOrB,UAAU,yBAAyB;IACjC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,SAAS,EAAE,qBAAqB,EAAE,CAAC;IACnC,gBAAgB,EAAE,sBAAsB,EAAE,CAAC;IAC3C,IAAI,EAAE,OAAO,CAAC;IACd,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC3D,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,CACjB,OAAO,EAAE,0BAA0B,KAChC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACtC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AA+MD,QAAA,MAAM,oBAAoB,+DAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}