@kpritam/grimoire-output-docusaurus 0.1.8

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/internal/assets.d.ts +9 -0
  7. package/dist/internal/assets.js +50 -0
  8. package/dist/internal/docusaurusConfig.d.ts +9 -0
  9. package/dist/internal/docusaurusConfig.js +259 -0
  10. package/dist/internal/spellbookAssets.d.ts +39 -0
  11. package/dist/internal/spellbookAssets.js +68 -0
  12. package/dist/layer.d.ts +3 -0
  13. package/dist/layer.js +6 -0
  14. package/dist/shared.d.ts +10 -0
  15. package/dist/shared.js +36 -0
  16. package/dist/upstream.d.ts +6 -0
  17. package/dist/upstream.js +84 -0
  18. package/package.json +59 -0
  19. package/src/index.ts +1 -0
  20. package/src/internal/assets.ts +66 -0
  21. package/src/internal/docusaurusConfig.ts +281 -0
  22. package/src/internal/spellbookAssets.ts +80 -0
  23. package/src/layer.ts +12 -0
  24. package/src/shared.ts +43 -0
  25. package/src/upstream.ts +119 -0
  26. package/templates/spellbook/spellbookPlugin.ts +156 -0
  27. package/templates/spellbook/src/components/SpellbookChat/ChatEngine.ts +79 -0
  28. package/templates/spellbook/src/components/SpellbookChat/ChatErrorBoundary.tsx +65 -0
  29. package/templates/spellbook/src/components/SpellbookChat/Markdown.tsx +259 -0
  30. package/templates/spellbook/src/components/SpellbookChat/README.md +111 -0
  31. package/templates/spellbook/src/components/SpellbookChat/SettingsPanel.tsx +376 -0
  32. package/templates/spellbook/src/components/SpellbookChat/VoiceMode.tsx +867 -0
  33. package/templates/spellbook/src/components/SpellbookChat/index.tsx +744 -0
  34. package/templates/spellbook/src/components/SpellbookChat/markdown.module.css +343 -0
  35. package/templates/spellbook/src/components/SpellbookChat/secretStore.ts +106 -0
  36. package/templates/spellbook/src/components/SpellbookChat/streamProviders/anthropic.ts +36 -0
  37. package/templates/spellbook/src/components/SpellbookChat/streamProviders/createCloudProvider.ts +112 -0
  38. package/templates/spellbook/src/components/SpellbookChat/streamProviders/google.ts +33 -0
  39. package/templates/spellbook/src/components/SpellbookChat/streamProviders/index.ts +32 -0
  40. package/templates/spellbook/src/components/SpellbookChat/streamProviders/mapFinishReason.ts +23 -0
  41. package/templates/spellbook/src/components/SpellbookChat/streamProviders/ollama.ts +44 -0
  42. package/templates/spellbook/src/components/SpellbookChat/streamProviders/openai.ts +34 -0
  43. package/templates/spellbook/src/components/SpellbookChat/streamProviders/openaiRealtime.ts +320 -0
  44. package/templates/spellbook/src/components/SpellbookChat/streamProviders/types.ts +172 -0
  45. package/templates/spellbook/src/components/SpellbookChat/streamProviders/webllm.ts +214 -0
  46. package/templates/spellbook/src/components/SpellbookChat/styles.module.css +852 -0
  47. package/templates/spellbook/src/components/SpellbookChat/systemPrompt.ts +107 -0
  48. package/templates/spellbook/src/components/SpellbookChat/transformers-ssr-stub.ts +16 -0
  49. package/templates/spellbook/src/components/SpellbookChat/types.ts +52 -0
  50. package/templates/spellbook/src/components/SpellbookChat/useBundleLoader.ts +46 -0
  51. package/templates/spellbook/src/components/SpellbookChat/useChatEngine.ts +524 -0
  52. package/templates/spellbook/src/components/SpellbookChat/useEmbeddings.ts +147 -0
  53. package/templates/spellbook/src/components/SpellbookChat/useRetrieval.ts +377 -0
  54. package/templates/spellbook/src/components/SpellbookChat/useSileroVAD.ts +236 -0
  55. package/templates/spellbook/src/components/SpellbookChat/useSpeechRecognition.ts +271 -0
  56. package/templates/spellbook/src/components/SpellbookChat/useSpeechSynthesis.ts +229 -0
  57. package/templates/spellbook/src/components/SpellbookChat/useUnifiedSTT.ts +134 -0
  58. package/templates/spellbook/src/components/SpellbookChat/useWhisperSTT.ts +411 -0
  59. package/templates/spellbook/src/components/SpellbookChat/vad-ssr-stub.ts +25 -0
  60. package/templates/spellbook/src/components/SpellbookChat/voiceDebug.ts +60 -0
  61. package/templates/spellbook/src/components/SpellbookChat/voiceFsm.ts +196 -0
  62. package/templates/spellbook/src/components/SpellbookChat/voiceStyles.module.css +334 -0
  63. package/templates/spellbook/src/components/SpellbookChat/webllm-ssr-stub.ts +8 -0
  64. package/templates/spellbook/src/components/SpellbookChatDisabled.tsx +20 -0
  65. package/templates/spellbook/src/theme/Root.tsx +29 -0
