@houtini/gemini-mcp 1.4.5 → 2.2.0
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/README.md +314 -834
- package/claude_desktop_config_example.json +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -4
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/image-viewer/image-viewer-app.html +180 -0
- package/dist/image-viewer/src/ui/image-viewer.html +324 -0
- package/dist/index-new.d.ts +3 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +7 -0
- package/dist/index-new.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -172
- package/dist/index.js.map +1 -1
- package/dist/landing-page-viewer/src/ui/landing-page-viewer.html +330 -0
- package/dist/services/gemini/export.d.ts +5 -0
- package/dist/services/gemini/export.d.ts.map +1 -0
- package/dist/services/gemini/export.js +5 -0
- package/dist/services/gemini/export.js.map +1 -0
- package/dist/services/gemini/image-service.d.ts +45 -0
- package/dist/services/gemini/image-service.d.ts.map +1 -0
- package/dist/services/gemini/image-service.js +248 -0
- package/dist/services/gemini/image-service.js.map +1 -0
- package/dist/services/gemini/index.d.ts +7 -2
- package/dist/services/gemini/index.d.ts.map +1 -1
- package/dist/services/gemini/index.js +132 -56
- package/dist/services/gemini/index.js.map +1 -1
- package/dist/services/gemini/types.d.ts +32 -0
- package/dist/services/gemini/types.d.ts.map +1 -1
- package/dist/services/gemini/video-service.d.ts +58 -0
- package/dist/services/gemini/video-service.d.ts.map +1 -0
- package/dist/services/gemini/video-service.js +325 -0
- package/dist/services/gemini/video-service.js.map +1 -0
- package/dist/services/media-server.d.ts +28 -0
- package/dist/services/media-server.d.ts.map +1 -0
- package/dist/services/media-server.js +195 -0
- package/dist/services/media-server.js.map +1 -0
- package/dist/svg-viewer/src/ui/svg-viewer.html +325 -0
- package/dist/tools/gemini-chat.d.ts.map +1 -1
- package/dist/tools/gemini-chat.js +7 -1
- package/dist/tools/gemini-chat.js.map +1 -1
- package/dist/tools/gemini-deep-research.d.ts +1 -2
- package/dist/tools/gemini-deep-research.d.ts.map +1 -1
- package/dist/tools/gemini-deep-research.js +11 -51
- package/dist/tools/gemini-deep-research.js.map +1 -1
- package/dist/tools/gemini-help.d.ts +3 -0
- package/dist/tools/gemini-help.d.ts.map +1 -0
- package/dist/tools/gemini-help.js +534 -0
- package/dist/tools/gemini-help.js.map +1 -0
- package/dist/tools/gemini-prompt-assistant.d.ts +20 -0
- package/dist/tools/gemini-prompt-assistant.d.ts.map +1 -0
- package/dist/tools/gemini-prompt-assistant.js +129 -0
- package/dist/tools/gemini-prompt-assistant.js.map +1 -0
- package/dist/tools/generate-landing-page.d.ts +15 -0
- package/dist/tools/generate-landing-page.d.ts.map +1 -0
- package/dist/tools/generate-landing-page.js +66 -0
- package/dist/tools/generate-landing-page.js.map +1 -0
- package/dist/tools/generate-svg.d.ts +14 -0
- package/dist/tools/generate-svg.d.ts.map +1 -0
- package/dist/tools/generate-svg.js +106 -0
- package/dist/tools/generate-svg.js.map +1 -0
- package/dist/tools/generate-video.d.ts +24 -0
- package/dist/tools/generate-video.d.ts.map +1 -0
- package/dist/tools/generate-video.js +163 -0
- package/dist/tools/generate-video.js.map +1 -0
- package/dist/tools/image-prompt-assistant.d.ts +3 -0
- package/dist/tools/image-prompt-assistant.d.ts.map +1 -0
- package/dist/tools/image-prompt-assistant.js +790 -0
- package/dist/tools/image-prompt-assistant.js.map +1 -0
- package/dist/tools/load-image-from-path.d.ts +11 -0
- package/dist/tools/load-image-from-path.d.ts.map +1 -0
- package/dist/tools/load-image-from-path.js +100 -0
- package/dist/tools/load-image-from-path.js.map +1 -0
- package/dist/tools/prompt-library/charts.d.ts +325 -0
- package/dist/tools/prompt-library/charts.d.ts.map +1 -0
- package/dist/tools/prompt-library/charts.js +384 -0
- package/dist/tools/prompt-library/charts.js.map +1 -0
- package/dist/tools/prompt-library/index.d.ts +8 -0
- package/dist/tools/prompt-library/index.d.ts.map +1 -0
- package/dist/tools/prompt-library/index.js +10 -0
- package/dist/tools/prompt-library/index.js.map +1 -0
- package/dist/tools/register-analyze-image.d.ts +3 -0
- package/dist/tools/register-analyze-image.d.ts.map +1 -0
- package/dist/tools/register-analyze-image.js +67 -0
- package/dist/tools/register-analyze-image.js.map +1 -0
- package/dist/tools/register-chat.d.ts +3 -0
- package/dist/tools/register-chat.d.ts.map +1 -0
- package/dist/tools/register-chat.js +71 -0
- package/dist/tools/register-chat.js.map +1 -0
- package/dist/tools/register-deep-research.d.ts +3 -0
- package/dist/tools/register-deep-research.d.ts.map +1 -0
- package/dist/tools/register-deep-research.js +59 -0
- package/dist/tools/register-deep-research.js.map +1 -0
- package/dist/tools/register-describe-image.d.ts +3 -0
- package/dist/tools/register-describe-image.d.ts.map +1 -0
- package/dist/tools/register-describe-image.js +59 -0
- package/dist/tools/register-describe-image.js.map +1 -0
- package/dist/tools/register-image-gen.d.ts +3 -0
- package/dist/tools/register-image-gen.d.ts.map +1 -0
- package/dist/tools/register-image-gen.js +235 -0
- package/dist/tools/register-image-gen.js.map +1 -0
- package/dist/tools/register-landing-page.d.ts +3 -0
- package/dist/tools/register-landing-page.d.ts.map +1 -0
- package/dist/tools/register-landing-page.js +79 -0
- package/dist/tools/register-landing-page.js.map +1 -0
- package/dist/tools/register-list-models.d.ts +3 -0
- package/dist/tools/register-list-models.d.ts.map +1 -0
- package/dist/tools/register-list-models.js +33 -0
- package/dist/tools/register-list-models.js.map +1 -0
- package/dist/tools/register-load-image.d.ts +3 -0
- package/dist/tools/register-load-image.d.ts.map +1 -0
- package/dist/tools/register-load-image.js +66 -0
- package/dist/tools/register-load-image.js.map +1 -0
- package/dist/tools/register-svg.d.ts +3 -0
- package/dist/tools/register-svg.d.ts.map +1 -0
- package/dist/tools/register-svg.js +84 -0
- package/dist/tools/register-svg.js.map +1 -0
- package/dist/tools/register-video.d.ts +3 -0
- package/dist/tools/register-video.d.ts.map +1 -0
- package/dist/tools/register-video.js +118 -0
- package/dist/tools/register-video.js.map +1 -0
- package/dist/tools/register-viewers.d.ts +8 -0
- package/dist/tools/register-viewers.d.ts.map +1 -0
- package/dist/tools/register-viewers.js +89 -0
- package/dist/tools/register-viewers.js.map +1 -0
- package/dist/tools/schemas.d.ts +33 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +39 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/ui/image-viewer.d.ts +2 -0
- package/dist/ui/image-viewer.d.ts.map +1 -0
- package/dist/ui/image-viewer.js +42 -0
- package/dist/ui/image-viewer.js.map +1 -0
- package/dist/utils/chart-design-system.d.ts +92 -0
- package/dist/utils/chart-design-system.d.ts.map +1 -0
- package/dist/utils/chart-design-system.js +235 -0
- package/dist/utils/chart-design-system.js.map +1 -0
- package/dist/utils/image-compress.d.ts +9 -0
- package/dist/utils/image-compress.d.ts.map +1 -0
- package/dist/utils/image-compress.js +43 -0
- package/dist/utils/image-compress.js.map +1 -0
- package/dist/utils/image-utils.d.ts +9 -0
- package/dist/utils/image-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.js +257 -0
- package/dist/utils/image-utils.js.map +1 -0
- package/dist/utils/resolve-images.d.ts +29 -0
- package/dist/utils/resolve-images.d.ts.map +1 -0
- package/dist/utils/resolve-images.js +56 -0
- package/dist/utils/resolve-images.js.map +1 -0
- package/dist/utils/tool-wrapper.d.ts +13 -0
- package/dist/utils/tool-wrapper.d.ts.map +1 -0
- package/dist/utils/tool-wrapper.js +22 -0
- package/dist/utils/tool-wrapper.js.map +1 -0
- package/dist/utils/video-utils.d.ts +16 -0
- package/dist/utils/video-utils.d.ts.map +1 -0
- package/dist/utils/video-utils.js +319 -0
- package/dist/utils/video-utils.js.map +1 -0
- package/dist/video-viewer/src/ui/video-viewer.html +310 -0
- package/package.json +21 -7
- package/server.json +30 -29
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Professional Chart & Data Visualization Design System
|
|
3
|
+
*
|
|
4
|
+
* Based on research from Observable, D3.js, Information is Beautiful,
|
|
5
|
+
* and modern dashboard design systems.
|
|
6
|
+
*
|
|
7
|
+
* Purpose: Enhance prompts with professional design principles to avoid
|
|
8
|
+
* generic AI-generated aesthetics.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Professional color palettes based on industry standards
|
|
12
|
+
*/
|
|
13
|
+
const COLOR_SYSTEMS = {
|
|
14
|
+
professional: {
|
|
15
|
+
primary: ['#2563eb', '#7c3aed', '#dc2626', '#ea580c', '#ca8a04'],
|
|
16
|
+
neutral: ['#0f172a', '#475569', '#94a3b8', '#cbd5e1', '#f1f5f9'],
|
|
17
|
+
accent: ['#06b6d4', '#10b981', '#f59e0b'],
|
|
18
|
+
description: 'IBM Carbon, Tailwind-inspired professional palette'
|
|
19
|
+
},
|
|
20
|
+
editorial: {
|
|
21
|
+
primary: ['#e63946', '#f1faee', '#a8dadc', '#457b9d', '#1d3557'],
|
|
22
|
+
neutral: ['#2b2d42', '#8d99ae', '#edf2f4'],
|
|
23
|
+
accent: ['#ff6b6b', '#4ecdc4'],
|
|
24
|
+
description: 'FiveThirtyEight, Economist-inspired editorial colours'
|
|
25
|
+
},
|
|
26
|
+
scientific: {
|
|
27
|
+
primary: ['#003f5c', '#58508d', '#bc5090', '#ff6361', '#ffa600'],
|
|
28
|
+
neutral: ['#1a1a1a', '#4a4a4a', '#8a8a8a', '#cacaca', '#f0f0f0'],
|
|
29
|
+
accent: ['#00d9ff', '#7209b7'],
|
|
30
|
+
description: 'Nature, Science journal-inspired academic palette'
|
|
31
|
+
},
|
|
32
|
+
minimal: {
|
|
33
|
+
primary: ['#000000', '#404040', '#737373', '#a6a6a6', '#d9d9d9'],
|
|
34
|
+
neutral: ['#fafafa', '#f5f5f5', '#e5e5e5'],
|
|
35
|
+
accent: ['#2563eb', '#dc2626'],
|
|
36
|
+
description: 'Edward Tufte-inspired minimal data-ink approach'
|
|
37
|
+
},
|
|
38
|
+
dark: {
|
|
39
|
+
primary: ['#60a5fa', '#a78bfa', '#f472b6', '#fb923c', '#fbbf24'],
|
|
40
|
+
neutral: ['#0a0a0a', '#1f1f1f', '#404040', '#737373', '#a6a6a6'],
|
|
41
|
+
accent: ['#22d3ee', '#34d399'],
|
|
42
|
+
description: 'Observable dark mode, GitHub dark-inspired'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Typography systems based on research findings
|
|
47
|
+
*/
|
|
48
|
+
const TYPOGRAPHY_SYSTEMS = {
|
|
49
|
+
professional: {
|
|
50
|
+
title: 'Inter or SF Pro (system-ui fallback)',
|
|
51
|
+
labels: 'Inter or Roboto',
|
|
52
|
+
data: 'JetBrains Mono or SF Mono (monospace)',
|
|
53
|
+
sizes: {
|
|
54
|
+
title: '18-24px',
|
|
55
|
+
subtitle: '14-16px',
|
|
56
|
+
labels: '11-13px',
|
|
57
|
+
annotations: '10-12px'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
editorial: {
|
|
61
|
+
title: 'Playfair Display or Georgia',
|
|
62
|
+
labels: 'Inter or Franklin Gothic',
|
|
63
|
+
data: 'Courier New or monospace',
|
|
64
|
+
sizes: {
|
|
65
|
+
title: '24-32px',
|
|
66
|
+
subtitle: '16-20px',
|
|
67
|
+
labels: '12-14px',
|
|
68
|
+
annotations: '11-13px'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
scientific: {
|
|
72
|
+
title: 'Arial or Helvetica',
|
|
73
|
+
labels: 'Arial or Helvetica',
|
|
74
|
+
data: 'Monaco or Consolas',
|
|
75
|
+
sizes: {
|
|
76
|
+
title: '16-20px',
|
|
77
|
+
subtitle: '13-15px',
|
|
78
|
+
labels: '10-12px',
|
|
79
|
+
annotations: '9-11px'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Design principles based on best practices
|
|
85
|
+
*/
|
|
86
|
+
const DESIGN_PRINCIPLES = {
|
|
87
|
+
'data-ink': `
|
|
88
|
+
• Maximize data-ink ratio (Tufte principle)
|
|
89
|
+
• Remove chartjunk and unnecessary decoration
|
|
90
|
+
• Use subtle gridlines (1px, 10-15% opacity)
|
|
91
|
+
• Direct labeling instead of legends when possible
|
|
92
|
+
• Minimal borders and backgrounds
|
|
93
|
+
`,
|
|
94
|
+
'storytelling': `
|
|
95
|
+
• Clear visual hierarchy with focal points
|
|
96
|
+
• Progressive disclosure of complexity
|
|
97
|
+
• Annotations and callouts for key insights
|
|
98
|
+
• Contextual reference lines or benchmarks
|
|
99
|
+
• Narrative flow through chart elements
|
|
100
|
+
`,
|
|
101
|
+
'exploration': `
|
|
102
|
+
• Rich interactive affordances suggested visually
|
|
103
|
+
• Tooltip regions clearly defined
|
|
104
|
+
• Hover states indicated subtly
|
|
105
|
+
• Multiple layers of detail accessibility
|
|
106
|
+
• Clear filtering and selection states
|
|
107
|
+
`,
|
|
108
|
+
'presentation': `
|
|
109
|
+
• High contrast for projector/screen visibility
|
|
110
|
+
• Large, readable text (minimum 12px)
|
|
111
|
+
• Strong visual anchors and structure
|
|
112
|
+
• Balanced whitespace and breathing room
|
|
113
|
+
• Professional polish without decoration
|
|
114
|
+
`
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Chart-specific best practices
|
|
118
|
+
*/
|
|
119
|
+
const CHART_SPECIFICS = {
|
|
120
|
+
line: `
|
|
121
|
+
• Line weight: 2-3px for primary, 1-2px for secondary
|
|
122
|
+
• Point markers only at data points, not everywhere
|
|
123
|
+
• Differentiate series with both colour AND line style
|
|
124
|
+
• Y-axis starts at 0 unless justified otherwise
|
|
125
|
+
`,
|
|
126
|
+
bar: `
|
|
127
|
+
• Bar spacing: 10-30% of bar width
|
|
128
|
+
• Sorted by value unless categorical order matters
|
|
129
|
+
• Horizontal bars for long labels
|
|
130
|
+
• Subtle rounded corners (2-4px) for modern feel
|
|
131
|
+
`,
|
|
132
|
+
scatter: `
|
|
133
|
+
• Point size: 4-8px diameter
|
|
134
|
+
• 30-50% opacity for overlapping points
|
|
135
|
+
• Trend lines should be subtle (dashed, lighter colour)
|
|
136
|
+
• Legend shows both shape AND colour
|
|
137
|
+
`,
|
|
138
|
+
pie: `
|
|
139
|
+
• Maximum 5-7 slices (combine small values into "Other")
|
|
140
|
+
• Start at 12 o'clock, arrange clockwise by size
|
|
141
|
+
• Consider donut chart (60-70% inner radius)
|
|
142
|
+
• Direct slice labeling with values
|
|
143
|
+
`,
|
|
144
|
+
heatmap: `
|
|
145
|
+
• Sequential colour scale for continuous data
|
|
146
|
+
• Diverging scale for data with meaningful midpoint
|
|
147
|
+
• Include colour legend with numerical scale
|
|
148
|
+
• Cell annotations for key values
|
|
149
|
+
`
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Generate professional design prompt enhancement
|
|
153
|
+
*/
|
|
154
|
+
export function enhanceChartPrompt(basePrompt, options = {}) {
|
|
155
|
+
const { colorScheme = 'professional', chartType = 'bar', emphasis = 'data-ink' } = options;
|
|
156
|
+
const colors = COLOR_SYSTEMS[colorScheme];
|
|
157
|
+
const typography = TYPOGRAPHY_SYSTEMS[colorScheme === 'editorial' ? 'editorial' :
|
|
158
|
+
colorScheme === 'scientific' ? 'scientific' : 'professional'];
|
|
159
|
+
const principles = DESIGN_PRINCIPLES[emphasis];
|
|
160
|
+
const specifics = CHART_SPECIFICS[chartType] || '';
|
|
161
|
+
return `${basePrompt}
|
|
162
|
+
|
|
163
|
+
PROFESSIONAL DESIGN SYSTEM:
|
|
164
|
+
Style: ${colors.description}
|
|
165
|
+
|
|
166
|
+
COLOUR PALETTE:
|
|
167
|
+
Primary: ${colors.primary.join(', ')}
|
|
168
|
+
Neutral greys: ${colors.neutral.join(', ')}
|
|
169
|
+
Accent: ${colors.accent.join(', ')}
|
|
170
|
+
|
|
171
|
+
TYPOGRAPHY:
|
|
172
|
+
• Titles: ${typography.title} at ${typography.sizes.title}, font-weight 600
|
|
173
|
+
• Axis labels: ${typography.labels} at ${typography.sizes.labels}, font-weight 500
|
|
174
|
+
• Data labels: ${typography.data} at ${typography.sizes.labels}
|
|
175
|
+
• Annotations: ${typography.labels} at ${typography.sizes.annotations}, font-weight 400
|
|
176
|
+
|
|
177
|
+
DESIGN PRINCIPLES:${principles}
|
|
178
|
+
|
|
179
|
+
CHART-SPECIFIC GUIDELINES:${specifics}
|
|
180
|
+
|
|
181
|
+
LAYOUT & SPACING:
|
|
182
|
+
• Margins: 40-60px (top/bottom), 50-80px (left/right) for axis labels
|
|
183
|
+
• Title placement: top-left or centered, 20-30px from chart area
|
|
184
|
+
• Legend: top-right or bottom, with 16px item spacing
|
|
185
|
+
• Grid: subtle (1px, #000 at 8-12% opacity), aligned to data ticks
|
|
186
|
+
|
|
187
|
+
AVOID:
|
|
188
|
+
❌ 3D effects, shadows, or gradients on data elements
|
|
189
|
+
❌ Decorative borders, backgrounds, or textures
|
|
190
|
+
❌ Bright, saturated neon colours
|
|
191
|
+
❌ Comic Sans, Papyrus, or novelty fonts
|
|
192
|
+
❌ Unnecessary chart elements (chartjunk)
|
|
193
|
+
❌ Default chart library aesthetics (Excel, Google Sheets look)
|
|
194
|
+
|
|
195
|
+
ACHIEVE:
|
|
196
|
+
✓ Clean, confident, professional presentation
|
|
197
|
+
✓ Accessibility (WCAG AA contrast ratios minimum)
|
|
198
|
+
✓ Print-ready quality (300 DPI equivalent)
|
|
199
|
+
✓ Timeless design that won't look dated
|
|
200
|
+
✓ Focus on data clarity and insight`;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get color palette for a specific scheme
|
|
204
|
+
*/
|
|
205
|
+
export function getColorPalette(scheme) {
|
|
206
|
+
return COLOR_SYSTEMS[scheme];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Validate chart design prompt for common issues
|
|
210
|
+
*/
|
|
211
|
+
export function validateChartPrompt(prompt) {
|
|
212
|
+
const warnings = [];
|
|
213
|
+
const suggestions = [];
|
|
214
|
+
// Check for anti-patterns
|
|
215
|
+
if (prompt.toLowerCase().includes('3d') || prompt.toLowerCase().includes('shadow')) {
|
|
216
|
+
warnings.push('3D effects and shadows reduce data clarity');
|
|
217
|
+
suggestions.push('Use flat design with subtle depth cues instead');
|
|
218
|
+
}
|
|
219
|
+
if (prompt.toLowerCase().includes('gradient') && prompt.includes('data')) {
|
|
220
|
+
warnings.push('Gradients on data elements can be distracting');
|
|
221
|
+
suggestions.push('Reserve gradients for backgrounds only, if at all');
|
|
222
|
+
}
|
|
223
|
+
if (!prompt.toLowerCase().includes('color') && !prompt.toLowerCase().includes('colour')) {
|
|
224
|
+
suggestions.push('Consider specifying a professional colour palette');
|
|
225
|
+
}
|
|
226
|
+
if (!prompt.toLowerCase().includes('font') && !prompt.toLowerCase().includes('typography')) {
|
|
227
|
+
suggestions.push('Specify typography for more professional results');
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
valid: warnings.length === 0,
|
|
231
|
+
warnings,
|
|
232
|
+
suggestions
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=chart-design-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-design-system.js","sourceRoot":"","sources":["../../src/utils/chart-design-system.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CompressResult {
|
|
2
|
+
base64: string;
|
|
3
|
+
mimeType: 'image/jpeg';
|
|
4
|
+
originalBytes: number;
|
|
5
|
+
previewBytes: number;
|
|
6
|
+
wasCompressed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function compressForViewer(base64Data: string, sourceMimeType: string): Promise<CompressResult>;
|
|
9
|
+
//# sourceMappingURL=image-compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-compress.d.ts","sourceRoot":"","sources":["../../src/utils/image-compress.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CAyCzB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
const MAX_VIEWER_DIMENSION = 1024;
|
|
3
|
+
const VIEWER_QUALITY = 100;
|
|
4
|
+
const MAX_VIEWER_BYTES = 400_000;
|
|
5
|
+
export async function compressForViewer(base64Data, sourceMimeType) {
|
|
6
|
+
const inputBuffer = Buffer.from(base64Data, 'base64');
|
|
7
|
+
const originalBytes = inputBuffer.length;
|
|
8
|
+
const outputBuffer = await sharp(inputBuffer)
|
|
9
|
+
.resize(MAX_VIEWER_DIMENSION, MAX_VIEWER_DIMENSION, {
|
|
10
|
+
fit: 'inside',
|
|
11
|
+
withoutEnlargement: true,
|
|
12
|
+
})
|
|
13
|
+
.jpeg({ quality: VIEWER_QUALITY, mozjpeg: true })
|
|
14
|
+
.toBuffer();
|
|
15
|
+
if (outputBuffer.length > MAX_VIEWER_BYTES) {
|
|
16
|
+
for (const quality of [95, 90, 85]) {
|
|
17
|
+
const retryBuffer = await sharp(inputBuffer)
|
|
18
|
+
.resize(MAX_VIEWER_DIMENSION, MAX_VIEWER_DIMENSION, {
|
|
19
|
+
fit: 'inside',
|
|
20
|
+
withoutEnlargement: true,
|
|
21
|
+
})
|
|
22
|
+
.jpeg({ quality, mozjpeg: true })
|
|
23
|
+
.toBuffer();
|
|
24
|
+
if (retryBuffer.length <= MAX_VIEWER_BYTES) {
|
|
25
|
+
return {
|
|
26
|
+
base64: retryBuffer.toString('base64'),
|
|
27
|
+
mimeType: 'image/jpeg',
|
|
28
|
+
originalBytes,
|
|
29
|
+
previewBytes: retryBuffer.length,
|
|
30
|
+
wasCompressed: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
base64: outputBuffer.toString('base64'),
|
|
37
|
+
mimeType: 'image/jpeg',
|
|
38
|
+
originalBytes,
|
|
39
|
+
previewBytes: outputBuffer.length,
|
|
40
|
+
wasCompressed: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=image-compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-compress.js","sourceRoot":"","sources":["../../src/utils/image-compress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAClC,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAUjC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,cAAsB;IAEtB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC;IAEzC,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC;SAC1C,MAAM,CAAC,oBAAoB,EAAE,oBAAoB,EAAE;QAClD,GAAG,EAAE,QAAQ;QACb,kBAAkB,EAAE,IAAI;KACzB,CAAC;SACD,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;SAChD,QAAQ,EAAE,CAAC;IAEd,IAAI,YAAY,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAC3C,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;YACnC,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC;iBACzC,MAAM,CAAC,oBAAoB,EAAE,oBAAoB,EAAE;gBAClD,GAAG,EAAE,QAAQ;gBACb,kBAAkB,EAAE,IAAI;aACzB,CAAC;iBACD,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;iBAChC,QAAQ,EAAE,CAAC;YAEd,IAAI,WAAW,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;gBAC3C,OAAO;oBACL,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBACtC,QAAQ,EAAE,YAAY;oBACtB,aAAa;oBACb,YAAY,EAAE,WAAW,CAAC,MAAM;oBAChC,aAAa,EAAE,IAAI;iBACpB,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACvC,QAAQ,EAAE,YAAY;QACtB,aAAa;QACb,YAAY,EAAE,YAAY,CAAC,MAAM;QACjC,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function saveImageToFile(base64Data: string, outputPath: string): Promise<string>;
|
|
2
|
+
export declare function createImagePreviewHtml(imagePath: string, prompt: string, description?: string): Promise<string>;
|
|
3
|
+
export interface ProcessedImage {
|
|
4
|
+
savedPath: string;
|
|
5
|
+
previewPath: string;
|
|
6
|
+
previewImageData: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function processGeneratedImage(base64Data: string, mimeType: string, savePath: string, prompt: string, description?: string): Promise<ProcessedImage>;
|
|
9
|
+
//# sourceMappingURL=image-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.d.ts","sourceRoot":"","sources":["../../src/utils/image-utils.ts"],"names":[],"mappings":"AAKA,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAsOjB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAkBzB"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import logger from './logger.js';
|
|
4
|
+
import { compressForViewer } from './image-compress.js';
|
|
5
|
+
export async function saveImageToFile(base64Data, outputPath) {
|
|
6
|
+
const absolutePath = resolve(outputPath);
|
|
7
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
8
|
+
await writeFile(absolutePath, Buffer.from(base64Data, 'base64'));
|
|
9
|
+
return absolutePath;
|
|
10
|
+
}
|
|
11
|
+
export async function createImagePreviewHtml(imagePath, prompt, description) {
|
|
12
|
+
const htmlPath = imagePath.replace(/\.(png|jpg|jpeg|webp)$/i, '.html');
|
|
13
|
+
const imageFilename = imagePath.split(/[/\\]/).pop() || 'image';
|
|
14
|
+
const html = `<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>Gemini Image: ${imageFilename}</title>
|
|
20
|
+
<style>
|
|
21
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
|
+
body {
|
|
23
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
24
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
padding: 20px;
|
|
30
|
+
}
|
|
31
|
+
.container {
|
|
32
|
+
background: white;
|
|
33
|
+
border-radius: 12px;
|
|
34
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
35
|
+
max-width: 1200px;
|
|
36
|
+
width: 100%;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
.header {
|
|
40
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
41
|
+
color: white;
|
|
42
|
+
padding: 30px;
|
|
43
|
+
text-align: center;
|
|
44
|
+
}
|
|
45
|
+
.header h1 {
|
|
46
|
+
font-size: 24px;
|
|
47
|
+
margin-bottom: 10px;
|
|
48
|
+
}
|
|
49
|
+
.header p {
|
|
50
|
+
opacity: 0.9;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
}
|
|
53
|
+
.content {
|
|
54
|
+
padding: 40px;
|
|
55
|
+
}
|
|
56
|
+
.image-container {
|
|
57
|
+
text-align: center;
|
|
58
|
+
margin-bottom: 30px;
|
|
59
|
+
background: #f8f9fa;
|
|
60
|
+
border-radius: 8px;
|
|
61
|
+
padding: 20px;
|
|
62
|
+
}
|
|
63
|
+
.image-container img {
|
|
64
|
+
max-width: 100%;
|
|
65
|
+
height: auto;
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
68
|
+
}
|
|
69
|
+
.metadata {
|
|
70
|
+
background: #f8f9fa;
|
|
71
|
+
border-radius: 8px;
|
|
72
|
+
padding: 20px;
|
|
73
|
+
margin-top: 20px;
|
|
74
|
+
}
|
|
75
|
+
.metadata h2 {
|
|
76
|
+
font-size: 18px;
|
|
77
|
+
margin-bottom: 15px;
|
|
78
|
+
color: #333;
|
|
79
|
+
}
|
|
80
|
+
.metadata-item {
|
|
81
|
+
margin-bottom: 15px;
|
|
82
|
+
padding-bottom: 15px;
|
|
83
|
+
border-bottom: 1px solid #e0e0e0;
|
|
84
|
+
}
|
|
85
|
+
.metadata-item:last-child {
|
|
86
|
+
border-bottom: none;
|
|
87
|
+
margin-bottom: 0;
|
|
88
|
+
padding-bottom: 0;
|
|
89
|
+
}
|
|
90
|
+
.metadata-label {
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
color: #667eea;
|
|
93
|
+
display: block;
|
|
94
|
+
margin-bottom: 5px;
|
|
95
|
+
font-size: 14px;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
letter-spacing: 0.5px;
|
|
98
|
+
}
|
|
99
|
+
.metadata-value {
|
|
100
|
+
color: #555;
|
|
101
|
+
line-height: 1.6;
|
|
102
|
+
}
|
|
103
|
+
.prompt-box {
|
|
104
|
+
background: #fff;
|
|
105
|
+
border: 2px solid #667eea;
|
|
106
|
+
border-radius: 6px;
|
|
107
|
+
padding: 15px;
|
|
108
|
+
margin-top: 10px;
|
|
109
|
+
position: relative;
|
|
110
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
line-height: 1.6;
|
|
113
|
+
word-wrap: break-word;
|
|
114
|
+
}
|
|
115
|
+
.copy-btn {
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: 10px;
|
|
118
|
+
right: 10px;
|
|
119
|
+
background: #667eea;
|
|
120
|
+
color: white;
|
|
121
|
+
border: none;
|
|
122
|
+
padding: 6px 12px;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
transition: background 0.3s;
|
|
128
|
+
}
|
|
129
|
+
.copy-btn:hover {
|
|
130
|
+
background: #5568d3;
|
|
131
|
+
}
|
|
132
|
+
.copy-btn:active {
|
|
133
|
+
background: #4a5bc4;
|
|
134
|
+
}
|
|
135
|
+
.copy-btn.copied {
|
|
136
|
+
background: #10b981;
|
|
137
|
+
}
|
|
138
|
+
.footer {
|
|
139
|
+
text-align: center;
|
|
140
|
+
padding: 20px;
|
|
141
|
+
color: #999;
|
|
142
|
+
font-size: 12px;
|
|
143
|
+
border-top: 1px solid #e0e0e0;
|
|
144
|
+
}
|
|
145
|
+
.actions {
|
|
146
|
+
display: flex;
|
|
147
|
+
gap: 10px;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
margin-top: 20px;
|
|
150
|
+
}
|
|
151
|
+
.btn {
|
|
152
|
+
background: #667eea;
|
|
153
|
+
color: white;
|
|
154
|
+
border: none;
|
|
155
|
+
padding: 12px 24px;
|
|
156
|
+
border-radius: 6px;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
text-decoration: none;
|
|
161
|
+
display: inline-block;
|
|
162
|
+
transition: background 0.3s;
|
|
163
|
+
}
|
|
164
|
+
.btn:hover {
|
|
165
|
+
background: #5568d3;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<div class="container">
|
|
171
|
+
<div class="header">
|
|
172
|
+
<h1>Gemini Generated Image</h1>
|
|
173
|
+
<p>Created with Google Gemini AI (Nano Banana Pro)</p>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="content">
|
|
177
|
+
<div class="image-container">
|
|
178
|
+
<img src="${imageFilename}" alt="Generated image">
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div class="metadata">
|
|
182
|
+
<h2>Generation Details</h2>
|
|
183
|
+
|
|
184
|
+
<div class="metadata-item">
|
|
185
|
+
<span class="metadata-label">Prompt</span>
|
|
186
|
+
<div class="prompt-box" id="promptBox">
|
|
187
|
+
<button class="copy-btn" onclick="copyPrompt()">Copy</button>
|
|
188
|
+
${prompt.replace(/</g, '<').replace(/>/g, '>')}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
${description ? `
|
|
193
|
+
<div class="metadata-item">
|
|
194
|
+
<span class="metadata-label">AI Description</span>
|
|
195
|
+
<div class="metadata-value">${description.replace(/</g, '<').replace(/>/g, '>')}</div>
|
|
196
|
+
</div>
|
|
197
|
+
` : ''}
|
|
198
|
+
|
|
199
|
+
<div class="metadata-item">
|
|
200
|
+
<span class="metadata-label">Generated</span>
|
|
201
|
+
<div class="metadata-value">${new Date().toLocaleString()}</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="metadata-item">
|
|
205
|
+
<span class="metadata-label">File</span>
|
|
206
|
+
<div class="metadata-value">${imageFilename}</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="actions">
|
|
211
|
+
<a href="${imageFilename}" download class="btn">Download Image</a>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div class="footer">
|
|
216
|
+
Generated by Gemini MCP Server · @houtini/gemini-mcp
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<script>
|
|
221
|
+
function copyPrompt() {
|
|
222
|
+
const promptText = document.getElementById('promptBox').innerText.replace('Copy', '').trim();
|
|
223
|
+
navigator.clipboard.writeText(promptText).then(() => {
|
|
224
|
+
const btn = document.querySelector('.copy-btn');
|
|
225
|
+
const originalText = btn.textContent;
|
|
226
|
+
btn.textContent = 'Copied!';
|
|
227
|
+
btn.classList.add('copied');
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
btn.textContent = originalText;
|
|
230
|
+
btn.classList.remove('copied');
|
|
231
|
+
}, 2000);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
</script>
|
|
235
|
+
</body>
|
|
236
|
+
</html>`;
|
|
237
|
+
await writeFile(htmlPath, html, 'utf-8');
|
|
238
|
+
return htmlPath;
|
|
239
|
+
}
|
|
240
|
+
export async function processGeneratedImage(base64Data, mimeType, savePath, prompt, description) {
|
|
241
|
+
const savedPath = await saveImageToFile(base64Data, savePath);
|
|
242
|
+
const previewPath = await createImagePreviewHtml(savedPath, prompt, description);
|
|
243
|
+
const compressed = await compressForViewer(base64Data, mimeType);
|
|
244
|
+
logger.info('Image saved successfully', {
|
|
245
|
+
savedPath,
|
|
246
|
+
previewPath,
|
|
247
|
+
originalKB: Math.round(compressed.originalBytes / 1024),
|
|
248
|
+
previewKB: Math.round(compressed.previewBytes / 1024),
|
|
249
|
+
compressionRatio: (compressed.originalBytes / compressed.previewBytes).toFixed(1) + 'x',
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
savedPath,
|
|
253
|
+
previewPath,
|
|
254
|
+
previewImageData: compressed.base64,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=image-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.js","sourceRoot":"","sources":["../../src/utils/image-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB,EAAE,UAAkB;IAC1E,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,SAAiB,EACjB,MAAc,EACd,WAAoB;IAEpB,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,yBAAyB,EAAE,OAAO,CAAC,CAAC;IACvE,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC;IAEhE,MAAM,IAAI,GAAG;;;;;yBAKU,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA+JlB,aAAa;;;;;;;;;;cAUnB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;;;;UAItD,WAAW,CAAC,CAAC,CAAC;;;wCAGgB,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;;SAEtF,CAAC,CAAC,CAAC,EAAE;;;;wCAI0B,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE;;;;;wCAK3B,aAAa;;;;;mBAKlC,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;QAyBxB,CAAC;IAEP,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAkB,EAClB,QAAgB,EAChB,QAAgB,EAChB,MAAc,EACd,WAAoB;IAEpB,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,WAAW,GAAG,MAAM,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IACjF,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAEjE,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;QACtC,SAAS;QACT,WAAW;QACX,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,aAAa,GAAG,IAAI,CAAC;QACvD,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC;QACrD,gBAAgB,EAAE,CAAC,UAAU,CAAC,aAAa,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG;KACxF,CAAC,CAAC;IAEH,OAAO;QACL,SAAS;QACT,WAAW;QACX,gBAAgB,EAAE,UAAU,CAAC,MAAM;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ImageInput {
|
|
2
|
+
data?: string;
|
|
3
|
+
filePath?: string;
|
|
4
|
+
mimeType?: string;
|
|
5
|
+
/** Thought signature from a previous Gemini image generation/edit call.
|
|
6
|
+
* Must be round-tripped verbatim for conversational editing. */
|
|
7
|
+
thoughtSignature?: string;
|
|
8
|
+
mediaResolution?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ResolvedImage {
|
|
11
|
+
data: string;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
/** Preserved thought signature for conversational editing. */
|
|
14
|
+
thoughtSignature?: string;
|
|
15
|
+
mediaResolution?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve an image input to base64 data, loading from disk if a filePath
|
|
19
|
+
* is provided. This runs server-side so the image data never transits MCP.
|
|
20
|
+
*
|
|
21
|
+
* Priority: if `data` is provided it is used directly; otherwise `filePath`
|
|
22
|
+
* is read from disk.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveImageInput(image: ImageInput): Promise<ResolvedImage>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve an array of image inputs, loading any filePath references from disk.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveImageInputs(images: ImageInput[]): Promise<ResolvedImage[]>;
|
|
29
|
+
//# sourceMappingURL=resolve-images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-images.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-images.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;qEACiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAsCjF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAEvF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { extname, resolve } from 'path';
|
|
3
|
+
import logger from './logger.js';
|
|
4
|
+
const MIME_TYPES = {
|
|
5
|
+
'.jpg': 'image/jpeg',
|
|
6
|
+
'.jpeg': 'image/jpeg',
|
|
7
|
+
'.png': 'image/png',
|
|
8
|
+
'.gif': 'image/gif',
|
|
9
|
+
'.webp': 'image/webp',
|
|
10
|
+
'.bmp': 'image/bmp',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve an image input to base64 data, loading from disk if a filePath
|
|
14
|
+
* is provided. This runs server-side so the image data never transits MCP.
|
|
15
|
+
*
|
|
16
|
+
* Priority: if `data` is provided it is used directly; otherwise `filePath`
|
|
17
|
+
* is read from disk.
|
|
18
|
+
*/
|
|
19
|
+
export async function resolveImageInput(image) {
|
|
20
|
+
if (image.data && image.mimeType) {
|
|
21
|
+
return {
|
|
22
|
+
data: image.data,
|
|
23
|
+
mimeType: image.mimeType,
|
|
24
|
+
thoughtSignature: image.thoughtSignature,
|
|
25
|
+
mediaResolution: image.mediaResolution,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (image.filePath) {
|
|
29
|
+
const abs = resolve(image.filePath);
|
|
30
|
+
const ext = extname(abs).toLowerCase();
|
|
31
|
+
const mimeType = MIME_TYPES[ext];
|
|
32
|
+
if (!mimeType) {
|
|
33
|
+
throw new Error(`Unsupported image format: ${ext}. Supported: ${Object.keys(MIME_TYPES).join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
const buffer = await readFile(abs);
|
|
36
|
+
logger.info('Resolved image from file path (server-side)', {
|
|
37
|
+
filePath: abs,
|
|
38
|
+
sizeKB: Math.round(buffer.length / 1024),
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
data: buffer.toString('base64'),
|
|
42
|
+
mimeType,
|
|
43
|
+
thoughtSignature: image.thoughtSignature,
|
|
44
|
+
mediaResolution: image.mediaResolution,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
throw new Error('Image must provide either data+mimeType (base64) or filePath. ' +
|
|
48
|
+
'Use filePath to bypass MCP transport limits for large images.');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve an array of image inputs, loading any filePath references from disk.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveImageInputs(images) {
|
|
54
|
+
return Promise.all(images.map(resolveImageInput));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=resolve-images.js.map
|