diff --git a/open-sse/config/moderationRegistry.ts b/open-sse/config/moderationRegistry.ts index 1321acad4a..c63d9e17b4 100644 --- a/open-sse/config/moderationRegistry.ts +++ b/open-sse/config/moderationRegistry.ts @@ -5,7 +5,25 @@ * Follows OpenAI's moderation API format. */ -export const MODERATION_PROVIDERS = { +export interface ModerationModel { + id: string; + name: string; +} + +export interface ModerationProvider { + id: string; + baseUrl: string; + authType: string; + authHeader: string; + models: ModerationModel[]; +} + +export interface ParsedModerationModel { + provider: string | null; + model: string | null; +} + +export const MODERATION_PROVIDERS: Record = { openai: { id: "openai", baseUrl: "https://api.openai.com/v1/moderations", @@ -26,19 +44,19 @@ export const MODERATION_PROVIDERS = { }; /** - * Get moderation provider config by ID + * Get moderation provider config by ID. */ -export function getModerationProvider(providerId) { +export function getModerationProvider(providerId: string): ModerationProvider | null { return MODERATION_PROVIDERS[providerId] || null; } /** - * Parse moderation model string + * Parse a moderation model string. */ -export function parseModerationModel(modelStr) { +export function parseModerationModel(modelStr: string | null | undefined): ParsedModerationModel { if (!modelStr) return { provider: null, model: null }; - for (const [providerId, config] of Object.entries(MODERATION_PROVIDERS)) { + for (const providerId of Object.keys(MODERATION_PROVIDERS)) { if (modelStr.startsWith(providerId + "/")) { return { provider: providerId, model: modelStr.slice(providerId.length + 1) }; } @@ -54,10 +72,10 @@ export function parseModerationModel(modelStr) { } /** - * Get all moderation models as a flat list + * Get all moderation models as a flat list. */ -export function getAllModerationModels() { - const models = []; +export function getAllModerationModels(): Array<{ id: string; name: string; provider: string }> { + const models: Array<{ id: string; name: string; provider: string }> = []; for (const [providerId, config] of Object.entries(MODERATION_PROVIDERS)) { for (const model of config.models) { models.push({ diff --git a/open-sse/config/ocrRegistry.ts b/open-sse/config/ocrRegistry.ts index 7b637aa39a..fdf47d44f1 100644 --- a/open-sse/config/ocrRegistry.ts +++ b/open-sse/config/ocrRegistry.ts @@ -5,7 +5,25 @@ * Follows Mistral's OCR API format. */ -export const OCR_PROVIDERS = { +export interface OcrModel { + id: string; + name: string; +} + +export interface OcrProvider { + id: string; + baseUrl: string; + authType: string; + authHeader: string; + models: OcrModel[]; +} + +export interface ParsedOcrModel { + provider: string | null; + model: string | null; +} + +export const OCR_PROVIDERS: Record = { mistral: { id: "mistral", baseUrl: "https://api.mistral.ai/v1/ocr", @@ -16,22 +34,22 @@ export const OCR_PROVIDERS = { }; /** - * Get OCR provider config by ID + * Get OCR provider config by ID. */ -export function getOcrProvider(providerId) { +export function getOcrProvider(providerId: string): OcrProvider | null { return OCR_PROVIDERS[providerId] || null; } /** - * Parse OCR model string + * Parse an OCR model string. * * Accepts either a "provider/model" prefixed string or a bare model id that * matches one of the registered OCR models. */ -export function parseOcrModel(modelStr) { +export function parseOcrModel(modelStr: string | null | undefined): ParsedOcrModel { if (!modelStr) return { provider: null, model: null }; - for (const [providerId] of Object.entries(OCR_PROVIDERS)) { + for (const providerId of Object.keys(OCR_PROVIDERS)) { if (modelStr.startsWith(providerId + "/")) { return { provider: providerId, model: modelStr.slice(providerId.length + 1) }; } @@ -47,10 +65,10 @@ export function parseOcrModel(modelStr) { } /** - * Get all OCR models as a flat list + * Get all OCR models as a flat list. */ -export function getAllOcrModels() { - const models = []; +export function getAllOcrModels(): Array<{ id: string; name: string; provider: string }> { + const models: Array<{ id: string; name: string; provider: string }> = []; for (const [providerId, config] of Object.entries(OCR_PROVIDERS)) { for (const model of config.models) { models.push({ diff --git a/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/MediaProviderPageClient.tsx b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/MediaProviderPageClient.tsx index 9ca2963b66..c1eed94d7c 100644 --- a/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/MediaProviderPageClient.tsx +++ b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/MediaProviderPageClient.tsx @@ -14,6 +14,7 @@ import { WebSearchExampleCard } from "../../components/WebSearchExampleCard"; import { WebFetchExampleCard } from "../../components/WebFetchExampleCard"; import { VideoExampleCard } from "../../components/VideoExampleCard"; import { MusicExampleCard } from "../../components/MusicExampleCard"; +import { OcrExampleCard } from "../../components/OcrExampleCard"; interface Connection { id: string; @@ -53,6 +54,8 @@ function renderPlayground(kind: MediaKind, providerId: string) { return ; case "music": return ; + case "ocr": + return ; case "imageToText": // Endpoint /api/v1/images/understanding does not exist yet — omitted. return ( diff --git a/src/app/(dashboard)/dashboard/media-providers/components/OcrExampleCard.tsx b/src/app/(dashboard)/dashboard/media-providers/components/OcrExampleCard.tsx new file mode 100644 index 0000000000..f5a675b034 --- /dev/null +++ b/src/app/(dashboard)/dashboard/media-providers/components/OcrExampleCard.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { useApiKey } from "../../providers/hooks/useApiKey"; +import { useProviderModels } from "../../providers/hooks/useProviderModels"; +import { buildCurl } from "../../providers/utils/buildCurl"; +import { PlaygroundCard } from "./PlaygroundCard"; + +interface Props { + providerId: string; +} + +const ENDPOINT_PATH = "/api/v1/ocr"; +const SAMPLE_DOCUMENT_URL = "https://arxiv.org/pdf/2201.04234"; + +function extractError(data: unknown): string | null { + if (!data || typeof data !== "object") return null; + const d = data as Record; + const err = d.error as Record | undefined; + if (err?.message) return String(err.message); + if (typeof d.message === "string") return d.message; + return null; +} + +export function OcrExampleCard({ providerId }: Props) { + const t = useTranslations("miniPlayground"); + const { apiKey } = useApiKey(); + const { models } = useProviderModels(providerId); + + const firstModel = models[0]?.id ?? ""; + const [model, setModel] = useState(""); + const [documentUrl, setDocumentUrl] = useState(SAMPLE_DOCUMENT_URL); + const [running, setRunning] = useState(false); + const [result, setResult] = useState<{ data: unknown; latencyMs: number } | undefined>(); + const [error, setError] = useState(null); + + const effectiveModel = model || firstModel; + + const buildBody = () => ({ + model: effectiveModel, + document: { type: "document_url", document_url: documentUrl }, + }); + + const curlSnippet = buildCurl({ + endpoint: + (typeof window !== "undefined" ? window.location.origin : "http://localhost:20128") + + ENDPOINT_PATH, + headers: { + Authorization: `Bearer ${apiKey || ""}`, + "Content-Type": "application/json", + }, + body: buildBody(), + }); + + const handleRun = async () => { + setRunning(true); + setError(null); + setResult(undefined); + const t0 = performance.now(); + try { + const res = await fetch(ENDPOINT_PATH, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + "x-connection-id": providerId, + }, + body: JSON.stringify(buildBody()), + }); + const data: unknown = await res.json(); + const latencyMs = performance.now() - t0; + const errMsg = extractError(data); + if (!res.ok || errMsg) { + setError(errMsg ?? `HTTP ${res.status}`); + } else { + setResult({ data, latencyMs }); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Request failed"); + } finally { + setRunning(false); + } + }; + + const modelOptions = models.length > 0 ? models : [{ id: "mistral-ocr-latest" }]; + + return ( + + {/* Model select */} +
+ + +
+ {/* Document URL */} +
+ + setDocumentUrl(e.target.value)} + placeholder={SAMPLE_DOCUMENT_URL} + className="w-full rounded-md border border-border bg-bg-subtle text-sm px-2 py-1.5 text-text-main focus:outline-none focus:ring-1 focus:ring-primary" + /> +
+
+ ); +}