@tpitre/story-ui 4.15.0 → 4.16.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.
- package/dist/cli/setup.js +3 -3
- package/dist/mcp-server/index.js +0 -6
- package/dist/mcp-server/routes/canvasGenerate.d.ts +23 -1
- package/dist/mcp-server/routes/canvasGenerate.d.ts.map +1 -1
- package/dist/mcp-server/routes/canvasGenerate.js +156 -26
- package/dist/mcp-server/routes/canvasSave.d.ts +7 -0
- package/dist/mcp-server/routes/canvasSave.d.ts.map +1 -1
- package/dist/mcp-server/routes/canvasSave.js +21 -7
- package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -1
- package/dist/story-generator/llm-providers/claude-provider.js +0 -39
- package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -1
- package/dist/story-generator/llm-providers/gemini-provider.js +12 -36
- package/dist/story-generator/llm-providers/index.js +3 -3
- package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -1
- package/dist/story-generator/llm-providers/openai-provider.js +10 -50
- package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -1
- package/dist/story-generator/llm-providers/settings-manager.js +4 -7
- package/dist/templates/StoryUI/StoryUIPanel.css +30 -1
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +7 -16
- package/dist/templates/StoryUI/StoryUIPanel.tsx +7 -16
- package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts +1 -1
- package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts.map +1 -1
- package/dist/templates/StoryUI/voice/VoiceCanvas.js +37 -9
- package/dist/templates/StoryUI/voice/VoiceCanvas.tsx +87 -64
- package/package.json +5 -3
- package/templates/StoryUI/StoryUIPanel.css +30 -1
- package/templates/StoryUI/StoryUIPanel.tsx +7 -16
- package/templates/StoryUI/voice/VoiceCanvas.tsx +87 -64
- package/dist/mcp-server/routes/canvasIntent.d.ts +0 -15
- package/dist/mcp-server/routes/canvasIntent.d.ts.map +0 -1
- package/dist/mcp-server/routes/canvasIntent.js +0 -553
- package/dist/mcp-server/routes/canvasPreview.d.ts +0 -14
- package/dist/mcp-server/routes/canvasPreview.d.ts.map +0 -1
- package/dist/mcp-server/routes/canvasPreview.js +0 -25
- package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts +0 -14
- package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts.map +0 -1
- package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.js +0 -90
- package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +0 -168
- package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts +0 -21
- package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts.map +0 -1
- package/dist/templates/StoryUI/voice/canvas/componentRegistry.js +0 -24
- package/dist/templates/StoryUI/voice/canvas/componentRegistry.ts +0 -30
- package/dist/templates/StoryUI/voice/canvas/operations.d.ts +0 -8
- package/dist/templates/StoryUI/voice/canvas/operations.d.ts.map +0 -1
- package/dist/templates/StoryUI/voice/canvas/operations.js +0 -189
- package/dist/templates/StoryUI/voice/canvas/operations.ts +0 -233
- package/dist/templates/StoryUI/voice/canvas/types.d.ts +0 -89
- package/dist/templates/StoryUI/voice/canvas/types.d.ts.map +0 -1
- package/dist/templates/StoryUI/voice/canvas/types.js +0 -2
- package/dist/templates/StoryUI/voice/canvas/types.ts +0 -106
- package/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +0 -168
- package/templates/StoryUI/voice/canvas/componentRegistry.ts +0 -30
- package/templates/StoryUI/voice/canvas/operations.ts +0 -233
- package/templates/StoryUI/voice/canvas/types.ts +0 -106
package/dist/cli/setup.js
CHANGED
|
@@ -206,21 +206,21 @@ const LLM_PROVIDERS = {
|
|
|
206
206
|
claude: {
|
|
207
207
|
name: 'Claude (Anthropic)',
|
|
208
208
|
envKey: 'ANTHROPIC_API_KEY',
|
|
209
|
-
models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-
|
|
209
|
+
models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
|
|
210
210
|
docsUrl: 'https://console.anthropic.com/',
|
|
211
211
|
description: 'Recommended - Best for complex reasoning and code quality'
|
|
212
212
|
},
|
|
213
213
|
openai: {
|
|
214
214
|
name: 'OpenAI (GPT)',
|
|
215
215
|
envKey: 'OPENAI_API_KEY',
|
|
216
|
-
models: ['gpt-4
|
|
216
|
+
models: ['gpt-5.4', 'gpt-5.4-mini', 'o4-mini'],
|
|
217
217
|
docsUrl: 'https://platform.openai.com/api-keys',
|
|
218
218
|
description: 'Versatile and fast'
|
|
219
219
|
},
|
|
220
220
|
gemini: {
|
|
221
221
|
name: 'Google Gemini',
|
|
222
222
|
envKey: 'GEMINI_API_KEY',
|
|
223
|
-
models: ['gemini-3.1-pro-preview', 'gemini-
|
|
223
|
+
models: ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash'],
|
|
224
224
|
docsUrl: 'https://aistudio.google.com/app/apikey',
|
|
225
225
|
description: 'Cost-effective with good performance'
|
|
226
226
|
}
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -23,10 +23,8 @@ import { getProviders, getModels, configureProviderRoute, validateApiKey, setDef
|
|
|
23
23
|
import { listFrameworks, detectCurrentFramework, getFrameworkDetails, validateStoryForFramework, postProcessStoryForFramework, } from './routes/frameworks.js';
|
|
24
24
|
import mcpRemoteRouter from './routes/mcpRemote.js';
|
|
25
25
|
// Voice Canvas endpoints
|
|
26
|
-
import { canvasIntentHandler, warmCanvasComponentCache } from './routes/canvasIntent.js';
|
|
27
26
|
import { canvasSaveHandler } from './routes/canvasSave.js';
|
|
28
27
|
import { canvasGenerateHandler, ensureVoiceCanvasStory } from './routes/canvasGenerate.js';
|
|
29
|
-
import { canvasPreviewHandler } from './routes/canvasPreview.js';
|
|
30
28
|
import { getAdapterRegistry } from '../story-generator/framework-adapters/index.js';
|
|
31
29
|
// Manifest — story ↔ chat source of truth
|
|
32
30
|
import { manifestGetHandler, manifestPatchHandler, manifestDeleteHandler, manifestReconcileHandler, manifestPollHandler, } from './routes/manifest.js';
|
|
@@ -110,9 +108,7 @@ app.post('/mcp/generate-story', generateStoryFromPrompt);
|
|
|
110
108
|
app.post('/mcp/generate-story-stream', generateStoryFromPromptStream);
|
|
111
109
|
// Voice Canvas endpoints
|
|
112
110
|
app.post('/mcp/canvas-generate', canvasGenerateHandler); // generate + write voice-canvas.stories.tsx
|
|
113
|
-
app.post('/mcp/canvas-preview', canvasPreviewHandler); // undo/redo: rewrite voice-canvas.stories.tsx
|
|
114
111
|
app.post('/mcp/canvas-save', canvasSaveHandler); // save canvas to named .stories.tsx
|
|
115
|
-
app.post('/mcp/canvas-intent', canvasIntentHandler); // legacy (kept for compatibility)
|
|
116
112
|
// Manifest — story ↔ chat source of truth
|
|
117
113
|
// NOTE: /reconcile must be registered BEFORE /:fileName to avoid route conflict
|
|
118
114
|
app.get('/story-ui/manifest/poll', manifestPollHandler);
|
|
@@ -883,8 +879,6 @@ if (storybookProxyEnabled) {
|
|
|
883
879
|
app.listen(PORT, () => {
|
|
884
880
|
console.error(`MCP server running on port ${PORT}`);
|
|
885
881
|
console.error(`Stories will be generated to: ${config.generatedStoriesPath}`);
|
|
886
|
-
// Pre-warm canvas component cache in background so first voice request is fast
|
|
887
|
-
warmCanvasComponentCache().catch(() => { });
|
|
888
882
|
// Ensure voice-canvas scratchpad story file exists before client polling starts.
|
|
889
883
|
// If it's missing, the first canvas generate creates it, triggering a false-positive
|
|
890
884
|
// "externally generated story" detection which reloads the page and kills Voice Canvas.
|
|
@@ -15,11 +15,33 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Request, Response } from 'express';
|
|
17
17
|
export declare const VOICE_CANVAS_STORY_ID = "generated-voice-canvas--default";
|
|
18
|
-
export declare function ensureReactLive(): void
|
|
18
|
+
export declare function ensureReactLive(): Promise<void>;
|
|
19
19
|
/**
|
|
20
20
|
* Write the static voice-canvas story template if it doesn't exist yet.
|
|
21
21
|
* Subsequent calls are no-ops — the file never changes after initial creation.
|
|
22
22
|
*/
|
|
23
23
|
export declare function ensureVoiceCanvasStory(storiesDir: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* If the LLM forgot to add a render() call (required by react-live noInline mode),
|
|
26
|
+
* detect the last defined PascalCase component and append render(<ComponentName />).
|
|
27
|
+
* This prevents the "No-Inline evaluations must call render" error when voice input
|
|
28
|
+
* is ambiguous or short and the LLM skips the final line.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ensureRenderCall(code: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Extract the canvas component code from the LLM response.
|
|
33
|
+
* Handles markdown code fences and stray text.
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractCanvasCode(response: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Scan LLM-generated canvas code for dangerous patterns and neutralize them.
|
|
38
|
+
*
|
|
39
|
+
* Instead of rejecting the entire response, each dangerous token is replaced
|
|
40
|
+
* with a safe alternative (typically a comment + `undefined` or `void(`) so
|
|
41
|
+
* the rest of the generated JSX remains functional.
|
|
42
|
+
*
|
|
43
|
+
* Returns the sanitized code string. Logs a warning for every pattern found.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sanitizeCanvasCode(code: string): string;
|
|
24
46
|
export declare function canvasGenerateHandler(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
25
47
|
//# sourceMappingURL=canvasGenerate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkB5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAuIvE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAmCrD;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc1D;AAgED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBvD;AAID,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CA0GtE"}
|
|
@@ -15,12 +15,17 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import path from 'path';
|
|
18
|
-
import {
|
|
18
|
+
import { exec } from 'child_process';
|
|
19
|
+
import { promisify } from 'util';
|
|
20
|
+
const execAsync = promisify(exec);
|
|
19
21
|
import { loadUserConfig } from '../../story-generator/configLoader.js';
|
|
20
22
|
import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
|
|
21
23
|
import { buildClaudePrompt } from '../../story-generator/promptGenerator.js';
|
|
22
24
|
import { chatCompletion } from '../../story-generator/llm-providers/story-llm-service.js';
|
|
23
25
|
import { logger } from '../../story-generator/logger.js';
|
|
26
|
+
// ── Component discovery cache ─────────────────────────────────
|
|
27
|
+
let _componentCache = null;
|
|
28
|
+
const COMPONENT_CACHE_TTL = 300000; // 5 minutes
|
|
24
29
|
// ── Constants ─────────────────────────────────────────────────
|
|
25
30
|
export const VOICE_CANVAS_STORY_ID = 'generated-voice-canvas--default';
|
|
26
31
|
const VOICE_CANVAS_STORY_FILE = 'voice-canvas.stories.tsx';
|
|
@@ -122,6 +127,8 @@ export const Default: StoryObj = {
|
|
|
122
127
|
|
|
123
128
|
useEffect(() => {
|
|
124
129
|
const handler = (e: MessageEvent) => {
|
|
130
|
+
// Only accept messages from same origin to prevent cross-origin code injection
|
|
131
|
+
if (e.origin !== window.location.origin) return;
|
|
125
132
|
if (e.data?.type === 'VOICE_CANVAS_UPDATE' && typeof e.data.code === 'string') {
|
|
126
133
|
setCode(e.data.code);
|
|
127
134
|
try { localStorage.setItem('${LS_KEY}', e.data.code); } catch {}
|
|
@@ -149,31 +156,43 @@ export const Default: StoryObj = {
|
|
|
149
156
|
* Detects pnpm / yarn / npm automatically.
|
|
150
157
|
*/
|
|
151
158
|
let reactLiveChecked = false;
|
|
152
|
-
|
|
159
|
+
let reactLiveInstalling = null;
|
|
160
|
+
export async function ensureReactLive() {
|
|
153
161
|
if (reactLiveChecked)
|
|
154
162
|
return;
|
|
155
|
-
|
|
163
|
+
// If another request is already installing, wait for it
|
|
164
|
+
if (reactLiveInstalling)
|
|
165
|
+
return reactLiveInstalling;
|
|
156
166
|
const cwd = process.cwd();
|
|
157
167
|
const reactLiveDir = path.join(cwd, 'node_modules', 'react-live');
|
|
158
|
-
if (fs.existsSync(reactLiveDir))
|
|
168
|
+
if (fs.existsSync(reactLiveDir)) {
|
|
169
|
+
reactLiveChecked = true;
|
|
159
170
|
return;
|
|
171
|
+
}
|
|
160
172
|
logger.log('[canvas-generate] react-live not found — installing...');
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
173
|
+
reactLiveInstalling = (async () => {
|
|
174
|
+
try {
|
|
175
|
+
let cmd = 'npm install react-live --save';
|
|
176
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
|
|
177
|
+
cmd = 'pnpm add react-live';
|
|
178
|
+
}
|
|
179
|
+
else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
|
|
180
|
+
cmd = 'yarn add react-live';
|
|
181
|
+
}
|
|
182
|
+
await execAsync(cmd, { cwd });
|
|
183
|
+
reactLiveChecked = true;
|
|
184
|
+
logger.log('[canvas-generate] react-live installed successfully');
|
|
165
185
|
}
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
catch (err) {
|
|
187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
188
|
+
logger.error('[canvas-generate] Could not auto-install react-live', { error: msg });
|
|
189
|
+
logger.log('[canvas-generate] Run manually: npm install react-live');
|
|
168
190
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logger.error('[canvas-generate] Could not auto-install react-live', { error: msg });
|
|
175
|
-
logger.log('[canvas-generate] Run manually: npm install react-live');
|
|
176
|
-
}
|
|
191
|
+
finally {
|
|
192
|
+
reactLiveInstalling = null;
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
return reactLiveInstalling;
|
|
177
196
|
}
|
|
178
197
|
// ── Write story to disk (once) ────────────────────────────────
|
|
179
198
|
/**
|
|
@@ -198,7 +217,7 @@ export function ensureVoiceCanvasStory(storiesDir) {
|
|
|
198
217
|
* This prevents the "No-Inline evaluations must call render" error when voice input
|
|
199
218
|
* is ambiguous or short and the LLM skips the final line.
|
|
200
219
|
*/
|
|
201
|
-
function ensureRenderCall(code) {
|
|
220
|
+
export function ensureRenderCall(code) {
|
|
202
221
|
if (/\brender\s*\(/.test(code))
|
|
203
222
|
return code;
|
|
204
223
|
// Find the last PascalCase component/const defined in the code
|
|
@@ -210,7 +229,7 @@ function ensureRenderCall(code) {
|
|
|
210
229
|
* Extract the canvas component code from the LLM response.
|
|
211
230
|
* Handles markdown code fences and stray text.
|
|
212
231
|
*/
|
|
213
|
-
function extractCanvasCode(response) {
|
|
232
|
+
export function extractCanvasCode(response) {
|
|
214
233
|
let code;
|
|
215
234
|
// Prefer explicit code fence
|
|
216
235
|
const fenceMatch = response.match(/```(?:jsx|tsx|js|ts)?\n([\s\S]+?)\n```/);
|
|
@@ -224,13 +243,112 @@ function extractCanvasCode(response) {
|
|
|
224
243
|
}
|
|
225
244
|
return ensureRenderCall(code);
|
|
226
245
|
}
|
|
246
|
+
// ── Security sanitization ─────────────────────────────────────
|
|
247
|
+
/**
|
|
248
|
+
* Dangerous patterns that must be neutralized in LLM-generated canvas code.
|
|
249
|
+
*
|
|
250
|
+
* Each entry defines a regex (applied with the global flag) and a replacement
|
|
251
|
+
* string. The replacement comments out the dangerous call so the surrounding
|
|
252
|
+
* code still parses — this avoids rejecting an entire response because the LLM
|
|
253
|
+
* happened to mention one of these tokens inside a string literal or comment.
|
|
254
|
+
*
|
|
255
|
+
* Categories covered:
|
|
256
|
+
* - Arbitrary code execution (eval, Function constructor)
|
|
257
|
+
* - Cookie / domain access
|
|
258
|
+
* - Storage APIs (localStorage, sessionStorage)
|
|
259
|
+
* - Network requests (fetch, XMLHttpRequest, WebSocket)
|
|
260
|
+
* - Location manipulation
|
|
261
|
+
* - Script injection
|
|
262
|
+
* - Unsafe React patterns (dangerouslySetInnerHTML)
|
|
263
|
+
* - Prototype pollution (__proto__, constructor.prototype)
|
|
264
|
+
* - Dynamic / CommonJS imports
|
|
265
|
+
*/
|
|
266
|
+
const DANGEROUS_PATTERNS = [
|
|
267
|
+
// Arbitrary code execution
|
|
268
|
+
{ pattern: /\beval\s*\(/g, label: 'eval()', replacement: '/* [sanitized: eval] */void(' },
|
|
269
|
+
{ pattern: /\bnew\s+Function\s*\(/g, label: 'new Function()', replacement: '/* [sanitized: new Function] */void(' },
|
|
270
|
+
{ pattern: /\bFunction\s*\(/g, label: 'Function()', replacement: '/* [sanitized: Function] */void(' },
|
|
271
|
+
// Cookie / domain access
|
|
272
|
+
{ pattern: /\bdocument\.cookie\b/g, label: 'document.cookie', replacement: '/* [sanitized: document.cookie] */undefined' },
|
|
273
|
+
{ pattern: /\bdocument\.domain\b/g, label: 'document.domain', replacement: '/* [sanitized: document.domain] */undefined' },
|
|
274
|
+
// Storage APIs
|
|
275
|
+
{ pattern: /\blocalStorage\b/g, label: 'localStorage', replacement: '/* [sanitized: localStorage] */undefined' },
|
|
276
|
+
{ pattern: /\bsessionStorage\b/g, label: 'sessionStorage', replacement: '/* [sanitized: sessionStorage] */undefined' },
|
|
277
|
+
// Network requests
|
|
278
|
+
{ pattern: /\bfetch\s*\(/g, label: 'fetch()', replacement: '/* [sanitized: fetch] */void(' },
|
|
279
|
+
{ pattern: /\bnew\s+XMLHttpRequest\b/g, label: 'XMLHttpRequest', replacement: '/* [sanitized: XMLHttpRequest] */undefined' },
|
|
280
|
+
{ pattern: /\bXMLHttpRequest\b/g, label: 'XMLHttpRequest', replacement: '/* [sanitized: XMLHttpRequest] */undefined' },
|
|
281
|
+
{ pattern: /\bnew\s+WebSocket\s*\(/g, label: 'WebSocket', replacement: '/* [sanitized: WebSocket] */void(' },
|
|
282
|
+
{ pattern: /\bWebSocket\s*\(/g, label: 'WebSocket', replacement: '/* [sanitized: WebSocket] */void(' },
|
|
283
|
+
// Location manipulation
|
|
284
|
+
{ pattern: /\bwindow\.location\b/g, label: 'window.location', replacement: '/* [sanitized: window.location] */undefined' },
|
|
285
|
+
// Script injection
|
|
286
|
+
{ pattern: /<script\b/gi, label: '<script>', replacement: '/* [sanitized: script tag] */undefined' },
|
|
287
|
+
// Unsafe React patterns
|
|
288
|
+
{ pattern: /\bdangerouslySetInnerHTML\b/g, label: 'dangerouslySetInnerHTML', replacement: '/* [sanitized: dangerouslySetInnerHTML] */undefined' },
|
|
289
|
+
// Prototype pollution
|
|
290
|
+
{ pattern: /__proto__/g, label: '__proto__', replacement: '/* [sanitized: __proto__] */undefined' },
|
|
291
|
+
{ pattern: /\bconstructor\.prototype\b/g, label: 'constructor.prototype', replacement: '/* [sanitized: constructor.prototype] */undefined' },
|
|
292
|
+
// Dynamic imports
|
|
293
|
+
{ pattern: /\bimport\s*\(/g, label: 'dynamic import()', replacement: '/* [sanitized: dynamic import] */void(' },
|
|
294
|
+
// CommonJS require
|
|
295
|
+
{ pattern: /\brequire\s*\(/g, label: 'require()', replacement: '/* [sanitized: require] */void(' },
|
|
296
|
+
];
|
|
297
|
+
/**
|
|
298
|
+
* Scan LLM-generated canvas code for dangerous patterns and neutralize them.
|
|
299
|
+
*
|
|
300
|
+
* Instead of rejecting the entire response, each dangerous token is replaced
|
|
301
|
+
* with a safe alternative (typically a comment + `undefined` or `void(`) so
|
|
302
|
+
* the rest of the generated JSX remains functional.
|
|
303
|
+
*
|
|
304
|
+
* Returns the sanitized code string. Logs a warning for every pattern found.
|
|
305
|
+
*/
|
|
306
|
+
export function sanitizeCanvasCode(code) {
|
|
307
|
+
let sanitized = code;
|
|
308
|
+
const found = [];
|
|
309
|
+
for (const { pattern, label, replacement } of DANGEROUS_PATTERNS) {
|
|
310
|
+
// Reset lastIndex in case the regex was used before (global flag)
|
|
311
|
+
pattern.lastIndex = 0;
|
|
312
|
+
if (pattern.test(sanitized)) {
|
|
313
|
+
found.push(label);
|
|
314
|
+
// Reset again before replace — .test() advances lastIndex
|
|
315
|
+
pattern.lastIndex = 0;
|
|
316
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (found.length > 0) {
|
|
320
|
+
logger.warn(`[canvas-generate] Sanitized ${found.length} dangerous pattern(s) from LLM output: ${found.join(', ')}`);
|
|
321
|
+
}
|
|
322
|
+
return sanitized;
|
|
323
|
+
}
|
|
227
324
|
// ── Handler ───────────────────────────────────────────────────
|
|
228
325
|
export async function canvasGenerateHandler(req, res) {
|
|
229
326
|
try {
|
|
230
|
-
|
|
327
|
+
let { prompt, canvasCode, provider, model, conversationHistory = [], } = req.body;
|
|
231
328
|
if (!prompt || typeof prompt !== 'string') {
|
|
232
329
|
return res.status(400).json({ error: 'prompt is required' });
|
|
233
330
|
}
|
|
331
|
+
// ── Request body size limits (truncate, don't reject) ──────
|
|
332
|
+
const MAX_PROMPT = 5000;
|
|
333
|
+
const MAX_CANVAS_CODE = 50000;
|
|
334
|
+
const MAX_HISTORY_ENTRIES = 50;
|
|
335
|
+
const MAX_HISTORY_CONTENT = 10000;
|
|
336
|
+
if (prompt.length > MAX_PROMPT) {
|
|
337
|
+
prompt = prompt.slice(0, MAX_PROMPT);
|
|
338
|
+
}
|
|
339
|
+
if (canvasCode && typeof canvasCode === 'string' && canvasCode.length > MAX_CANVAS_CODE) {
|
|
340
|
+
canvasCode = canvasCode.slice(0, MAX_CANVAS_CODE);
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(conversationHistory)) {
|
|
343
|
+
if (conversationHistory.length > MAX_HISTORY_ENTRIES) {
|
|
344
|
+
conversationHistory = conversationHistory.slice(-MAX_HISTORY_ENTRIES);
|
|
345
|
+
}
|
|
346
|
+
for (const entry of conversationHistory) {
|
|
347
|
+
if (entry && typeof entry.content === 'string' && entry.content.length > MAX_HISTORY_CONTENT) {
|
|
348
|
+
entry.content = entry.content.slice(0, MAX_HISTORY_CONTENT);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
234
352
|
// Load config + discover components — same quality context as standard generation
|
|
235
353
|
const config = loadUserConfig();
|
|
236
354
|
// Voice Canvas requires React — it uses react-live to render JSX in the browser.
|
|
@@ -239,8 +357,16 @@ export async function canvasGenerateHandler(req, res) {
|
|
|
239
357
|
error: `Voice Canvas is only available for React-based Storybook projects. Current framework: ${config.componentFramework}`,
|
|
240
358
|
});
|
|
241
359
|
}
|
|
242
|
-
|
|
243
|
-
const
|
|
360
|
+
let components;
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
if (_componentCache && now - _componentCache.timestamp < COMPONENT_CACHE_TTL) {
|
|
363
|
+
components = _componentCache.components;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const discovery = new EnhancedComponentDiscovery(config);
|
|
367
|
+
components = await discovery.discoverAll();
|
|
368
|
+
_componentCache = { components, timestamp: now };
|
|
369
|
+
}
|
|
244
370
|
// Build the system prompt using the standard prompt pipeline
|
|
245
371
|
const baseSystemPrompt = await buildClaudePrompt(prompt, config, components);
|
|
246
372
|
const systemPrompt = baseSystemPrompt + '\n' + CANVAS_MODE_SUFFIX;
|
|
@@ -264,10 +390,14 @@ export async function canvasGenerateHandler(req, res) {
|
|
|
264
390
|
maxTokens: 4096,
|
|
265
391
|
temperature: 0.3,
|
|
266
392
|
});
|
|
267
|
-
// Extract the canvas code from the LLM response
|
|
268
|
-
|
|
393
|
+
// Extract the canvas code from the LLM response and sanitize it.
|
|
394
|
+
// sanitizeCanvasCode neutralizes dangerous patterns (eval, fetch, script
|
|
395
|
+
// injection, prototype pollution, etc.) before the code reaches the
|
|
396
|
+
// client where react-live would execute it as arbitrary JS.
|
|
397
|
+
const rawCode = extractCanvasCode(response);
|
|
398
|
+
const result = sanitizeCanvasCode(rawCode);
|
|
269
399
|
// Ensure react-live is installed and story template exists (no-ops after first run)
|
|
270
|
-
ensureReactLive();
|
|
400
|
+
await ensureReactLive();
|
|
271
401
|
const storiesDir = config.generatedStoriesPath || './src/stories/generated/';
|
|
272
402
|
ensureVoiceCanvasStory(storiesDir);
|
|
273
403
|
logger.log(`[canvas-generate] Generated ${result.split('\n').length} lines for: "${prompt.slice(0, 60)}"`);
|
|
@@ -9,5 +9,12 @@
|
|
|
9
9
|
* Returns: { fileName, filePath, code }
|
|
10
10
|
*/
|
|
11
11
|
import { Request, Response } from 'express';
|
|
12
|
+
/**
|
|
13
|
+
* Convert a react-live canvas component (JSX string) to a proper .stories.tsx file.
|
|
14
|
+
* The input is in the format: `const Canvas = () => { ... }; render(<Canvas />);`
|
|
15
|
+
*/
|
|
16
|
+
export declare function jsxCodeToStory(jsxCode: string, title: string, importPath: string): string;
|
|
17
|
+
/** Derive a readable title from the last voice/text prompt. */
|
|
18
|
+
export declare function titleFromPrompt(prompt: string): string;
|
|
12
19
|
export declare function canvasSaveHandler(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
13
20
|
//# sourceMappingURL=canvasSave.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvasSave.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasSave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"canvasSave.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasSave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA0I5C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAwCzF;AAID,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CAiHlE"}
|
|
@@ -125,7 +125,7 @@ ${jsx}
|
|
|
125
125
|
* Convert a react-live canvas component (JSX string) to a proper .stories.tsx file.
|
|
126
126
|
* The input is in the format: `const Canvas = () => { ... }; render(<Canvas />);`
|
|
127
127
|
*/
|
|
128
|
-
function jsxCodeToStory(jsxCode, title, importPath) {
|
|
128
|
+
export function jsxCodeToStory(jsxCode, title, importPath) {
|
|
129
129
|
// Remove the render(<Canvas />) call at the end
|
|
130
130
|
const cleanCode = jsxCode.replace(/\nrender\s*\(<Canvas\s*\/>\);?\s*$/, '').trim();
|
|
131
131
|
// Extract component names used in JSX (uppercase identifiers after '<')
|
|
@@ -166,7 +166,7 @@ function jsxCodeToStory(jsxCode, title, importPath) {
|
|
|
166
166
|
}
|
|
167
167
|
// ── Express handler ─────────────────────────────────────────
|
|
168
168
|
/** Derive a readable title from the last voice/text prompt. */
|
|
169
|
-
function titleFromPrompt(prompt) {
|
|
169
|
+
export function titleFromPrompt(prompt) {
|
|
170
170
|
// Strip filler words, take first ~6 meaningful words, title-case
|
|
171
171
|
const stop = new Set(['a', 'an', 'the', 'with', 'and', 'for', 'of', 'to', 'in', 'on', 'at', 'by']);
|
|
172
172
|
const words = prompt
|
|
@@ -183,15 +183,29 @@ function titleFromPrompt(prompt) {
|
|
|
183
183
|
}
|
|
184
184
|
export async function canvasSaveHandler(req, res) {
|
|
185
185
|
try {
|
|
186
|
-
const { tree, jsxCode, title: rawTitle, lastPrompt } = req.body;
|
|
186
|
+
const { tree, jsxCode, title: rawTitle, lastPrompt: rawLastPrompt } = req.body;
|
|
187
|
+
// ── Request body size limits ───────────────────────────────
|
|
188
|
+
const MAX_JSX_CODE = 100000;
|
|
189
|
+
const MAX_TITLE = 200;
|
|
190
|
+
const MAX_LAST_PROMPT = 5000;
|
|
191
|
+
if (jsxCode && typeof jsxCode === 'string' && jsxCode.length > MAX_JSX_CODE) {
|
|
192
|
+
return res.status(400).json({ error: `jsxCode exceeds maximum length of ${MAX_JSX_CODE} characters` });
|
|
193
|
+
}
|
|
194
|
+
// Truncate title and lastPrompt if needed (safe to trim these)
|
|
195
|
+
const safeTitle = (rawTitle && typeof rawTitle === 'string')
|
|
196
|
+
? rawTitle.slice(0, MAX_TITLE)
|
|
197
|
+
: rawTitle;
|
|
198
|
+
const lastPrompt = (rawLastPrompt && typeof rawLastPrompt === 'string' && rawLastPrompt.length > MAX_LAST_PROMPT)
|
|
199
|
+
? rawLastPrompt.slice(0, MAX_LAST_PROMPT)
|
|
200
|
+
: rawLastPrompt;
|
|
187
201
|
// Auto-generate title from last prompt if not provided
|
|
188
|
-
const title = (
|
|
189
|
-
?
|
|
202
|
+
const title = (safeTitle && typeof safeTitle === 'string' && safeTitle.trim())
|
|
203
|
+
? safeTitle.trim()
|
|
190
204
|
: (lastPrompt && typeof lastPrompt === 'string' && lastPrompt.trim())
|
|
191
205
|
? titleFromPrompt(lastPrompt.trim())
|
|
192
|
-
: '
|
|
206
|
+
: `Canvas ${new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}`;
|
|
193
207
|
const config = loadUserConfig();
|
|
194
|
-
const importPath = config.importPath || '
|
|
208
|
+
const importPath = config.importPath || '';
|
|
195
209
|
const storiesDir = config.generatedStoriesPath || './src/stories/generated/';
|
|
196
210
|
let code;
|
|
197
211
|
if (jsxCode && typeof jsxCode === 'string' && jsxCode.trim()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/claude-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/claude-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAmFrD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAY;IACvC,QAAQ,CAAC,eAAe,cAAiB;gBAE7B,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAUtC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAkE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAuGvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgD/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,aAAa;IAiBrB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAMrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
|
|
@@ -33,32 +33,6 @@ const CLAUDE_MODELS = [
|
|
|
33
33
|
inputPricePer1kTokens: 0.003,
|
|
34
34
|
outputPricePer1kTokens: 0.015,
|
|
35
35
|
},
|
|
36
|
-
{
|
|
37
|
-
id: 'claude-opus-4-20250514',
|
|
38
|
-
name: 'Claude Opus 4',
|
|
39
|
-
provider: 'claude',
|
|
40
|
-
contextWindow: 200000,
|
|
41
|
-
maxOutputTokens: 32000,
|
|
42
|
-
supportsVision: true,
|
|
43
|
-
supportsDocuments: true,
|
|
44
|
-
supportsFunctionCalling: true,
|
|
45
|
-
supportsStreaming: true,
|
|
46
|
-
inputPricePer1kTokens: 0.015,
|
|
47
|
-
outputPricePer1kTokens: 0.075,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'claude-sonnet-4-5-20250929',
|
|
51
|
-
name: 'Claude Sonnet 4.5',
|
|
52
|
-
provider: 'claude',
|
|
53
|
-
contextWindow: 200000,
|
|
54
|
-
maxOutputTokens: 16000,
|
|
55
|
-
supportsVision: true,
|
|
56
|
-
supportsDocuments: true,
|
|
57
|
-
supportsFunctionCalling: true,
|
|
58
|
-
supportsStreaming: true,
|
|
59
|
-
inputPricePer1kTokens: 0.003,
|
|
60
|
-
outputPricePer1kTokens: 0.015,
|
|
61
|
-
},
|
|
62
36
|
{
|
|
63
37
|
id: 'claude-haiku-4-5-20251001',
|
|
64
38
|
name: 'Claude Haiku 4.5',
|
|
@@ -70,19 +44,6 @@ const CLAUDE_MODELS = [
|
|
|
70
44
|
supportsFunctionCalling: true,
|
|
71
45
|
supportsStreaming: true,
|
|
72
46
|
inputPricePer1kTokens: 0.0008,
|
|
73
|
-
outputPricePer1kTokens: 0.004,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'claude-sonnet-4-20250514',
|
|
77
|
-
name: 'Claude Sonnet 4',
|
|
78
|
-
provider: 'claude',
|
|
79
|
-
contextWindow: 200000,
|
|
80
|
-
maxOutputTokens: 16000,
|
|
81
|
-
supportsVision: true,
|
|
82
|
-
supportsDocuments: true,
|
|
83
|
-
supportsFunctionCalling: true,
|
|
84
|
-
supportsStreaming: true,
|
|
85
|
-
inputPricePer1kTokens: 0.003,
|
|
86
47
|
outputPricePer1kTokens: 0.015,
|
|
87
48
|
},
|
|
88
49
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAuFrD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAY;IACvC,QAAQ,CAAC,eAAe,cAAiB;gBAE7B,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAU5C,OAAO,CAAC,SAAS;IAKX,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA8D1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAgHvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+C/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,eAAe;IAiBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
|
|
@@ -23,8 +23,8 @@ const GEMINI_MODELS = [
|
|
|
23
23
|
outputPricePer1kTokens: 0.012,
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
id: 'gemini-
|
|
27
|
-
name: 'Gemini
|
|
26
|
+
id: 'gemini-3-flash-preview',
|
|
27
|
+
name: 'Gemini 3 Flash Preview',
|
|
28
28
|
provider: 'gemini',
|
|
29
29
|
contextWindow: 1048576,
|
|
30
30
|
maxOutputTokens: 65536,
|
|
@@ -32,9 +32,8 @@ const GEMINI_MODELS = [
|
|
|
32
32
|
supportsDocuments: true,
|
|
33
33
|
supportsFunctionCalling: true,
|
|
34
34
|
supportsStreaming: true,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
outputPricePer1kTokens: 0.01,
|
|
35
|
+
inputPricePer1kTokens: 0.00015,
|
|
36
|
+
outputPricePer1kTokens: 0.0006,
|
|
38
37
|
},
|
|
39
38
|
{
|
|
40
39
|
id: 'gemini-2.5-flash',
|
|
@@ -50,35 +49,9 @@ const GEMINI_MODELS = [
|
|
|
50
49
|
inputPricePer1kTokens: 0.00015,
|
|
51
50
|
outputPricePer1kTokens: 0.0006,
|
|
52
51
|
},
|
|
53
|
-
{
|
|
54
|
-
id: 'gemini-2.5-flash-lite',
|
|
55
|
-
name: 'Gemini 2.5 Flash Lite',
|
|
56
|
-
provider: 'gemini',
|
|
57
|
-
contextWindow: 1048576,
|
|
58
|
-
maxOutputTokens: 65536,
|
|
59
|
-
supportsVision: true,
|
|
60
|
-
supportsDocuments: true,
|
|
61
|
-
supportsFunctionCalling: true,
|
|
62
|
-
supportsStreaming: true,
|
|
63
|
-
inputPricePer1kTokens: 0.00008,
|
|
64
|
-
outputPricePer1kTokens: 0.0003,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'gemini-3-flash-preview',
|
|
68
|
-
name: 'Gemini 3 Flash Preview',
|
|
69
|
-
provider: 'gemini',
|
|
70
|
-
contextWindow: 1048576,
|
|
71
|
-
maxOutputTokens: 65536,
|
|
72
|
-
supportsVision: true,
|
|
73
|
-
supportsDocuments: true,
|
|
74
|
-
supportsFunctionCalling: true,
|
|
75
|
-
supportsStreaming: true,
|
|
76
|
-
inputPricePer1kTokens: 0.00015,
|
|
77
|
-
outputPricePer1kTokens: 0.0006,
|
|
78
|
-
},
|
|
79
52
|
];
|
|
80
|
-
// Default model - Gemini
|
|
81
|
-
const DEFAULT_MODEL = 'gemini-
|
|
53
|
+
// Default model - Gemini 3.1 Pro Preview (flagship, March 2026)
|
|
54
|
+
const DEFAULT_MODEL = 'gemini-3.1-pro-preview';
|
|
82
55
|
// API configuration
|
|
83
56
|
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
84
57
|
export class GeminiProvider extends BaseLLMProvider {
|
|
@@ -124,12 +97,13 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
124
97
|
parts: [{ text: systemPrompt }],
|
|
125
98
|
};
|
|
126
99
|
}
|
|
127
|
-
const url =
|
|
100
|
+
const url = this.getApiUrl(model);
|
|
128
101
|
try {
|
|
129
102
|
const response = await fetch(url, {
|
|
130
103
|
method: 'POST',
|
|
131
104
|
headers: {
|
|
132
105
|
'Content-Type': 'application/json',
|
|
106
|
+
'x-goog-api-key': apiKey,
|
|
133
107
|
},
|
|
134
108
|
body: JSON.stringify(requestBody),
|
|
135
109
|
signal: AbortSignal.timeout(this.config.timeout || 120000),
|
|
@@ -174,12 +148,13 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
174
148
|
parts: [{ text: systemPrompt }],
|
|
175
149
|
};
|
|
176
150
|
}
|
|
177
|
-
const url = `${this.getApiUrl(model, true)}?
|
|
151
|
+
const url = `${this.getApiUrl(model, true)}?alt=sse`;
|
|
178
152
|
try {
|
|
179
153
|
const response = await fetch(url, {
|
|
180
154
|
method: 'POST',
|
|
181
155
|
headers: {
|
|
182
156
|
'Content-Type': 'application/json',
|
|
157
|
+
'x-goog-api-key': apiKey,
|
|
183
158
|
},
|
|
184
159
|
body: JSON.stringify(requestBody),
|
|
185
160
|
signal: AbortSignal.timeout(this.config.timeout || 120000),
|
|
@@ -251,11 +226,12 @@ export class GeminiProvider extends BaseLLMProvider {
|
|
|
251
226
|
async validateApiKey(apiKey) {
|
|
252
227
|
try {
|
|
253
228
|
// Make a minimal API call to validate the key
|
|
254
|
-
const url =
|
|
229
|
+
const url = this.getApiUrl('gemini-2.5-flash');
|
|
255
230
|
const response = await fetch(url, {
|
|
256
231
|
method: 'POST',
|
|
257
232
|
headers: {
|
|
258
233
|
'Content-Type': 'application/json',
|
|
234
|
+
'x-goog-api-key': apiKey,
|
|
259
235
|
},
|
|
260
236
|
body: JSON.stringify({
|
|
261
237
|
contents: [{ parts: [{ text: 'Hi' }] }],
|
|
@@ -143,7 +143,7 @@ export function initializeFromEnv() {
|
|
|
143
143
|
if (claudeKey) {
|
|
144
144
|
registry.configureProvider('claude', {
|
|
145
145
|
apiKey: claudeKey,
|
|
146
|
-
model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-
|
|
146
|
+
model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-6',
|
|
147
147
|
});
|
|
148
148
|
logger.info('Claude provider configured from environment');
|
|
149
149
|
}
|
|
@@ -152,7 +152,7 @@ export function initializeFromEnv() {
|
|
|
152
152
|
if (openaiKey) {
|
|
153
153
|
registry.configureProvider('openai', {
|
|
154
154
|
apiKey: openaiKey,
|
|
155
|
-
model: process.env.OPENAI_MODEL || 'gpt-
|
|
155
|
+
model: process.env.OPENAI_MODEL || 'gpt-5.4',
|
|
156
156
|
organizationId: process.env.OPENAI_ORG_ID,
|
|
157
157
|
});
|
|
158
158
|
logger.info('OpenAI provider configured from environment');
|
|
@@ -162,7 +162,7 @@ export function initializeFromEnv() {
|
|
|
162
162
|
if (geminiKey) {
|
|
163
163
|
registry.configureProvider('gemini', {
|
|
164
164
|
apiKey: geminiKey,
|
|
165
|
-
model: process.env.GEMINI_MODEL || 'gemini-
|
|
165
|
+
model: process.env.GEMINI_MODEL || 'gemini-3.1-pro-preview',
|
|
166
166
|
});
|
|
167
167
|
logger.info('Gemini provider configured from environment');
|
|
168
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/openai-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"openai-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/openai-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAwFrD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAY;IACvC,QAAQ,CAAC,eAAe,cAAiB;gBAE7B,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAUtC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAmE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IA6GvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+C/D,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,eAAe;IAmBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
|