@tpitre/story-ui 2.2.0 → 2.3.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/.env.sample +82 -11
- package/README.md +89 -0
- package/dist/cli/deploy.d.ts +17 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +696 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -2
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +437 -110
- package/dist/mcp-server/index.d.ts +2 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +120 -2
- package/dist/mcp-server/mcp-stdio-server.d.ts +3 -0
- package/dist/mcp-server/mcp-stdio-server.d.ts.map +1 -0
- package/dist/mcp-server/mcp-stdio-server.js +8 -1
- package/dist/mcp-server/routes/claude.d.ts +3 -0
- package/dist/mcp-server/routes/claude.d.ts.map +1 -0
- package/dist/mcp-server/routes/claude.js +60 -23
- package/dist/mcp-server/routes/components.d.ts +4 -0
- package/dist/mcp-server/routes/components.d.ts.map +1 -0
- package/dist/mcp-server/routes/frameworks.d.ts +38 -0
- package/dist/mcp-server/routes/frameworks.d.ts.map +1 -0
- package/dist/mcp-server/routes/frameworks.js +183 -0
- package/dist/mcp-server/routes/generateStory.d.ts +3 -0
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -0
- package/dist/mcp-server/routes/generateStory.js +160 -76
- package/dist/mcp-server/routes/generateStoryStream.d.ts +12 -0
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -0
- package/dist/mcp-server/routes/generateStoryStream.js +947 -0
- package/dist/mcp-server/routes/hybridStories.d.ts +18 -0
- package/dist/mcp-server/routes/hybridStories.d.ts.map +1 -0
- package/dist/mcp-server/routes/mcpRemote.d.ts +14 -0
- package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -0
- package/dist/mcp-server/routes/mcpRemote.js +489 -0
- package/dist/mcp-server/routes/memoryStories.d.ts +26 -0
- package/dist/mcp-server/routes/memoryStories.d.ts.map +1 -0
- package/dist/mcp-server/routes/providers.d.ts +89 -0
- package/dist/mcp-server/routes/providers.d.ts.map +1 -0
- package/dist/mcp-server/routes/providers.js +369 -0
- package/dist/mcp-server/routes/storySync.d.ts +26 -0
- package/dist/mcp-server/routes/storySync.d.ts.map +1 -0
- package/dist/mcp-server/routes/streamTypes.d.ts +110 -0
- package/dist/mcp-server/routes/streamTypes.d.ts.map +1 -0
- package/dist/mcp-server/routes/streamTypes.js +18 -0
- package/dist/mcp-server/sessionManager.d.ts +50 -0
- package/dist/mcp-server/sessionManager.d.ts.map +1 -0
- package/dist/story-generator/componentBlacklist.d.ts +21 -0
- package/dist/story-generator/componentBlacklist.d.ts.map +1 -0
- package/dist/story-generator/componentDiscovery.d.ts +28 -0
- package/dist/story-generator/componentDiscovery.d.ts.map +1 -0
- package/dist/story-generator/componentRegistryGenerator.d.ts +49 -0
- package/dist/story-generator/componentRegistryGenerator.d.ts.map +1 -0
- package/dist/story-generator/componentRegistryGenerator.js +205 -0
- package/dist/story-generator/configLoader.d.ts +33 -0
- package/dist/story-generator/configLoader.d.ts.map +1 -0
- package/dist/story-generator/considerationsLoader.d.ts +32 -0
- package/dist/story-generator/considerationsLoader.d.ts.map +1 -0
- package/dist/story-generator/documentation-sources.d.ts +28 -0
- package/dist/story-generator/documentation-sources.d.ts.map +1 -0
- package/dist/story-generator/documentationLoader.d.ts +64 -0
- package/dist/story-generator/documentationLoader.d.ts.map +1 -0
- package/dist/story-generator/dynamicPackageDiscovery.d.ts +97 -0
- package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -0
- package/dist/story-generator/enhancedComponentDiscovery.d.ts +125 -0
- package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -0
- package/dist/story-generator/enhancedComponentDiscovery.js +111 -11
- package/dist/story-generator/framework-adapters/angular-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/angular-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/angular-adapter.js +427 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts +75 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/base-adapter.js +147 -0
- package/dist/story-generator/framework-adapters/framework-detector.d.ts +55 -0
- package/dist/story-generator/framework-adapters/framework-detector.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/framework-detector.js +323 -0
- package/dist/story-generator/framework-adapters/index.d.ts +97 -0
- package/dist/story-generator/framework-adapters/index.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/index.js +198 -0
- package/dist/story-generator/framework-adapters/react-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/react-adapter.js +316 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.js +372 -0
- package/dist/story-generator/framework-adapters/types.d.ts +182 -0
- package/dist/story-generator/framework-adapters/types.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/types.js +8 -0
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts +36 -0
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/vue-adapter.js +336 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts +54 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.js +387 -0
- package/dist/story-generator/generateStory.d.ts +7 -0
- package/dist/story-generator/generateStory.d.ts.map +1 -0
- package/dist/story-generator/gitignoreManager.d.ts +50 -0
- package/dist/story-generator/gitignoreManager.d.ts.map +1 -0
- package/dist/story-generator/imageProcessor.d.ts +80 -0
- package/dist/story-generator/imageProcessor.d.ts.map +1 -0
- package/dist/story-generator/imageProcessor.js +391 -0
- package/dist/story-generator/inMemoryStoryService.d.ts +89 -0
- package/dist/story-generator/inMemoryStoryService.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/base-provider.d.ts +36 -0
- package/dist/story-generator/llm-providers/base-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/base-provider.js +135 -0
- package/dist/story-generator/llm-providers/claude-provider.d.ts +23 -0
- package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/claude-provider.js +414 -0
- package/dist/story-generator/llm-providers/gemini-provider.d.ts +24 -0
- package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/gemini-provider.js +406 -0
- package/dist/story-generator/llm-providers/index.d.ts +63 -0
- package/dist/story-generator/llm-providers/index.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/index.js +169 -0
- package/dist/story-generator/llm-providers/openai-provider.d.ts +24 -0
- package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/openai-provider.js +458 -0
- package/dist/story-generator/llm-providers/settings-manager.d.ts +75 -0
- package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/settings-manager.js +173 -0
- package/dist/story-generator/llm-providers/story-llm-service.d.ts +79 -0
- package/dist/story-generator/llm-providers/story-llm-service.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/story-llm-service.js +240 -0
- package/dist/story-generator/llm-providers/types.d.ts +153 -0
- package/dist/story-generator/llm-providers/types.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/types.js +8 -0
- package/dist/story-generator/logger.d.ts +14 -0
- package/dist/story-generator/logger.d.ts.map +1 -0
- package/dist/story-generator/logger.js +96 -29
- package/dist/story-generator/postProcessStory.d.ts +6 -0
- package/dist/story-generator/postProcessStory.d.ts.map +1 -0
- package/dist/story-generator/productionGitignoreManager.d.ts +91 -0
- package/dist/story-generator/productionGitignoreManager.d.ts.map +1 -0
- package/dist/story-generator/promptGenerator.d.ts +48 -0
- package/dist/story-generator/promptGenerator.d.ts.map +1 -0
- package/dist/story-generator/promptGenerator.js +186 -1
- package/dist/story-generator/storyHistory.d.ts +44 -0
- package/dist/story-generator/storyHistory.d.ts.map +1 -0
- package/dist/story-generator/storySync.d.ts +68 -0
- package/dist/story-generator/storySync.d.ts.map +1 -0
- package/dist/story-generator/storyTracker.d.ts +48 -0
- package/dist/story-generator/storyTracker.d.ts.map +1 -0
- package/dist/story-generator/storyValidator.d.ts +6 -0
- package/dist/story-generator/storyValidator.d.ts.map +1 -0
- package/dist/story-generator/universalDesignSystemAdapter.d.ts +68 -0
- package/dist/story-generator/universalDesignSystemAdapter.d.ts.map +1 -0
- package/dist/story-generator/universalDesignSystemAdapter.js +138 -1
- package/dist/story-generator/urlRedirectService.d.ts +21 -0
- package/dist/story-generator/urlRedirectService.d.ts.map +1 -0
- package/dist/story-generator/validateStory.d.ts +19 -0
- package/dist/story-generator/validateStory.d.ts.map +1 -0
- package/dist/story-generator/validateStory.js +6 -2
- package/dist/story-generator/visionPrompts.d.ts +88 -0
- package/dist/story-generator/visionPrompts.d.ts.map +1 -0
- package/dist/story-generator/visionPrompts.js +462 -0
- package/dist/story-ui.config.d.ts +78 -0
- package/dist/story-ui.config.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts +4 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.js +1874 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +18 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.js +37 -0
- package/dist/templates/StoryUI/index.d.ts +3 -0
- package/dist/templates/StoryUI/index.d.ts.map +1 -0
- package/dist/templates/StoryUI/index.js +2 -0
- package/package.json +17 -3
- package/templates/StoryUI/StoryUIPanel.tsx +1960 -384
- package/templates/StoryUI/index.tsx +1 -1
- package/templates/StoryUI/manager.tsx +264 -0
- package/templates/production-app/.env.example +11 -0
- package/templates/production-app/index.html +66 -0
- package/templates/production-app/package.json +30 -0
- package/templates/production-app/public/favicon.svg +5 -0
- package/templates/production-app/src/App.tsx +1560 -0
- package/templates/production-app/src/LivePreviewRenderer.tsx +420 -0
- package/templates/production-app/src/componentRegistry.ts +315 -0
- package/templates/production-app/src/considerations.ts +16 -0
- package/templates/production-app/src/index.css +284 -0
- package/templates/production-app/src/main.tsx +25 -0
- package/templates/production-app/tsconfig.json +32 -0
- package/templates/production-app/tsconfig.node.json +11 -0
- package/templates/production-app/vite.config.ts +83 -0
- package/templates/react-import-rule.json +2 -2
- package/dist/index.js +0 -12
- package/dist/story-ui.config.loader.js +0 -205
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Processing Module for StoryUI Vision Feature
|
|
3
|
+
*
|
|
4
|
+
* This module handles all image processing operations including:
|
|
5
|
+
* - Validation of image inputs (size, format, data integrity)
|
|
6
|
+
* - Conversion between different image formats (URL, base64, buffer)
|
|
7
|
+
* - Media type detection from magic bytes and file extensions
|
|
8
|
+
* - Preparation of images for LLM provider consumption
|
|
9
|
+
*
|
|
10
|
+
* Supports: PNG, JPEG, GIF, WebP formats
|
|
11
|
+
* Max size: 20MB for base64 encoded images
|
|
12
|
+
*/
|
|
13
|
+
import { logger } from './logger.js';
|
|
14
|
+
// Maximum image size in bytes (20MB)
|
|
15
|
+
const MAX_IMAGE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
16
|
+
// Size warning threshold (5MB)
|
|
17
|
+
const SIZE_WARNING_THRESHOLD = 5 * 1024 * 1024;
|
|
18
|
+
// Supported MIME types
|
|
19
|
+
const SUPPORTED_MEDIA_TYPES = [
|
|
20
|
+
'image/png',
|
|
21
|
+
'image/jpeg',
|
|
22
|
+
'image/jpg',
|
|
23
|
+
'image/gif',
|
|
24
|
+
'image/webp',
|
|
25
|
+
];
|
|
26
|
+
// Magic bytes for image format detection
|
|
27
|
+
const MAGIC_BYTES = {
|
|
28
|
+
png: { bytes: [0x89, 0x50, 0x4e, 0x47], type: 'image/png' },
|
|
29
|
+
jpeg: { bytes: [0xff, 0xd8, 0xff], type: 'image/jpeg' },
|
|
30
|
+
gif87a: { bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif' },
|
|
31
|
+
gif89a: { bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif' },
|
|
32
|
+
webp: { bytes: [0x52, 0x49, 0x46, 0x46], type: 'image/webp' }, // RIFF header (WebP also has WEBP at offset 8)
|
|
33
|
+
};
|
|
34
|
+
// File extension to MIME type mapping
|
|
35
|
+
const EXTENSION_TO_MIME = {
|
|
36
|
+
png: 'image/png',
|
|
37
|
+
jpg: 'image/jpeg',
|
|
38
|
+
jpeg: 'image/jpeg',
|
|
39
|
+
gif: 'image/gif',
|
|
40
|
+
webp: 'image/webp',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Get media type from buffer by checking magic bytes
|
|
44
|
+
*
|
|
45
|
+
* @param buffer - Image buffer to analyze
|
|
46
|
+
* @returns Detected MIME type or undefined if not recognized
|
|
47
|
+
*/
|
|
48
|
+
export function getMediaTypeFromBuffer(buffer) {
|
|
49
|
+
// Check each magic byte signature
|
|
50
|
+
for (const [format, { bytes, type }] of Object.entries(MAGIC_BYTES)) {
|
|
51
|
+
if (buffer.length < bytes.length)
|
|
52
|
+
continue;
|
|
53
|
+
// Compare the first N bytes
|
|
54
|
+
const matches = bytes.every((byte, index) => buffer[index] === byte);
|
|
55
|
+
if (matches) {
|
|
56
|
+
// Special case for WebP: verify WEBP signature at offset 8
|
|
57
|
+
if (format === 'webp') {
|
|
58
|
+
if (buffer.length < 12)
|
|
59
|
+
continue;
|
|
60
|
+
const webpSignature = buffer.slice(8, 12).toString('ascii');
|
|
61
|
+
if (webpSignature === 'WEBP') {
|
|
62
|
+
logger.debug(`Detected image format from magic bytes: ${type}`);
|
|
63
|
+
return type;
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
logger.debug(`Detected image format from magic bytes: ${type}`);
|
|
68
|
+
return type;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
logger.warn('Unable to detect image format from magic bytes');
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get media type from URL file extension
|
|
76
|
+
*
|
|
77
|
+
* @param url - URL to extract extension from
|
|
78
|
+
* @returns MIME type or undefined if not recognized
|
|
79
|
+
*/
|
|
80
|
+
function getMediaTypeFromUrl(url) {
|
|
81
|
+
try {
|
|
82
|
+
const urlObj = new URL(url);
|
|
83
|
+
const pathname = urlObj.pathname.toLowerCase();
|
|
84
|
+
const extension = pathname.split('.').pop();
|
|
85
|
+
if (extension && EXTENSION_TO_MIME[extension]) {
|
|
86
|
+
const mediaType = EXTENSION_TO_MIME[extension];
|
|
87
|
+
logger.debug(`Detected media type from URL extension: ${mediaType}`);
|
|
88
|
+
return mediaType;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
logger.warn(`Failed to parse URL for media type detection: ${url}`);
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Normalize base64 data by removing data URI prefix if present
|
|
98
|
+
*
|
|
99
|
+
* @param data - Base64 string (with or without data URI prefix)
|
|
100
|
+
* @returns Pure base64 string without prefix
|
|
101
|
+
*/
|
|
102
|
+
function normalizeBase64(data) {
|
|
103
|
+
// Remove data URI prefix if present (e.g., "data:image/png;base64,")
|
|
104
|
+
const dataUriPattern = /^data:([^;]+);base64,/;
|
|
105
|
+
const match = data.match(dataUriPattern);
|
|
106
|
+
if (match) {
|
|
107
|
+
logger.debug(`Removing data URI prefix: ${match[1]}`);
|
|
108
|
+
return data.replace(dataUriPattern, '');
|
|
109
|
+
}
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Extract media type from data URI prefix if present
|
|
114
|
+
*
|
|
115
|
+
* @param data - Base64 string that may contain data URI prefix
|
|
116
|
+
* @returns Media type from prefix or undefined
|
|
117
|
+
*/
|
|
118
|
+
function extractMediaTypeFromDataUri(data) {
|
|
119
|
+
const dataUriPattern = /^data:([^;]+);base64,/;
|
|
120
|
+
const match = data.match(dataUriPattern);
|
|
121
|
+
if (match) {
|
|
122
|
+
const mediaType = match[1].toLowerCase();
|
|
123
|
+
if (SUPPORTED_MEDIA_TYPES.includes(mediaType)) {
|
|
124
|
+
logger.debug(`Extracted media type from data URI: ${mediaType}`);
|
|
125
|
+
return mediaType;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Validate an image input
|
|
132
|
+
*
|
|
133
|
+
* Checks:
|
|
134
|
+
* - Has either data or url
|
|
135
|
+
* - File size is within limits
|
|
136
|
+
* - Media type is supported
|
|
137
|
+
* - Base64 data is valid
|
|
138
|
+
*
|
|
139
|
+
* @param image - Image input to validate
|
|
140
|
+
* @returns Validation result with error/warning messages
|
|
141
|
+
*/
|
|
142
|
+
export function validateImage(image) {
|
|
143
|
+
const name = image.name || 'unknown';
|
|
144
|
+
// Check that either data or url is provided
|
|
145
|
+
if (!image.data && !image.url) {
|
|
146
|
+
return {
|
|
147
|
+
valid: false,
|
|
148
|
+
error: `Image "${name}" must have either 'data' or 'url' property`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Both data and url provided - prefer data
|
|
152
|
+
if (image.data && image.url) {
|
|
153
|
+
logger.warn(`Image "${name}" has both data and url, will use data`);
|
|
154
|
+
}
|
|
155
|
+
// Validate media type if provided
|
|
156
|
+
if (image.mediaType) {
|
|
157
|
+
const mediaType = image.mediaType.toLowerCase();
|
|
158
|
+
if (!SUPPORTED_MEDIA_TYPES.includes(mediaType)) {
|
|
159
|
+
return {
|
|
160
|
+
valid: false,
|
|
161
|
+
error: `Unsupported media type "${mediaType}". Supported types: ${SUPPORTED_MEDIA_TYPES.join(', ')}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Validate base64 data if provided
|
|
166
|
+
if (image.data) {
|
|
167
|
+
try {
|
|
168
|
+
const normalizedData = normalizeBase64(image.data);
|
|
169
|
+
// Check if valid base64
|
|
170
|
+
const base64Pattern = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
171
|
+
if (!base64Pattern.test(normalizedData)) {
|
|
172
|
+
return {
|
|
173
|
+
valid: false,
|
|
174
|
+
error: `Invalid base64 data for image "${name}"`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Calculate decoded size
|
|
178
|
+
const padding = (normalizedData.match(/=/g) || []).length;
|
|
179
|
+
const sizeBytes = (normalizedData.length * 3 / 4) - padding;
|
|
180
|
+
logger.debug(`Image "${name}" size: ${(sizeBytes / 1024 / 1024).toFixed(2)}MB`);
|
|
181
|
+
// Check size limits
|
|
182
|
+
if (sizeBytes > MAX_IMAGE_SIZE_BYTES) {
|
|
183
|
+
return {
|
|
184
|
+
valid: false,
|
|
185
|
+
error: `Image "${name}" exceeds maximum size of ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB (size: ${(sizeBytes / 1024 / 1024).toFixed(2)}MB)`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// Warn about large images
|
|
189
|
+
if (sizeBytes > SIZE_WARNING_THRESHOLD) {
|
|
190
|
+
return {
|
|
191
|
+
valid: true,
|
|
192
|
+
warning: `Image "${name}" is large (${(sizeBytes / 1024 / 1024).toFixed(2)}MB). Consider optimizing for better performance.`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
valid: false,
|
|
199
|
+
error: `Failed to validate base64 data for image "${name}": ${error instanceof Error ? error.message : String(error)}`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// If URL is provided and no data, we can't validate size until fetch
|
|
204
|
+
if (image.url && !image.data) {
|
|
205
|
+
try {
|
|
206
|
+
// Basic URL validation
|
|
207
|
+
new URL(image.url);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
valid: false,
|
|
212
|
+
error: `Invalid URL for image "${name}": ${image.url}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { valid: true };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Fetch an image from a URL and convert to base64
|
|
220
|
+
*
|
|
221
|
+
* @param url - URL to fetch image from
|
|
222
|
+
* @returns Object containing base64 data and detected media type
|
|
223
|
+
* @throws Error if fetch fails or image is invalid
|
|
224
|
+
*/
|
|
225
|
+
export async function fetchImageAsBase64(url) {
|
|
226
|
+
logger.info(`Fetching image from URL: ${url}`);
|
|
227
|
+
try {
|
|
228
|
+
const response = await fetch(url);
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
231
|
+
}
|
|
232
|
+
// Get content type from response headers
|
|
233
|
+
const contentType = response.headers.get('content-type')?.toLowerCase();
|
|
234
|
+
logger.debug(`Response content-type: ${contentType}`);
|
|
235
|
+
// Get the image buffer
|
|
236
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
237
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
238
|
+
// Check size
|
|
239
|
+
if (buffer.length > MAX_IMAGE_SIZE_BYTES) {
|
|
240
|
+
throw new Error(`Image exceeds maximum size of ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB (size: ${(buffer.length / 1024 / 1024).toFixed(2)}MB)`);
|
|
241
|
+
}
|
|
242
|
+
// Detect media type (priority: magic bytes > content-type header > URL extension)
|
|
243
|
+
let mediaType = getMediaTypeFromBuffer(buffer);
|
|
244
|
+
if (!mediaType && contentType && SUPPORTED_MEDIA_TYPES.includes(contentType)) {
|
|
245
|
+
mediaType = contentType;
|
|
246
|
+
logger.debug(`Using media type from content-type header: ${mediaType}`);
|
|
247
|
+
}
|
|
248
|
+
if (!mediaType) {
|
|
249
|
+
mediaType = getMediaTypeFromUrl(url);
|
|
250
|
+
}
|
|
251
|
+
if (!mediaType) {
|
|
252
|
+
throw new Error('Unable to determine image media type from URL, headers, or content');
|
|
253
|
+
}
|
|
254
|
+
// Convert to base64
|
|
255
|
+
const base64Data = buffer.toString('base64');
|
|
256
|
+
logger.info(`Successfully fetched and encoded image: ${(buffer.length / 1024).toFixed(2)}KB, type: ${mediaType}`);
|
|
257
|
+
return {
|
|
258
|
+
data: base64Data,
|
|
259
|
+
mediaType,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
264
|
+
logger.error(`Failed to fetch image from URL ${url}: ${errorMessage}`);
|
|
265
|
+
throw new Error(`Failed to fetch image: ${errorMessage}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Process a single image input into the internal ProcessedImage format
|
|
270
|
+
*
|
|
271
|
+
* @param image - Image input to process
|
|
272
|
+
* @returns Processed image with validated data and media type
|
|
273
|
+
* @throws Error if processing fails
|
|
274
|
+
*/
|
|
275
|
+
async function processSingleImage(image) {
|
|
276
|
+
const name = image.name || 'unknown';
|
|
277
|
+
logger.debug(`Processing image: ${name}`);
|
|
278
|
+
// Validate image first
|
|
279
|
+
const validation = validateImage(image);
|
|
280
|
+
if (!validation.valid) {
|
|
281
|
+
throw new Error(validation.error || 'Image validation failed');
|
|
282
|
+
}
|
|
283
|
+
// Log warnings
|
|
284
|
+
if (validation.warning) {
|
|
285
|
+
logger.warn(validation.warning);
|
|
286
|
+
}
|
|
287
|
+
let data;
|
|
288
|
+
let mediaType;
|
|
289
|
+
// Process based on input type
|
|
290
|
+
if (image.data) {
|
|
291
|
+
// Extract media type from data URI if present
|
|
292
|
+
mediaType = extractMediaTypeFromDataUri(image.data);
|
|
293
|
+
// Normalize base64 data (remove data URI prefix)
|
|
294
|
+
data = normalizeBase64(image.data);
|
|
295
|
+
// If no media type from data URI, try to detect from base64 data
|
|
296
|
+
if (!mediaType) {
|
|
297
|
+
const buffer = Buffer.from(data, 'base64');
|
|
298
|
+
mediaType = getMediaTypeFromBuffer(buffer);
|
|
299
|
+
}
|
|
300
|
+
// Use provided media type as fallback
|
|
301
|
+
if (!mediaType && image.mediaType) {
|
|
302
|
+
const providedType = image.mediaType.toLowerCase();
|
|
303
|
+
if (SUPPORTED_MEDIA_TYPES.includes(providedType)) {
|
|
304
|
+
mediaType = providedType;
|
|
305
|
+
logger.debug(`Using provided media type: ${mediaType}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (!mediaType) {
|
|
309
|
+
throw new Error(`Unable to determine media type for image "${name}". Please provide mediaType property.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else if (image.url) {
|
|
313
|
+
// Fetch image from URL
|
|
314
|
+
const fetched = await fetchImageAsBase64(image.url);
|
|
315
|
+
data = fetched.data;
|
|
316
|
+
mediaType = fetched.mediaType;
|
|
317
|
+
// Override with provided media type if specified
|
|
318
|
+
if (image.mediaType) {
|
|
319
|
+
const providedType = image.mediaType.toLowerCase();
|
|
320
|
+
if (SUPPORTED_MEDIA_TYPES.includes(providedType)) {
|
|
321
|
+
logger.debug(`Overriding detected media type ${mediaType} with provided type ${providedType}`);
|
|
322
|
+
mediaType = providedType;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
throw new Error(`Image "${name}" must have either 'data' or 'url' property`);
|
|
328
|
+
}
|
|
329
|
+
logger.info(`Successfully processed image "${name}": ${mediaType}`);
|
|
330
|
+
return {
|
|
331
|
+
data,
|
|
332
|
+
mediaType,
|
|
333
|
+
name: image.name,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Convert processed images to ImageContent format for LLM providers
|
|
338
|
+
*
|
|
339
|
+
* @param processed - Array of processed images
|
|
340
|
+
* @returns Array of ImageContent objects ready for LLM consumption
|
|
341
|
+
*/
|
|
342
|
+
function convertToImageContent(processed) {
|
|
343
|
+
return processed.map(img => ({
|
|
344
|
+
type: 'image',
|
|
345
|
+
source: {
|
|
346
|
+
type: 'base64',
|
|
347
|
+
mediaType: img.mediaType,
|
|
348
|
+
data: img.data,
|
|
349
|
+
},
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Process multiple image inputs and convert to ImageContent format
|
|
354
|
+
*
|
|
355
|
+
* This is the main entry point for image processing. It:
|
|
356
|
+
* 1. Validates each image input
|
|
357
|
+
* 2. Fetches URL images and converts to base64
|
|
358
|
+
* 3. Detects media types from magic bytes, headers, or extensions
|
|
359
|
+
* 4. Returns array of ImageContent objects ready for LLM providers
|
|
360
|
+
*
|
|
361
|
+
* @param images - Array of image inputs to process
|
|
362
|
+
* @returns Promise resolving to array of ImageContent objects
|
|
363
|
+
* @throws Error if any image fails to process
|
|
364
|
+
*/
|
|
365
|
+
export async function processImageInputs(images) {
|
|
366
|
+
if (!images || images.length === 0) {
|
|
367
|
+
logger.debug('No images to process');
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
logger.info(`Processing ${images.length} image(s)`);
|
|
371
|
+
try {
|
|
372
|
+
// Process all images (can be done in parallel for better performance)
|
|
373
|
+
const processed = await Promise.all(images.map((img, index) => {
|
|
374
|
+
// Add index to name for better error messages if name not provided
|
|
375
|
+
const imageWithName = {
|
|
376
|
+
...img,
|
|
377
|
+
name: img.name || `image-${index + 1}`,
|
|
378
|
+
};
|
|
379
|
+
return processSingleImage(imageWithName);
|
|
380
|
+
}));
|
|
381
|
+
// Convert to ImageContent format
|
|
382
|
+
const imageContents = convertToImageContent(processed);
|
|
383
|
+
logger.info(`Successfully processed ${imageContents.length} image(s)`);
|
|
384
|
+
return imageContents;
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
388
|
+
logger.error(`Failed to process images: ${errorMessage}`);
|
|
389
|
+
throw new Error(`Image processing failed: ${errorMessage}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { StoryUIConfig } from '../story-ui.config.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory story service for production environments
|
|
4
|
+
* Stores generated stories in memory and serves them via API
|
|
5
|
+
*/
|
|
6
|
+
export declare class InMemoryStoryService {
|
|
7
|
+
private stories;
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config: StoryUIConfig);
|
|
10
|
+
/**
|
|
11
|
+
* Stores a generated story in memory
|
|
12
|
+
*/
|
|
13
|
+
storeStory(story: GeneratedStory): void;
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves a story by ID
|
|
16
|
+
*/
|
|
17
|
+
getStory(id: string): GeneratedStory | null;
|
|
18
|
+
/**
|
|
19
|
+
* Gets all stored stories
|
|
20
|
+
*/
|
|
21
|
+
getAllStories(): GeneratedStory[];
|
|
22
|
+
/**
|
|
23
|
+
* Deletes a story by ID
|
|
24
|
+
*/
|
|
25
|
+
deleteStory(id: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Clears all stories
|
|
28
|
+
*/
|
|
29
|
+
clearAllStories(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Gets story content for Storybook integration
|
|
32
|
+
*/
|
|
33
|
+
getStoryContent(id: string): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Gets story metadata for listing
|
|
36
|
+
*/
|
|
37
|
+
getStoryMetadata(): StoryMetadata[];
|
|
38
|
+
/**
|
|
39
|
+
* Cleans up old stories to prevent memory leaks
|
|
40
|
+
*/
|
|
41
|
+
private cleanupOldStories;
|
|
42
|
+
/**
|
|
43
|
+
* Counts components used in a story
|
|
44
|
+
*/
|
|
45
|
+
private countComponents;
|
|
46
|
+
/**
|
|
47
|
+
* Gets memory usage statistics
|
|
48
|
+
*/
|
|
49
|
+
getMemoryStats(): MemoryStats;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generated story interface
|
|
53
|
+
*/
|
|
54
|
+
export interface GeneratedStory {
|
|
55
|
+
id: string;
|
|
56
|
+
title: string;
|
|
57
|
+
description: string;
|
|
58
|
+
content: string;
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
lastAccessed: Date;
|
|
61
|
+
prompt?: string;
|
|
62
|
+
components?: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Story metadata for listing
|
|
66
|
+
*/
|
|
67
|
+
export interface StoryMetadata {
|
|
68
|
+
id: string;
|
|
69
|
+
title: string;
|
|
70
|
+
description: string;
|
|
71
|
+
createdAt: Date;
|
|
72
|
+
lastAccessed: Date;
|
|
73
|
+
componentCount: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Memory usage statistics
|
|
77
|
+
*/
|
|
78
|
+
export interface MemoryStats {
|
|
79
|
+
storyCount: number;
|
|
80
|
+
totalSizeBytes: number;
|
|
81
|
+
averageSizeBytes: number;
|
|
82
|
+
oldestStory: Date | null;
|
|
83
|
+
newestStory: Date | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Gets or creates the global story service instance
|
|
87
|
+
*/
|
|
88
|
+
export declare function getInMemoryStoryService(config: StoryUIConfig): InMemoryStoryService;
|
|
89
|
+
//# sourceMappingURL=inMemoryStoryService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inMemoryStoryService.d.ts","sourceRoot":"","sources":["../../story-generator/inMemoryStoryService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IAIjC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAWvC;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAS3C;;OAEG;IACH,aAAa,IAAI,cAAc,EAAE;IAMjC;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIhC;;OAEG;IACH,eAAe,IAAI,IAAI;IAIvB;;OAEG;IACH,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK1C;;OAEG;IACH,gBAAgB,IAAI,aAAa,EAAE;IAWnC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,cAAc,IAAI,WAAW;CAgB9B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAOD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,oBAAoB,CAKnF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class for LLM providers with shared functionality.
|
|
5
|
+
*/
|
|
6
|
+
import { LLMProvider, ProviderType, ProviderConfig, ModelInfo, ChatMessage, ChatOptions, ChatResponse, StreamChunk, ImageContent, ImageAnalysis, ValidationResult } from './types.js';
|
|
7
|
+
export declare abstract class BaseLLMProvider implements LLMProvider {
|
|
8
|
+
abstract readonly name: string;
|
|
9
|
+
abstract readonly type: ProviderType;
|
|
10
|
+
abstract readonly supportedModels: ModelInfo[];
|
|
11
|
+
protected config: ProviderConfig;
|
|
12
|
+
constructor(config?: Partial<ProviderConfig>);
|
|
13
|
+
/**
|
|
14
|
+
* Called after construction to set the provider type
|
|
15
|
+
* Subclasses should call this in their constructor after super()
|
|
16
|
+
*/
|
|
17
|
+
protected setProviderType(): void;
|
|
18
|
+
abstract chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>;
|
|
19
|
+
abstract validateApiKey(apiKey: string): Promise<ValidationResult>;
|
|
20
|
+
supportsVision(): boolean;
|
|
21
|
+
supportsDocuments(): boolean;
|
|
22
|
+
supportsFunctionCalling(): boolean;
|
|
23
|
+
supportsStreaming(): boolean;
|
|
24
|
+
configure(config: ProviderConfig): void;
|
|
25
|
+
getConfig(): ProviderConfig;
|
|
26
|
+
isConfigured(): boolean;
|
|
27
|
+
protected getSelectedModel(): ModelInfo | undefined;
|
|
28
|
+
protected validateMessages(messages: ChatMessage[]): void;
|
|
29
|
+
protected buildSystemPrompt(options?: ChatOptions): string | undefined;
|
|
30
|
+
chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncIterable<StreamChunk>;
|
|
31
|
+
analyzeImage(image: ImageContent, prompt?: string): Promise<ImageAnalysis>;
|
|
32
|
+
estimateTokens(text: string): number;
|
|
33
|
+
protected logRequest(messages: ChatMessage[], options?: ChatOptions): void;
|
|
34
|
+
protected logResponse(response: ChatResponse): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=base-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/base-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,8BAAsB,eAAgB,YAAW,WAAW;IAC1D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,SAAS,EAAE,CAAC;IAE/C,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC;gBAErB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAU5C;;;OAGG;IACH,SAAS,CAAC,eAAe,IAAI,IAAI;IAKjC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IACpF,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAGlE,cAAc,IAAI,OAAO;IAKzB,iBAAiB,IAAI,OAAO;IAK5B,uBAAuB,IAAI,OAAO;IAKlC,iBAAiB,IAAI,OAAO;IAM5B,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAQvC,SAAS,IAAI,cAAc;IAI3B,YAAY,IAAI,OAAO;IAKvB,SAAS,CAAC,gBAAgB,IAAI,SAAS,GAAG,SAAS;IAInD,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI;IAYzD,SAAS,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAK/D,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAqBvB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA2BhF,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMpC,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI;IAQ1E,SAAS,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAOpD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class for LLM providers with shared functionality.
|
|
5
|
+
*/
|
|
6
|
+
import { logger } from '../logger.js';
|
|
7
|
+
export class BaseLLMProvider {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
// Note: provider type will be set by subclass after construction
|
|
10
|
+
this.config = {
|
|
11
|
+
provider: 'custom', // Placeholder, will be set when configure() is called
|
|
12
|
+
model: '',
|
|
13
|
+
timeout: 120000, // 2 minutes default
|
|
14
|
+
...config,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Called after construction to set the provider type
|
|
19
|
+
* Subclasses should call this in their constructor after super()
|
|
20
|
+
*/
|
|
21
|
+
setProviderType() {
|
|
22
|
+
this.config.provider = this.type;
|
|
23
|
+
}
|
|
24
|
+
// Default capability checks (can be overridden)
|
|
25
|
+
supportsVision() {
|
|
26
|
+
const model = this.getSelectedModel();
|
|
27
|
+
return model?.supportsVision ?? false;
|
|
28
|
+
}
|
|
29
|
+
supportsDocuments() {
|
|
30
|
+
const model = this.getSelectedModel();
|
|
31
|
+
return model?.supportsDocuments ?? false;
|
|
32
|
+
}
|
|
33
|
+
supportsFunctionCalling() {
|
|
34
|
+
const model = this.getSelectedModel();
|
|
35
|
+
return model?.supportsFunctionCalling ?? false;
|
|
36
|
+
}
|
|
37
|
+
supportsStreaming() {
|
|
38
|
+
const model = this.getSelectedModel();
|
|
39
|
+
return model?.supportsStreaming ?? false;
|
|
40
|
+
}
|
|
41
|
+
// Configuration methods
|
|
42
|
+
configure(config) {
|
|
43
|
+
this.config = { ...this.config, ...config };
|
|
44
|
+
logger.debug(`${this.name} provider configured`, {
|
|
45
|
+
model: config.model,
|
|
46
|
+
hasApiKey: !!config.apiKey,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
getConfig() {
|
|
50
|
+
return { ...this.config };
|
|
51
|
+
}
|
|
52
|
+
isConfigured() {
|
|
53
|
+
return !!this.config.apiKey && !!this.config.model;
|
|
54
|
+
}
|
|
55
|
+
// Helper methods
|
|
56
|
+
getSelectedModel() {
|
|
57
|
+
return this.supportedModels.find(m => m.id === this.config.model);
|
|
58
|
+
}
|
|
59
|
+
validateMessages(messages) {
|
|
60
|
+
if (!messages || messages.length === 0) {
|
|
61
|
+
throw new Error('Messages array cannot be empty');
|
|
62
|
+
}
|
|
63
|
+
for (const msg of messages) {
|
|
64
|
+
if (!['user', 'assistant', 'system'].includes(msg.role)) {
|
|
65
|
+
throw new Error(`Invalid message role: ${msg.role}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
buildSystemPrompt(options) {
|
|
70
|
+
return options?.systemPrompt;
|
|
71
|
+
}
|
|
72
|
+
// Default streaming implementation (providers can override)
|
|
73
|
+
async *chatStream(messages, options) {
|
|
74
|
+
// Default non-streaming implementation
|
|
75
|
+
try {
|
|
76
|
+
const response = await this.chat(messages, options);
|
|
77
|
+
yield {
|
|
78
|
+
type: 'text',
|
|
79
|
+
content: response.content,
|
|
80
|
+
};
|
|
81
|
+
yield {
|
|
82
|
+
type: 'done',
|
|
83
|
+
usage: response.usage,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
yield {
|
|
88
|
+
type: 'error',
|
|
89
|
+
error: error instanceof Error ? error.message : String(error),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Default image analysis (providers can override)
|
|
94
|
+
async analyzeImage(image, prompt) {
|
|
95
|
+
if (!this.supportsVision()) {
|
|
96
|
+
throw new Error(`${this.name} provider does not support image analysis`);
|
|
97
|
+
}
|
|
98
|
+
const analysisPrompt = prompt || 'Analyze this image and describe what you see, including any UI components, layout structure, colors, and design patterns.';
|
|
99
|
+
const messages = [
|
|
100
|
+
{
|
|
101
|
+
role: 'user',
|
|
102
|
+
content: [
|
|
103
|
+
{ type: 'text', text: analysisPrompt },
|
|
104
|
+
image,
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
const response = await this.chat(messages);
|
|
109
|
+
// Parse the response to extract structured information
|
|
110
|
+
return {
|
|
111
|
+
description: response.content,
|
|
112
|
+
// Additional parsing could be done here based on the prompt
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Simple token estimation (providers can override with more accurate methods)
|
|
116
|
+
estimateTokens(text) {
|
|
117
|
+
// Rough estimate: ~4 characters per token for English text
|
|
118
|
+
return Math.ceil(text.length / 4);
|
|
119
|
+
}
|
|
120
|
+
// Request logging
|
|
121
|
+
logRequest(messages, options) {
|
|
122
|
+
logger.debug(`${this.name} API request`, {
|
|
123
|
+
model: options?.model || this.config.model,
|
|
124
|
+
messageCount: messages.length,
|
|
125
|
+
hasSystemPrompt: !!options?.systemPrompt,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
logResponse(response) {
|
|
129
|
+
logger.debug(`${this.name} API response`, {
|
|
130
|
+
model: response.model,
|
|
131
|
+
finishReason: response.finishReason,
|
|
132
|
+
usage: response.usage,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Implementation of the LLM provider interface for Anthropic's Claude models.
|
|
5
|
+
*/
|
|
6
|
+
import { ProviderType, ProviderConfig, ModelInfo, ChatMessage, ChatOptions, ChatResponse, StreamChunk, ValidationResult } from './types.js';
|
|
7
|
+
import { BaseLLMProvider } from './base-provider.js';
|
|
8
|
+
export declare class ClaudeProvider extends BaseLLMProvider {
|
|
9
|
+
readonly name = "Claude";
|
|
10
|
+
readonly type: ProviderType;
|
|
11
|
+
readonly supportedModels: ModelInfo[];
|
|
12
|
+
constructor(config?: Partial<ProviderConfig>);
|
|
13
|
+
chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>;
|
|
14
|
+
chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncIterable<StreamChunk>;
|
|
15
|
+
validateApiKey(apiKey: string): Promise<ValidationResult>;
|
|
16
|
+
private convertMessages;
|
|
17
|
+
private convertContent;
|
|
18
|
+
private convertResponse;
|
|
19
|
+
private mapStopReason;
|
|
20
|
+
estimateTokens(text: string): number;
|
|
21
|
+
}
|
|
22
|
+
export declare function createClaudeProvider(config?: Partial<ProviderConfig>): ClaudeProvider;
|
|
23
|
+
//# sourceMappingURL=claude-provider.d.ts.map
|
|
@@ -0,0 +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;AAwJrD,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"}
|