"use client"; import { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { Modal, Input, Button, Badge } from "@/shared/components"; const DEFAULT_BASE_URL = "https://api.openai.com/v1"; // Dual-mode modal: edit when `node` provided, add otherwise export default function AddCustomEmbeddingModal({ isOpen, onClose, onCreated, onSaved, node }) { const isEdit = !!node; const [formData, setFormData] = useState({ name: "", prefix: "", baseUrl: DEFAULT_BASE_URL, }); const [submitting, setSubmitting] = useState(false); const [checkKey, setCheckKey] = useState(""); const [checkModelId, setCheckModelId] = useState(""); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); useEffect(() => { if (!isOpen) return; setValidationResult(null); setCheckKey(""); setCheckModelId(""); if (isEdit) { setFormData({ name: node.name || "", prefix: node.prefix || "", baseUrl: node.baseUrl || DEFAULT_BASE_URL, }); } else { setFormData({ name: "", prefix: "", baseUrl: DEFAULT_BASE_URL }); } }, [isOpen, isEdit, node]); const handleSubmit = async () => { if (!formData.name.trim() || !formData.prefix.trim() || !formData.baseUrl.trim()) return; setSubmitting(true); try { const url = isEdit ? `/api/provider-nodes/${node.id}` : "/api/provider-nodes"; const method = isEdit ? "PUT" : "POST"; const payload = { name: formData.name, prefix: formData.prefix, baseUrl: formData.baseUrl, }; if (!isEdit) payload.type = "custom-embedding"; const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await res.json(); if (res.ok) { if (isEdit) onSaved?.(data.node); else onCreated?.(data.node); } } catch (error) { console.log("Error saving custom embedding node:", error); } finally { setSubmitting(false); } }; const handleValidate = async () => { setValidating(true); try { const res = await fetch("/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: formData.baseUrl, apiKey: checkKey, type: "custom-embedding", modelId: checkModelId.trim() || undefined, }), }); const data = await res.json(); setValidationResult(data); } catch { setValidationResult({ valid: false, error: "Network error" }); } finally { setValidating(false); } }; const renderValidationResult = () => { if (!validationResult) return null; const { valid, error, dimensions } = validationResult; if (valid) { return ( <> Valid {dimensions && {dimensions} dims} ); } return (
Invalid {error && {error}}
); }; return (
setFormData({ ...formData, name: e.target.value })} placeholder="Voyage AI" hint="Required. A friendly label for this embedding provider." /> setFormData({ ...formData, prefix: e.target.value })} placeholder="voyage" hint="Required. Used as the provider prefix for model IDs (e.g. voyage/voyage-3)." /> setFormData({ ...formData, baseUrl: e.target.value })} placeholder="https://api.voyageai.com/v1" hint="Most embedding APIs are OpenAI-compatible: Voyage, Cohere, Jina, Mistral, Together..." /> setCheckKey(e.target.value)} /> setCheckModelId(e.target.value)} placeholder="e.g. voyage-3, embed-english-v3.0, text-embedding-3-small" hint="Required for validation. Will send a test embeddings request." />
{renderValidationResult()}
); } AddCustomEmbeddingModal.propTypes = { isOpen: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, onCreated: PropTypes.func, onSaved: PropTypes.func, node: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, prefix: PropTypes.string, baseUrl: PropTypes.string, }), };