Rewrite frontend as single self-contained HTML file — all CSS/JS inline, no external files to fail loading

This commit is contained in:
2026-05-13 17:24:10 +02:00
parent 3432d362e2
commit ddb0f88257
116 changed files with 4240 additions and 921 deletions

View File

@@ -0,0 +1,33 @@
import { VisionProvider, VisionProviderConfig, VisionResult, BatchContext } from '../../interfaces';
/**
* Google Gemini Vision Provider Implementation
*/
export declare class GeminiVisionProvider implements VisionProvider {
private config;
private genAI;
private model;
constructor(config: VisionProviderConfig);
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeImage(imagePath: string, prompt: string): Promise<VisionResult>;
/**
* Compare two images and describe the differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
compareImages(image1Path: string, image2Path: string, prompt: string): Promise<VisionResult>;
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeBatch(imagePaths: string[], lastBatchContext: BatchContext, prompt: string): Promise<VisionResult>;
}

View File

@@ -0,0 +1,162 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeminiVisionProvider = void 0;
const fs_1 = __importDefault(require("fs"));
const generative_ai_1 = require("@google/generative-ai");
/**
* Google Gemini Vision Provider Implementation
*/
class GeminiVisionProvider {
constructor(config) {
this.config = config;
this.genAI = new generative_ai_1.GoogleGenerativeAI(config.apiKey);
this.model = this.genAI.getGenerativeModel({ model: config.model });
}
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeImage(imagePath, prompt) {
try {
const imageData = fs_1.default.readFileSync(imagePath);
const mimeType = 'image/jpeg'; // Assuming JPEG, could be detected based on file extension
// Create a file part for the image
const imagePart = {
inlineData: {
data: imageData.toString('base64'),
mimeType
}
};
// Generate content using Gemini
const result = await this.model.generateContent([prompt, imagePart]);
const response = await result.response;
const text = response.text();
// Gemini doesn't provide token usage information in the same way as OpenAI
// We'll estimate based on prompt length and response length
const inputTokens = Math.ceil(prompt.length / 4) + 1000; // rough estimate for image
const outputTokens = Math.ceil(text.length / 4);
return {
description: text,
usage: {
inputTokens,
outputTokens,
totalTokens: inputTokens + outputTokens
}
};
}
catch (error) {
console.error("Error describing image with Gemini:", error);
return {
description: "Unable to describe this image.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Compare two images and describe the differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async compareImages(image1Path, image2Path, prompt) {
try {
const image1Data = fs_1.default.readFileSync(image1Path);
const image2Data = fs_1.default.readFileSync(image2Path);
const mimeType = 'image/jpeg'; // Assuming JPEG, could be detected based on file extension
// Create file parts for both images
const image1Part = {
inlineData: {
data: image1Data.toString('base64'),
mimeType
}
};
const image2Part = {
inlineData: {
data: image2Data.toString('base64'),
mimeType
}
};
// Generate content using Gemini with both images
const result = await this.model.generateContent([prompt, image1Part, image2Part]);
const response = await result.response;
const text = response.text();
// Estimate token usage
const inputTokens = Math.ceil(prompt.length / 4) + 2000; // rough estimate for two images
const outputTokens = Math.ceil(text.length / 4);
return {
description: text,
usage: {
inputTokens,
outputTokens,
totalTokens: inputTokens + outputTokens
}
};
}
catch (error) {
console.error("Error comparing images with Gemini:", error);
return {
description: "Unable to describe the differences between these images.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeBatch(imagePaths, lastBatchContext, prompt) {
try {
// Create a prompt that includes context from the last batch if available
let contextualPrompt = prompt;
if (lastBatchContext && lastBatchContext.lastDescription) {
contextualPrompt = `Previous batch summary: ${lastBatchContext.lastDescription}\n\n${prompt}`;
}
// Create content parts array starting with the prompt
const contentParts = [contextualPrompt];
// Add all images to the content parts
for (const imagePath of imagePaths) {
const imageData = fs_1.default.readFileSync(imagePath);
const mimeType = 'image/jpeg'; // Assuming JPEG, could be detected based on file extension
contentParts.push({
inlineData: {
data: imageData.toString('base64'),
mimeType
}
});
}
// Generate content using Gemini with all images
const result = await this.model.generateContent(contentParts);
const response = await result.response;
const text = response.text();
// Estimate token usage
const inputTokens = Math.ceil(contextualPrompt.length / 4) + (1000 * imagePaths.length); // rough estimate
const outputTokens = Math.ceil(text.length / 4);
return {
description: text,
usage: {
inputTokens,
outputTokens,
totalTokens: inputTokens + outputTokens
}
};
}
catch (error) {
console.error("Error describing batch of images with Gemini:", error);
return {
description: "Unable to describe this batch of images.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
}
exports.GeminiVisionProvider = GeminiVisionProvider;
//# sourceMappingURL=geminiVisionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"geminiVisionProvider.js","sourceRoot":"","sources":["../../../src/providers/vision/geminiVisionProvider.ts"],"names":[],"mappings":"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"}

5
dist/providers/vision/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export * from './visionProviderFactory';
export * from './openAIVisionProvider';
export * from './geminiVisionProvider';
export * from './ollamaVisionProvider';
export * from './openRouterVisionProvider';

22
dist/providers/vision/index.js vendored Normal file
View File

@@ -0,0 +1,22 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./visionProviderFactory"), exports);
__exportStar(require("./openAIVisionProvider"), exports);
__exportStar(require("./geminiVisionProvider"), exports);
__exportStar(require("./ollamaVisionProvider"), exports);
__exportStar(require("./openRouterVisionProvider"), exports);
//# sourceMappingURL=index.js.map

1
dist/providers/vision/index.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/vision/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,yDAAuC;AACvC,yDAAuC;AACvC,yDAAuC;AACvC,6DAA2C"}

View File

@@ -0,0 +1,33 @@
import { VisionProvider, VisionProviderConfig, VisionResult, BatchContext } from '../../interfaces';
/**
* Ollama Vision Provider Implementation
* See: https://github.com/ollama/ollama/blob/main/docs/api.md
*/
export declare class OllamaVisionProvider implements VisionProvider {
private config;
private axiosInstance;
constructor(config: VisionProviderConfig);
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeImage(imagePath: string, prompt: string): Promise<VisionResult>;
/**
* Compare two images and describe differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
compareImages(image1Path: string, image2Path: string, prompt: string): Promise<VisionResult>;
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch (optional)
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeBatch(imagePaths: string[], lastBatchContext: BatchContext, prompt: string): Promise<VisionResult>;
}

View File

@@ -0,0 +1,141 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OllamaVisionProvider = void 0;
const fs_1 = __importDefault(require("fs"));
const axios_1 = __importDefault(require("axios"));
/**
* Ollama Vision Provider Implementation
* See: https://github.com/ollama/ollama/blob/main/docs/api.md
*/
class OllamaVisionProvider {
constructor(config) {
this.config = config;
this.axiosInstance = axios_1.default.create({
baseURL: config.baseUrl || "http://localhost:11434",
headers: { "Content-Type": "application/json" }
});
}
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeImage(imagePath, prompt) {
try {
const imageData = fs_1.default.readFileSync(imagePath);
const base64Image = imageData.toString('base64');
const response = await this.axiosInstance.post('/api/generate', {
model: this.config.model,
prompt: prompt,
images: [base64Image],
stream: false,
options: {
max_tokens: this.config.maxTokens || 300,
temperature: 0.1
}
});
const combinedText = response.data.response || "";
return {
description: combinedText.trim(),
usage: {
inputTokens: 0,
outputTokens: 0,
totalTokens: 0
}
};
}
catch (error) {
console.error("Ollama describeImage error:", error);
return {
description: "Unable to describe this image.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Compare two images and describe differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async compareImages(image1Path, image2Path, prompt) {
try {
const image1Data = fs_1.default.readFileSync(image1Path).toString('base64');
const image2Data = fs_1.default.readFileSync(image2Path).toString('base64');
const response = await this.axiosInstance.post('/api/generate', {
model: this.config.model,
prompt: prompt,
images: [image1Data, image2Data],
stream: false,
options: {
max_tokens: this.config.maxTokens || 300,
temperature: 0.2
}
});
const combinedText = response.data.response || "";
return {
description: combinedText.trim(),
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
catch (error) {
console.error("Ollama compareImages error:", error);
return {
description: "Unable to describe the differences.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch (optional)
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeBatch(imagePaths, lastBatchContext, prompt) {
try {
let userPrompt = prompt;
// If there's context, prepend it. This helps maintain a storyline across batches.
if (lastBatchContext && lastBatchContext.lastDescription) {
userPrompt = `Previous batch summary: ${lastBatchContext.lastDescription}\n\n${prompt}`;
}
// Convert images to base64
const imagesBase64 = imagePaths.map(fp => {
const imageData = fs_1.default.readFileSync(fp);
return imageData.toString('base64');
});
const response = await this.axiosInstance.post('/api/generate', {
model: this.config.model,
prompt: userPrompt,
images: imagesBase64,
stream: false,
options: {
max_tokens: this.config.maxTokens || 300,
temperature: 0.2
}
}, {
timeout: 120000 // Timeout in milliseconds, e.g., 5000 ms = 5 seconds
});
const combinedText = response.data.response || "";
return {
description: combinedText.trim(),
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
catch (error) {
console.error("Ollama describeBatch error:", error);
return {
description: "Unable to describe this batch of images.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
}
exports.OllamaVisionProvider = OllamaVisionProvider;
//# sourceMappingURL=ollamaVisionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ollamaVisionProvider.js","sourceRoot":"","sources":["../../../src/providers/vision/ollamaVisionProvider.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,32 @@
import { VisionProvider, VisionProviderConfig, VisionResult, BatchContext } from '../../interfaces';
/**
* OpenAI Vision Provider Implementation
*/
export declare class OpenAIVisionProvider implements VisionProvider {
private config;
private openai;
constructor(config: VisionProviderConfig);
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeImage(imagePath: string, prompt: string): Promise<VisionResult>;
/**
* Compare two images and describe the differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
compareImages(image1Path: string, image2Path: string, prompt: string): Promise<VisionResult>;
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
describeBatch(imagePaths: string[], lastBatchContext: BatchContext, prompt: string): Promise<VisionResult>;
}

View File

@@ -0,0 +1,182 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIVisionProvider = void 0;
const fs_1 = __importDefault(require("fs"));
const openai_1 = require("openai");
/**
* OpenAI Vision Provider Implementation
*/
class OpenAIVisionProvider {
constructor(config) {
this.config = config;
this.openai = new openai_1.OpenAI({
apiKey: config.apiKey,
});
}
/**
* Describe a single image
* @param imagePath - Path to the image file
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeImage(imagePath, prompt) {
try {
const imageData = fs_1.default.readFileSync(imagePath);
const base64Image = imageData.toString('base64');
const response = await this.openai.chat.completions.create({
model: this.config.model,
temperature: 0.1,
messages: [
{
role: "user",
content: [
{ type: "text", text: prompt },
{
type: "image_url",
image_url: {
url: `data:image/jpeg;base64,${base64Image}`
}
}
]
}
],
max_completion_tokens: this.config.maxTokens || 300
});
return {
description: response.choices[0].message.content?.trim() || "No description generated.",
usage: {
inputTokens: response.usage?.prompt_tokens || 0,
outputTokens: response.usage?.completion_tokens || 0,
totalTokens: response.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error("Error describing image:", error);
return {
description: "Unable to describe this image.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Compare two images and describe the differences
* @param image1Path - Path to the first image
* @param image2Path - Path to the second image
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async compareImages(image1Path, image2Path, prompt) {
try {
const image1Data = fs_1.default.readFileSync(image1Path);
const image2Data = fs_1.default.readFileSync(image2Path);
const base64Image1 = image1Data.toString('base64');
const base64Image2 = image2Data.toString('base64');
const response = await this.openai.chat.completions.create({
model: this.config.model,
messages: [
{
role: "user",
content: [
{ type: "text", text: prompt },
{
type: "image_url",
image_url: {
url: `data:image/jpeg;base64,${base64Image1}`
}
},
{
type: "image_url",
image_url: {
url: `data:image/jpeg;base64,${base64Image2}`
}
}
]
}
],
max_completion_tokens: this.config.maxTokens || 300
});
return {
description: response.choices[0].message.content?.trim() || "No description generated.",
usage: {
inputTokens: response.usage?.prompt_tokens || 0,
outputTokens: response.usage?.completion_tokens || 0,
totalTokens: response.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error("Error comparing images:", error);
return {
description: "Unable to describe the differences between these images.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
/**
* Describe a batch of images
* @param imagePaths - Array of paths to the images
* @param lastBatchContext - Context from the previous batch
* @param prompt - Prompt for the AI
* @returns Description and usage stats
*/
async describeBatch(imagePaths, lastBatchContext, prompt) {
try {
// Convert images to base64
const imagesBase64 = imagePaths.map(fp => {
const imageData = fs_1.default.readFileSync(fp);
return imageData.toString('base64');
});
// Build the messages array for the chat completion
const messages = [
{
role: "user",
content: [
{ type: "text", text: prompt }
]
}
];
// If we have some text context from the last batch, inject that as well
if (lastBatchContext && lastBatchContext.lastDescription) {
messages.unshift({
role: "system",
content: `Previous batch summary: ${lastBatchContext.lastDescription}`
});
}
// Append each image in the new batch
imagesBase64.forEach(base64 => {
messages[messages.length - 1].content.push({
type: "image_url",
image_url: {
url: `data:image/jpeg;base64,${base64}`
}
});
});
const response = await this.openai.chat.completions.create({
model: this.config.model,
messages,
max_completion_tokens: this.config.maxTokens || 300
});
return {
description: response.choices[0].message.content?.trim() || "No description generated.",
usage: {
inputTokens: response.usage?.prompt_tokens || 0,
outputTokens: response.usage?.completion_tokens || 0,
totalTokens: response.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error("Error describing batch of images:", error);
return {
description: "Unable to describe this batch of images.",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
}
exports.OpenAIVisionProvider = OpenAIVisionProvider;
//# sourceMappingURL=openAIVisionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"openAIVisionProvider.js","sourceRoot":"","sources":["../../../src/providers/vision/openAIVisionProvider.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,9 @@
import { VisionProvider, VisionProviderConfig, VisionResult, BatchContext } from '../../interfaces';
export declare class OpenRouterVisionProvider implements VisionProvider {
private config;
private axiosInstance;
constructor(config: VisionProviderConfig);
describeImage(imagePath: string, prompt: string): Promise<VisionResult>;
compareImages(image1Path: string, image2Path: string, prompt: string): Promise<VisionResult>;
describeBatch(imagePaths: string[], lastBatchContext: BatchContext, prompt: string): Promise<VisionResult>;
}

View File

@@ -0,0 +1,161 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenRouterVisionProvider = void 0;
const fs_1 = __importDefault(require("fs"));
const axios_1 = __importDefault(require("axios"));
class OpenRouterVisionProvider {
constructor(config) {
this.config = config;
this.axiosInstance = axios_1.default.create({
baseURL: config.baseUrl || 'https://openrouter.ai/api/v1',
headers: {
'Authorization': `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/anomalyco/aidio-description',
'X-Title': 'Aidio Description Generator'
}
});
}
async describeImage(imagePath, prompt) {
try {
const imageData = fs_1.default.readFileSync(imagePath);
const base64Image = imageData.toString('base64');
const response = await this.axiosInstance.post('/chat/completions', {
model: this.config.model,
temperature: 0.1,
messages: [
{
role: 'user',
content: [
{ type: 'text', text: prompt },
{
type: 'image_url',
image_url: {
url: `data:image/jpeg;base64,${base64Image}`
}
}
]
}
],
max_tokens: this.config.maxTokens || 300
});
const data = response.data;
return {
description: data.choices?.[0]?.message?.content?.trim() || 'No description generated.',
usage: {
inputTokens: data.usage?.prompt_tokens || 0,
outputTokens: data.usage?.completion_tokens || 0,
totalTokens: data.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error('OpenRouter describeImage error:', error.response?.data || error.message);
return {
description: 'Unable to describe this image.',
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
async compareImages(image1Path, image2Path, prompt) {
try {
const image1Data = fs_1.default.readFileSync(image1Path);
const image2Data = fs_1.default.readFileSync(image2Path);
const base64Image1 = image1Data.toString('base64');
const base64Image2 = image2Data.toString('base64');
const response = await this.axiosInstance.post('/chat/completions', {
model: this.config.model,
temperature: 0.1,
messages: [
{
role: 'user',
content: [
{ type: 'text', text: prompt },
{
type: 'image_url',
image_url: { url: `data:image/jpeg;base64,${base64Image1}` }
},
{
type: 'image_url',
image_url: { url: `data:image/jpeg;base64,${base64Image2}` }
}
]
}
],
max_tokens: this.config.maxTokens || 300
});
const data = response.data;
return {
description: data.choices?.[0]?.message?.content?.trim() || 'No description generated.',
usage: {
inputTokens: data.usage?.prompt_tokens || 0,
outputTokens: data.usage?.completion_tokens || 0,
totalTokens: data.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error('OpenRouter compareImages error:', error.response?.data || error.message);
return {
description: 'Unable to describe the differences between these images.',
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
async describeBatch(imagePaths, lastBatchContext, prompt) {
try {
const imagesBase64 = imagePaths.map(fp => {
const imageData = fs_1.default.readFileSync(fp);
return imageData.toString('base64');
});
const messages = [
{
role: 'user',
content: [
{ type: 'text', text: prompt }
]
}
];
if (lastBatchContext && lastBatchContext.lastDescription) {
messages.unshift({
role: 'system',
content: `Previous batch summary: ${lastBatchContext.lastDescription}`
});
}
imagesBase64.forEach(base64 => {
messages[messages.length - 1].content.push({
type: 'image_url',
image_url: {
url: `data:image/jpeg;base64,${base64}`
}
});
});
const response = await this.axiosInstance.post('/chat/completions', {
model: this.config.model,
messages,
max_tokens: this.config.maxTokens || 300
});
const data = response.data;
return {
description: data.choices?.[0]?.message?.content?.trim() || 'No description generated.',
usage: {
inputTokens: data.usage?.prompt_tokens || 0,
outputTokens: data.usage?.completion_tokens || 0,
totalTokens: data.usage?.total_tokens || 0
}
};
}
catch (error) {
console.error('OpenRouter describeBatch error:', error.response?.data || error.message);
return {
description: 'Unable to describe this batch of images.',
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
}
}
}
exports.OpenRouterVisionProvider = OpenRouterVisionProvider;
//# sourceMappingURL=openRouterVisionProvider.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"openRouterVisionProvider.js","sourceRoot":"","sources":["../../../src/providers/vision/openRouterVisionProvider.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,8 @@
import { VisionProvider } from '../../interfaces';
import { Config } from '../../config/config';
/**
* Factory for creating vision AI providers
*/
export declare class VisionProviderFactory {
static getProvider(config: Config): VisionProvider;
}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VisionProviderFactory = void 0;
const openAIVisionProvider_1 = require("./openAIVisionProvider");
const geminiVisionProvider_1 = require("./geminiVisionProvider");
const ollamaVisionProvider_1 = require("./ollamaVisionProvider");
const openRouterVisionProvider_1 = require("./openRouterVisionProvider");
/**
* Factory for creating vision AI providers
*/
class VisionProviderFactory {
static getProvider(config) {
const providerName = config.visionProvider;
const providerConfig = config.visionProviders[providerName];
if (!providerConfig) {
throw new Error(`Vision provider "${providerName}" not configured.`);
}
switch (providerName) {
case 'openai':
return new openAIVisionProvider_1.OpenAIVisionProvider(providerConfig);
case 'gemini':
return new geminiVisionProvider_1.GeminiVisionProvider(providerConfig);
case "ollama":
return new ollamaVisionProvider_1.OllamaVisionProvider(providerConfig);
case 'openrouter':
return new openRouterVisionProvider_1.OpenRouterVisionProvider(providerConfig);
// Add other providers here
default:
throw new Error(`Vision provider "${providerName}" not implemented.`);
}
}
}
exports.VisionProviderFactory = VisionProviderFactory;
//# sourceMappingURL=visionProviderFactory.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"visionProviderFactory.js","sourceRoot":"","sources":["../../../src/providers/vision/visionProviderFactory.ts"],"names":[],"mappings":";;;AAEA,iEAA8D;AAC9D,iEAA8D;AAC9D,iEAA8D;AAC9D,yEAAsE;AAEtE;;GAEG;AACH,MAAa,qBAAqB;IAChC,MAAM,CAAC,WAAW,CAAC,MAAc;QAC/B,MAAM,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC;QAC3C,MAAM,cAAc,GAAG,MAAM,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;QAE5D,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,oBAAoB,YAAY,mBAAmB,CAAC,CAAC;QACvE,CAAC;QAED,QAAQ,YAAY,EAAE,CAAC;YACrB,KAAK,QAAQ;gBACX,OAAO,IAAI,2CAAoB,CAAC,cAAc,CAAC,CAAC;YAClD,KAAK,QAAQ;gBACX,OAAO,IAAI,2CAAoB,CAAC,cAAc,CAAC,CAAC;YAClD,KAAK,QAAQ;gBACX,OAAO,IAAI,2CAAoB,CAAC,cAAc,CAAC,CAAC;YAClD,KAAK,YAAY;gBACf,OAAO,IAAI,mDAAwB,CAAC,cAAc,CAAC,CAAC;YACtD,2BAA2B;YAC3B;gBACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,YAAY,oBAAoB,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;CACF;AAvBD,sDAuBC"}