@@ -0,0 +1,376 @@
1
+ import {
2
+ type FormEvent,
3
+ type ReactNode,
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useState,
8
+ } from "react";
9
+
10
+ import {
11
+ clearSecret,
12
+ getSecret,
13
+ setSecret,
14
+ } from "./secretStore";
15
+ import { loadProvider } from "./streamProviders/index";
16
+ import {
17
+ PROVIDER_ORDER,
18
+ STORAGE_KEYS,
19
+ } from "./streamProviders/types";
20
+ import type { ProviderId, StreamProvider } from "./streamProviders/types";
21
+
22
+ import {
23
+ notifySettingsChanged,
24
+ readActiveProviderId,
25
+ } from "./useChatEngine";
26
+
27
+ import styles from "./styles.module.css";
28
+
29
+ const OLLAMA_CUSTOM = "__custom__";
30
+
31
+ export interface SettingsPanelProps {
32
+ readonly variant?: "inline" | "card";
33
+ readonly onClose?: () => void;
34
+ /** Live engine status (e.g. WebLLM download %) while the panel is open. */
35
+ readonly remoteStatusMessage?: string;
36
+ }
37
+
38
+ export default function SettingsPanel(props: SettingsPanelProps): ReactNode {
39
+ const { variant = "inline", onClose, remoteStatusMessage } = props;
40
+ const labelId = useId();
41
+ const [providerId, setProviderId] = useState<ProviderId>(() =>
42
+ readActiveProviderId(),
43
+ );
44
+ const [provider, setProvider] = useState<StreamProvider | null>(null);
45
+ const [pickerMeta, setPickerMeta] = useState<
46
+ Partial<Record<ProviderId, { displayName: string; tagline: string }>>
47
+ >({});
48
+ const [apiKey, setApiKey] = useState("");
49
+ const [baseUrl, setBaseUrl] = useState("");
50
+ const [tokenEndpoint, setTokenEndpoint] = useState("");
51
+ const [modelSelect, setModelSelect] = useState("");
52
+ const [ollamaCustomModel, setOllamaCustomModel] = useState("");
53
+ const [ollamaPreset, setOllamaPreset] = useState<string>(OLLAMA_CUSTOM);
54
+
55
+ const loadMeta = useCallback(async (id: ProviderId) => {
56
+ const p = await loadProvider(id);
57
+ setProvider(p);
58
+ const mk = STORAGE_KEYS.field(id, "model");
59
+ const storedModel = localStorage.getItem(mk)?.trim() ?? "";
60
+ const defaultId = p.models[0]?.id ?? "";
61
+
62
+ if (id === "ollama") {
63
+ const known = p.models.some((m) => m.id === storedModel);
64
+ if (storedModel && !known) {
65
+ setOllamaPreset(OLLAMA_CUSTOM);
66
+ setOllamaCustomModel(storedModel);
67
+ setModelSelect(storedModel);
68
+ } else {
69
+ const useId = storedModel && known ? storedModel : defaultId;
70
+ setOllamaPreset(useId);
71
+ setOllamaCustomModel("");
72
+ setModelSelect(useId);
73
+ }
74
+ } else {
75
+ setModelSelect(storedModel || defaultId);
76
+ }
77
+
78
+ // API keys live in memory; pre-fill the field only if the user has
79
+ // already entered one this session.
80
+ setApiKey(getSecret(id) ?? "");
81
+ setBaseUrl(localStorage.getItem(STORAGE_KEYS.field(id, "baseUrl")) ?? "");
82
+ setTokenEndpoint(
83
+ localStorage.getItem(STORAGE_KEYS.field(id, "tokenEndpoint")) ?? "",
84
+ );
85
+ }, []);
86
+
87
+ useEffect(() => {
88
+ void loadMeta(providerId);
89
+ }, [providerId, loadMeta]);
90
+
91
+ useEffect(() => {
92
+ void Promise.all(
93
+ PROVIDER_ORDER.map((id) =>
94
+ loadProvider(id).then((p) => [id, p] as const),
95
+ ),
96
+ ).then((pairs) => {
97
+ const next: Partial<
98
+ Record<ProviderId, { displayName: string; tagline: string }>
99
+ > = {};
100
+ for (const [id, p] of pairs) {
101
+ next[id] = { displayName: p.displayName, tagline: p.tagline };
102
+ }
103
+ setPickerMeta(next);
104
+ });
105
+ }, []);
106
+
107
+ const onProviderPick = (id: ProviderId): void => {
108
+ setProviderId(id);
109
+ if (typeof localStorage !== "undefined") {
110
+ localStorage.setItem(STORAGE_KEYS.activeProvider, id);
111
+ }
112
+ };
113
+
114
+ const save = (e?: FormEvent): void => {
115
+ e?.preventDefault();
116
+ if (typeof localStorage === "undefined") return;
117
+ const p = provider;
118
+ if (!p) return;
119
+
120
+ for (const field of p.configFields) {
121
+ if (field.key === "apiKey") {
122
+ // Secrets are NEVER persisted to disk — in-memory only.
123
+ setSecret(providerId, apiKey);
124
+ }
125
+ if (field.key === "baseUrl") {
126
+ const t = baseUrl.trim();
127
+ if (t) {
128
+ localStorage.setItem(STORAGE_KEYS.field(providerId, "baseUrl"), t);
129
+ } else {
130
+ localStorage.removeItem(STORAGE_KEYS.field(providerId, "baseUrl"));
131
+ }
132
+ }
133
+ if (field.key === "tokenEndpoint") {
134
+ const t = tokenEndpoint.trim();
135
+ if (t) {
136
+ localStorage.setItem(
137
+ STORAGE_KEYS.field(providerId, "tokenEndpoint"),
138
+ t,
139
+ );
140
+ } else {
141
+ localStorage.removeItem(
142
+ STORAGE_KEYS.field(providerId, "tokenEndpoint"),
143
+ );
144
+ }
145
+ }
146
+ }
147
+
148
+ let effectiveModel = modelSelect.trim();
149
+ if (providerId === "ollama" && ollamaPreset === OLLAMA_CUSTOM) {
150
+ effectiveModel = ollamaCustomModel.trim();
151
+ } else if (providerId === "ollama") {
152
+ effectiveModel = ollamaPreset.trim();
153
+ }
154
+
155
+ if (effectiveModel) {
156
+ localStorage.setItem(
157
+ STORAGE_KEYS.field(providerId, "model"),
158
+ effectiveModel,
159
+ );
160
+ } else {
161
+ localStorage.removeItem(STORAGE_KEYS.field(providerId, "model"));
162
+ }
163
+
164
+ localStorage.setItem(STORAGE_KEYS.activeProvider, providerId);
165
+ notifySettingsChanged();
166
+ onClose?.();
167
+ };
168
+
169
+ const clearCurrent = (): void => {
170
+ if (typeof localStorage === "undefined" || !provider) return;
171
+ clearSecret(providerId);
172
+ for (const field of provider.configFields) {
173
+ if (field.key === "baseUrl") {
174
+ localStorage.removeItem(STORAGE_KEYS.field(providerId, "baseUrl"));
175
+ }
176
+ if (field.key === "tokenEndpoint") {
177
+ localStorage.removeItem(
178
+ STORAGE_KEYS.field(providerId, "tokenEndpoint"),
179
+ );
180
+ }
181
+ }
182
+ localStorage.removeItem(STORAGE_KEYS.field(providerId, "model"));
183
+ if (providerId === "ollama") {
184
+ const d = provider.models[0]?.id ?? "";
185
+ setOllamaPreset(d || OLLAMA_CUSTOM);
186
+ setOllamaCustomModel("");
187
+ setModelSelect(d);
188
+ } else {
189
+ setModelSelect(provider.models[0]?.id ?? "");
190
+ }
191
+ setApiKey("");
192
+ setBaseUrl("");
193
+ setTokenEndpoint("");
194
+ notifySettingsChanged();
195
+ };
196
+
197
+ if (!provider) {
198
+ return (
199
+ <section
200
+ className={
201
+ variant === "card" ? styles.settingsCard : styles.settingsInline
202
+ }
203
+ >
204
+ <p className={styles.settingsHelp}>Loading provider…</p>
205
+ </section>
206
+ );
207
+ }
208
+
209
+ const showOllamaCustom =
210
+ providerId === "ollama" && ollamaPreset === OLLAMA_CUSTOM;
211
+
212
+ return (
213
+ <section
214
+ className={
215
+ variant === "card" ? styles.settingsCard : styles.settingsInline
216
+ }
217
+ aria-labelledby={labelId}
218
+ >
219
+ <h3 id={labelId} className={styles.settingsTitle}>
220
+ AI provider
221
+ </h3>
222
+ <p className={styles.settingsHelp}>
223
+ Choose how the assistant talks to a model. Keys stay in this tab's
224
+ memory only (cleared on refresh); URLs and model choices persist
225
+ locally. Nothing is proxied through a third-party server.
226
+ </p>
227
+
228
+ <form className={styles.settingsForm} onSubmit={save}>
229
+ <label className={styles.settingsLabel} htmlFor="chat-provider">
230
+ Provider
231
+ </label>
232
+ <select
233
+ id="chat-provider"
234
+ className={styles.settingsSelect}
235
+ value={providerId}
236
+ onChange={(ev) => onProviderPick(ev.target.value as ProviderId)}
237
+ >
238
+ {PROVIDER_ORDER.map((id) => {
239
+ const meta = pickerMeta[id];
240
+ const label = meta
241
+ ? `${meta.displayName} — ${meta.tagline}`
242
+ : id;
243
+ return (
244
+ <option key={id} value={id}>
245
+ {label}
246
+ </option>
247
+ );
248
+ })}
249
+ </select>
250
+ <p className={styles.providerTagline}>{provider.tagline}</p>
251
+
252
+ {provider.id === "webllm" ? (
253
+ <p className={styles.settingsHelp}>
254
+ Models download on first use (about 300&nbsp;MB–2&nbsp;GB) and
255
+ stay cached in your browser (IndexedDB).
256
+ </p>
257
+ ) : null}
258
+
259
+ {remoteStatusMessage ? (
260
+ <div className={styles.preloadPanel}>
261
+ <p className={styles.preloadText}>{remoteStatusMessage}</p>
262
+ <div className={styles.progressTrack}>
263
+ <div className={styles.progressFillIndeterminate} />
264
+ </div>
265
+ </div>
266
+ ) : null}
267
+
268
+ {provider.configFields.map((field) => (
269
+ <div key={field.key} className={styles.settingsFieldGroup}>
270
+ <label
271
+ className={styles.settingsLabel}
272
+ htmlFor={`chat-${provider.id}-${field.key}`}
273
+ >
274
+ {field.label}
275
+ {field.required ? " *" : ""}
276
+ </label>
277
+ <input
278
+ id={`chat-${provider.id}-${field.key}`}
279
+ className={styles.settingsInput}
280
+ type={field.secret ? "password" : "text"}
281
+ autoComplete="off"
282
+ spellCheck={false}
283
+ placeholder={field.placeholder}
284
+ value={
285
+ field.key === "apiKey"
286
+ ? apiKey
287
+ : field.key === "tokenEndpoint"
288
+ ? tokenEndpoint
289
+ : baseUrl
290
+ }
291
+ onChange={(ev) => {
292
+ const v = ev.target.value;
293
+ if (field.key === "apiKey") setApiKey(v);
294
+ else if (field.key === "tokenEndpoint") setTokenEndpoint(v);
295
+ else setBaseUrl(v);
296
+ }}
297
+ />
298
+ {field.helpText ? (
299
+ <p className={styles.fieldHelp}>{field.helpText}</p>
300
+ ) : null}
301
+ </div>
302
+ ))}
303
+
304
+ <label className={styles.settingsLabel} htmlFor="chat-model">
305
+ Model
306
+ </label>
307
+ {provider.id === "ollama" ? (
308
+ <>
309
+ <select
310
+ id="chat-model"
311
+ className={styles.settingsSelect}
312
+ value={ollamaPreset}
313
+ onChange={(ev) => {
314
+ const v = ev.target.value;
315
+ setOllamaPreset(v);
316
+ if (v !== OLLAMA_CUSTOM) {
317
+ setModelSelect(v);
318
+ }
319
+ }}
320
+ >
321
+ {provider.models.map((m) => (
322
+ <option key={m.id} value={m.id}>
323
+ {m.label}
324
+ {m.note ? ` · ${m.note}` : ""}
325
+ </option>
326
+ ))}
327
+ <option value={OLLAMA_CUSTOM}>(custom…)</option>
328
+ </select>
329
+ {showOllamaCustom ? (
330
+ <input
331
+ className={styles.settingsInput}
332
+ style={{ marginTop: "0.5rem" }}
333
+ id="chat-model-custom"
334
+ type="text"
335
+ placeholder="your-model:tag"
336
+ value={ollamaCustomModel}
337
+ onChange={(ev) => setOllamaCustomModel(ev.target.value)}
338
+ />
339
+ ) : null}
340
+ <p className={styles.fieldHelp}>
341
+ Use any tag you have pulled locally; the preset list is a
342
+ shortcut.
343
+ </p>
344
+ </>
345
+ ) : (
346
+ <select
347
+ id="chat-model"
348
+ className={styles.settingsSelect}
349
+ value={modelSelect}
350
+ onChange={(ev) => setModelSelect(ev.target.value)}
351
+ >
352
+ {provider.models.map((m) => (
353
+ <option key={m.id} value={m.id}>
354
+ {m.label}
355
+ {m.note ? ` · ${m.note}` : ""}
356
+ </option>
357
+ ))}
358
+ </select>
359
+ )}
360
+
361
+ <div className={styles.settingsActions}>
362
+ <button type="submit" className={styles.buttonPrimary}>
363
+ Save
364
+ </button>
365
+ <button
366
+ type="button"
367
+ className={styles.buttonGhost}
368
+ onClick={clearCurrent}
369
+ >
370
+ Clear {provider.displayName}
371
+ </button>
372
+ </div>
373
+ </form>
374
+ </section>
375
+ );
376
+ }