mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 02:14:39 +00:00
feat(embedding): data-driven dimension config with multi-provider support
- Add `dimensions` field to EmbeddingSettings for per-provider truncation - Default to 1536 dims to match pgvector schema, preventing INSERT failures - Replace hardcoded Gemini-only dimension truncation with data-driven config - Fix verify endpoint to apply dimensions and return mismatch warning - Expand curated embedding models: Gemini (gemini-embedding-001), Cohere (embed-v4), DashScope (text-embedding-v3), OpenAI (text-embedding-3-large) - Add dimensions input to provider detail UI with i18n (en/vi/zh) - Add anthropic_native to NoEmbeddingTypes (incompatible auth format) - System settings verify always requests 1536d to match production behavior
This commit is contained in:
1 parent
fabec18606
commit
28a17c4d6b
13 files changed
+144
-47
No files matched your search
@@ -123,8 +123,12 @@ func buildEmbeddingProvider(
|
||||
apiBase = memCfg.EmbeddingAPIBase
|
||||
}
|
||||
|
||||
// Gemini requires dimension truncation to match pgvector(1536) index.
|
||||
needsDimTruncate := dbp.ProviderType == store.ProviderGeminiNative
|
||||
// Dimension truncation: provider settings override, default to 1536 to match pgvector schema.
|
||||
// Models that natively output 1536 ignore the parameter; models with larger native dims get truncated.
|
||||
dims := 1536
|
||||
if es != nil && es.Dimensions > 0 {
|
||||
dims = es.Dimensions
|
||||
}
|
||||
|
||||
// Try registry first for the actual API key / base (handles runtime-registered providers)
|
||||
if providerReg != nil {
|
||||
@@ -134,9 +138,7 @@ func buildEmbeddingProvider(
|
||||
apiBase = op.APIBase()
|
||||
}
|
||||
ep := memory.NewOpenAIEmbeddingProvider(dbp.Name, op.APIKey(), apiBase, model)
|
||||
if needsDimTruncate {
|
||||
ep.WithDimensions(1536)
|
||||
}
|
||||
ep.WithDimensions(dims)
|
||||
return ep
|
||||
}
|
||||
slog.Debug("embedding provider in registry is not OpenAI-compatible, using DB record", "name", dbp.Name)
|
||||
@@ -146,9 +148,7 @@ func buildEmbeddingProvider(
|
||||
// Fallback: build directly from DB record
|
||||
if dbp.APIKey != "" {
|
||||
ep := memory.NewOpenAIEmbeddingProvider(dbp.Name, dbp.APIKey, apiBase, model)
|
||||
if needsDimTruncate {
|
||||
ep.WithDimensions(1536)
|
||||
}
|
||||
ep.WithDimensions(dims)
|
||||
return ep
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ func (h *ProvidersHandler) handleVerifyEmbedding(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Model string `json:"model"`
|
||||
Model string `json:"model"`
|
||||
Dimensions int `json:"dimensions"` // optional: truncate output to N dims
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<16)).Decode(&req); err != nil && err.Error() != "EOF" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
|
||||
@@ -45,12 +46,13 @@ func (h *ProvidersHandler) handleVerifyEmbedding(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
// Parse embedding settings once for model/apiBase/dimensions resolution.
|
||||
es := store.ParseEmbeddingSettings(p.Settings)
|
||||
|
||||
// Resolve model: request body → settings.embedding.model → default
|
||||
model := req.Model
|
||||
if model == "" {
|
||||
if es := store.ParseEmbeddingSettings(p.Settings); es != nil && es.Model != "" {
|
||||
model = es.Model
|
||||
}
|
||||
if model == "" && es != nil && es.Model != "" {
|
||||
model = es.Model
|
||||
}
|
||||
if model == "" {
|
||||
model = "text-embedding-3-small"
|
||||
@@ -58,12 +60,22 @@ func (h *ProvidersHandler) handleVerifyEmbedding(w http.ResponseWriter, r *http.
|
||||
|
||||
// Resolve API base: settings.embedding.api_base → provider api_base → resolved base
|
||||
apiBase := h.resolveAPIBase(p)
|
||||
if es := store.ParseEmbeddingSettings(p.Settings); es != nil && es.APIBase != "" {
|
||||
if es != nil && es.APIBase != "" {
|
||||
apiBase = es.APIBase
|
||||
}
|
||||
|
||||
ep := memory.NewOpenAIEmbeddingProvider(p.Name, p.APIKey, apiBase, model)
|
||||
|
||||
// Apply dimension truncation: request body → provider settings → none.
|
||||
// Clamp to reasonable range to avoid sending absurd values upstream.
|
||||
truncDims := req.Dimensions
|
||||
if truncDims <= 0 && es != nil && es.Dimensions > 0 {
|
||||
truncDims = es.Dimensions
|
||||
}
|
||||
if truncDims > 0 && truncDims <= 8192 {
|
||||
ep.WithDimensions(truncDims)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -77,7 +89,11 @@ func (h *ProvidersHandler) handleVerifyEmbedding(w http.ResponseWriter, r *http.
|
||||
if len(vectors) > 0 && len(vectors[0]) > 0 {
|
||||
dims = len(vectors[0])
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"valid": true, "dimensions": dims})
|
||||
result := map[string]any{"valid": true, "dimensions": dims}
|
||||
if dims > 0 && dims != 1536 {
|
||||
result["dimension_mismatch"] = true
|
||||
}
|
||||
writeJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
// handleEmbeddingStatus returns the current embedding system configuration.
|
||||
|
||||
@@ -74,9 +74,10 @@ type LLMProviderData struct {
|
||||
|
||||
// EmbeddingSettings holds embedding-specific configuration stored in provider settings JSONB.
|
||||
type EmbeddingSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Model string `json:"model,omitempty"` // e.g. "text-embedding-3-small"
|
||||
APIBase string `json:"api_base,omitempty"` // override if embedding endpoint differs from chat
|
||||
Enabled bool `json:"enabled"`
|
||||
Model string `json:"model,omitempty"` // e.g. "text-embedding-3-small"
|
||||
APIBase string `json:"api_base,omitempty"` // override if embedding endpoint differs from chat
|
||||
Dimensions int `json:"dimensions,omitempty"` // truncate output to N dims (e.g. 1536); 0 = model default
|
||||
}
|
||||
|
||||
// ParseEmbeddingSettings extracts embedding config from a provider's settings JSONB.
|
||||
@@ -96,10 +97,11 @@ func ParseEmbeddingSettings(settings json.RawMessage) *EmbeddingSettings {
|
||||
|
||||
// NoEmbeddingTypes lists provider types that cannot serve embeddings.
|
||||
var NoEmbeddingTypes = map[string]bool{
|
||||
ProviderACP: true,
|
||||
ProviderClaudeCLI: true,
|
||||
ProviderChatGPTOAuth: true,
|
||||
ProviderSuno: true,
|
||||
ProviderAnthropicNative: true, // uses x-api-key auth, not Bearer; no embedding models
|
||||
ProviderACP: true,
|
||||
ProviderClaudeCLI: true,
|
||||
ProviderChatGPTOAuth: true,
|
||||
ProviderSuno: true,
|
||||
}
|
||||
|
||||
// ProviderStore manages LLM providers.
|
||||
|
||||
@@ -27,16 +27,30 @@ const EMBEDDING_MODELS: Record<string, { id: string; name: string }[]> = {
|
||||
// OpenAI — native 1536d
|
||||
openai_compat: [
|
||||
{ id: "text-embedding-3-small", name: "text-embedding-3-small (1536d)" },
|
||||
{ id: "text-embedding-3-large", name: "text-embedding-3-large (3072d → 1536 via dimensions)" },
|
||||
{ id: "text-embedding-ada-002", name: "text-embedding-ada-002 (1536d)" },
|
||||
],
|
||||
// OpenRouter — proxied OpenAI models
|
||||
openrouter: [
|
||||
{ id: "openai/text-embedding-3-small", name: "openai/text-embedding-3-small (1536d)" },
|
||||
{ id: "openai/text-embedding-3-large", name: "openai/text-embedding-3-large (3072d → 1536)" },
|
||||
{ id: "openai/text-embedding-ada-002", name: "openai/text-embedding-ada-002 (1536d)" },
|
||||
],
|
||||
// Mistral — mistral-embed native 1024d, codestral-embed native 1536d (MRL)
|
||||
// Gemini — gemini-embedding-001 (3072d native, truncate to 1536 via dimensions param)
|
||||
gemini_native: [
|
||||
{ id: "gemini-embedding-001", name: "gemini-embedding-001 (3072d → 1536 via dimensions)" },
|
||||
],
|
||||
// Mistral — codestral-embed defaults to 1536d (MRL)
|
||||
mistral: [
|
||||
{ id: "codestral-embed", name: "codestral-embed (1536d)" },
|
||||
{ id: "codestral-embed", name: "codestral-embed (1536d default)" },
|
||||
],
|
||||
// DashScope/Qwen — text-embedding-v3 (custom dimensions support)
|
||||
dashscope: [
|
||||
{ id: "text-embedding-v3", name: "text-embedding-v3 (1536 via dimensions)" },
|
||||
],
|
||||
// Cohere — embed-v4 native 1536d
|
||||
cohere: [
|
||||
{ id: "embed-v4", name: "embed-v4 (1536d native)" },
|
||||
],
|
||||
};
|
||||
// Fallback for unlisted provider types — no curated models
|
||||
@@ -148,7 +162,8 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
|
||||
|
||||
const handleVerifyEmb = () => {
|
||||
if (!selectedEmbProviderData) return;
|
||||
verifyEmbedding(selectedEmbProviderData.id, embModel.trim() || undefined);
|
||||
// Always request 1536 dims — pgvector schema requires vector(1536).
|
||||
verifyEmbedding(selectedEmbProviderData.id, embModel.trim() || undefined, 1536);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
@@ -275,11 +290,23 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
|
||||
{t("embedding.verify")}
|
||||
</Button>
|
||||
{embResult && (
|
||||
<span className={`flex items-center gap-1 text-xs ${embResult.valid ? "text-emerald-600 dark:text-emerald-400" : "text-destructive"}`}>
|
||||
<span className={`flex items-center gap-1 text-xs ${
|
||||
embResult.valid
|
||||
? embResult.dimension_mismatch
|
||||
? "text-amber-600 dark:text-amber-400"
|
||||
: "text-emerald-600 dark:text-emerald-400"
|
||||
: "text-destructive"
|
||||
}`}>
|
||||
{embResult.valid ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
{t("embedding.dimensions", { count: embResult.dimensions })}
|
||||
{embResult.dimension_mismatch ? (
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{embResult.dimension_mismatch
|
||||
? t("embedding.dimensionsMismatch", { count: embResult.dimensions })
|
||||
: t("embedding.dimensions", { count: embResult.dimensions })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -158,11 +158,14 @@
|
||||
"enable": "Enable Embedding",
|
||||
"enableDesc": "Allow this provider to generate text embeddings for memory, skills, and knowledge graph",
|
||||
"model": "Embedding Model",
|
||||
"dimensions": "Output Dimensions",
|
||||
"dimensionsHint": "Truncate output to N dimensions (e.g. 1536). Required for models with native dims > 1536 like gemini-embedding-001 (3072d).",
|
||||
"apiBase": "Embedding API Base",
|
||||
"apiBasePlaceholder": "(same as provider)",
|
||||
"apiBaseHint": "Override if embedding endpoint differs from the provider's main API base",
|
||||
"verify": "Verify Embedding",
|
||||
"verifyFailed": "Embedding verification failed"
|
||||
"verifyFailed": "Embedding verification failed",
|
||||
"dimensionsMismatch": "{{count}} dimensions — does not match required 1536"
|
||||
},
|
||||
"toast": {
|
||||
"created": "Provider created",
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
"modelTip": "Embedding model (e.g. text-embedding-3-small). Must support /embeddings endpoint.",
|
||||
"providerPlaceholder": "Select provider",
|
||||
"modelPlaceholder": "text-embedding-3-small",
|
||||
"supportedProviders": "Verified 1536d: OpenAI (text-embedding-3-small), OpenRouter (openai/*), Mistral (codestral-embed). Other providers may work if they support /embeddings with 1536d output.",
|
||||
"supportedProviders": "Verified 1536d: OpenAI (text-embedding-3-small/large), Gemini (gemini-embedding-001 + dimensions), Mistral (codestral-embed), Cohere (embed-v4), DashScope (text-embedding-v3 + dimensions). Configure dimensions in provider settings for models with native dims > 1536.",
|
||||
"verify": "Verify Embedding",
|
||||
"verifyRequired": "Verify the embedding model before saving.",
|
||||
"verifyFailed": "Verification failed",
|
||||
"dimensions": "{{count}} dimensions"
|
||||
"dimensions": "{{count}} dimensions",
|
||||
"dimensionsMismatch": "{{count}} dimensions — does not match required 1536"
|
||||
},
|
||||
"ux": {
|
||||
"title": "UX Behavior",
|
||||
|
||||
@@ -158,11 +158,14 @@
|
||||
"enable": "Bật Embedding",
|
||||
"enableDesc": "Cho phép provider này tạo embedding cho bộ nhớ, kỹ năng và đồ thị tri thức",
|
||||
"model": "Model Embedding",
|
||||
"dimensions": "Số chiều đầu ra",
|
||||
"dimensionsHint": "Cắt ngắn đầu ra về N chiều (VD: 1536). Bắt buộc với model có chiều gốc > 1536 như gemini-embedding-001 (3072d).",
|
||||
"apiBase": "API Base Embedding",
|
||||
"apiBasePlaceholder": "(giống provider)",
|
||||
"apiBaseHint": "Ghi đè nếu endpoint embedding khác với API base chính của provider",
|
||||
"verify": "Kiểm tra Embedding",
|
||||
"verifyFailed": "Kiểm tra embedding thất bại"
|
||||
"verifyFailed": "Kiểm tra embedding thất bại",
|
||||
"dimensionsMismatch": "{{count}} chiều — không khớp yêu cầu 1536"
|
||||
},
|
||||
"toast": {
|
||||
"created": "Đã tạo provider",
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
"modelTip": "Model embedding (vd: text-embedding-3-small). Phải hỗ trợ endpoint /embeddings.",
|
||||
"providerPlaceholder": "Chọn provider",
|
||||
"modelPlaceholder": "text-embedding-3-small",
|
||||
"supportedProviders": "Đã xác minh 1536d: OpenAI (text-embedding-3-small), OpenRouter (openai/*), Mistral (codestral-embed). Các provider khác có thể hoạt động nếu hỗ trợ /embeddings với output 1536d.",
|
||||
"supportedProviders": "Đã xác minh 1536d: OpenAI (text-embedding-3-small/large), Gemini (gemini-embedding-001 + dimensions), Mistral (codestral-embed), Cohere (embed-v4), DashScope (text-embedding-v3 + dimensions). Cấu hình dimensions trong provider settings cho model có chiều gốc > 1536.",
|
||||
"verify": "Kiểm tra Embedding",
|
||||
"verifyRequired": "Kiểm tra model embedding trước khi lưu.",
|
||||
"verifyFailed": "Kiểm tra thất bại",
|
||||
"dimensions": "{{count}} chiều"
|
||||
"dimensions": "{{count}} chiều",
|
||||
"dimensionsMismatch": "{{count}} chiều — không khớp yêu cầu 1536"
|
||||
},
|
||||
"ux": {
|
||||
"title": "Hành vi UX",
|
||||
|
||||
@@ -158,11 +158,14 @@
|
||||
"enable": "启用Embedding",
|
||||
"enableDesc": "允许此Provider为记忆、技能和知识图谱生成文本嵌入",
|
||||
"model": "Embedding模型",
|
||||
"dimensions": "输出维度",
|
||||
"dimensionsHint": "将输出截断为N维(如1536)。对于原生维度 > 1536的模型(如gemini-embedding-001 3072d)必须设置。",
|
||||
"apiBase": "Embedding API地址",
|
||||
"apiBasePlaceholder": "(与Provider相同)",
|
||||
"apiBaseHint": "如果Embedding端点与Provider的主API地址不同,可在此覆盖",
|
||||
"verify": "验证Embedding",
|
||||
"verifyFailed": "Embedding验证失败"
|
||||
"verifyFailed": "Embedding验证失败",
|
||||
"dimensionsMismatch": "{{count}}维 — 不符合要求的1536"
|
||||
},
|
||||
"toast": {
|
||||
"created": "Provider已创建",
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
"modelTip": "Embedding模型(如text-embedding-3-small)。必须支持/embeddings端点。",
|
||||
"providerPlaceholder": "选择Provider",
|
||||
"modelPlaceholder": "text-embedding-3-small",
|
||||
"supportedProviders": "已验证1536d:OpenAI (text-embedding-3-small)、OpenRouter (openai/*)、Mistral (codestral-embed)。其他Provider如支持/embeddings并输出1536d也可使用。",
|
||||
"supportedProviders": "已验证1536d:OpenAI (text-embedding-3-small/large)、Gemini (gemini-embedding-001 + dimensions)、Mistral (codestral-embed)、Cohere (embed-v4)、DashScope (text-embedding-v3 + dimensions)。原生维度 > 1536的模型需在Provider设置中配置dimensions。",
|
||||
"verify": "验证Embedding",
|
||||
"verifyRequired": "保存前请先验证Embedding模型。",
|
||||
"verifyFailed": "验证失败",
|
||||
"dimensions": "{{count}}维"
|
||||
"dimensions": "{{count}}维",
|
||||
"dimensionsMismatch": "{{count}}维 — 不符合要求的1536"
|
||||
},
|
||||
"ux": {
|
||||
"title": "UX行为",
|
||||
|
||||
@@ -5,6 +5,7 @@ interface VerifyResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
dimensions?: number;
|
||||
dimension_mismatch?: boolean; // true when output dims != 1536
|
||||
}
|
||||
|
||||
export function useProviderVerify() {
|
||||
@@ -44,13 +45,16 @@ export function useProviderVerify() {
|
||||
const [embResult, setEmbResult] = useState<VerifyResult | null>(null);
|
||||
|
||||
const verifyEmbedding = useCallback(
|
||||
async (providerId: string, model?: string) => {
|
||||
async (providerId: string, model?: string, dimensions?: number) => {
|
||||
setEmbVerifying(true);
|
||||
setEmbResult(null);
|
||||
try {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (model) body.model = model;
|
||||
if (dimensions && dimensions > 0) body.dimensions = dimensions;
|
||||
const res = await http.post<VerifyResult>(
|
||||
`/v1/providers/${providerId}/verify-embedding`,
|
||||
model ? { model } : {},
|
||||
body,
|
||||
);
|
||||
setEmbResult(res);
|
||||
return res;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Copy, Loader2, CheckCircle2, XCircle } from "lucide-react";
|
||||
import { Copy, Loader2, CheckCircle2, XCircle, AlertTriangle } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
@@ -19,7 +19,7 @@ interface ProviderOverviewProps {
|
||||
}
|
||||
|
||||
const NO_API_KEY_TYPES = new Set(["claude_cli", "acp", "chatgpt_oauth"]);
|
||||
const NO_EMBEDDING_TYPES = new Set(["claude_cli", "acp", "chatgpt_oauth", "suno"]);
|
||||
const NO_EMBEDDING_TYPES = new Set(["claude_cli", "acp", "chatgpt_oauth", "suno", "anthropic_native"]);
|
||||
|
||||
export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps) {
|
||||
const { t } = useTranslation("providers");
|
||||
@@ -44,6 +44,7 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
const [embEnabled, setEmbEnabled] = useState(initEmb?.enabled ?? false);
|
||||
const [embModel, setEmbModel] = useState(initEmb?.model ?? "");
|
||||
const [embApiBase, setEmbApiBase] = useState(initEmb?.api_base ?? "");
|
||||
const [embDimensions, setEmbDimensions] = useState(initEmb?.dimensions ? String(initEmb.dimensions) : "");
|
||||
|
||||
// Re-sync embedding state when provider changes (e.g. after save)
|
||||
useEffect(() => {
|
||||
@@ -51,11 +52,12 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
setEmbEnabled(es?.enabled ?? false);
|
||||
setEmbModel(es?.model ?? "");
|
||||
setEmbApiBase(es?.api_base ?? "");
|
||||
setEmbDimensions(es?.dimensions ? String(es.dimensions) : "");
|
||||
}, [provider.settings]);
|
||||
|
||||
// Verify embedding
|
||||
const { verifyEmbedding, embVerifying, embResult, resetEmb } = useProviderVerify();
|
||||
useEffect(() => { resetEmb(); }, [embModel, resetEmb]);
|
||||
useEffect(() => { resetEmb(); }, [embModel, embDimensions, resetEmb]);
|
||||
|
||||
// Save state
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -76,10 +78,16 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
// Merge embedding settings into existing settings
|
||||
if (showEmbedding) {
|
||||
const existing = (provider.settings || {}) as Record<string, unknown>;
|
||||
const parsedDims = embDimensions ? parseInt(embDimensions, 10) : 0;
|
||||
data.settings = {
|
||||
...existing,
|
||||
embedding: embEnabled
|
||||
? { enabled: true, model: embModel.trim() || undefined, api_base: embApiBase.trim() || undefined }
|
||||
? {
|
||||
enabled: true,
|
||||
model: embModel.trim() || undefined,
|
||||
api_base: embApiBase.trim() || undefined,
|
||||
dimensions: parsedDims > 0 ? parsedDims : undefined,
|
||||
}
|
||||
: { enabled: false },
|
||||
};
|
||||
}
|
||||
@@ -97,7 +105,8 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
};
|
||||
|
||||
const handleVerifyEmbedding = () => {
|
||||
verifyEmbedding(provider.id, embModel.trim() || undefined);
|
||||
const parsedDims = embDimensions ? parseInt(embDimensions, 10) : 0;
|
||||
verifyEmbedding(provider.id, embModel.trim() || undefined, parsedDims > 0 ? parsedDims : undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -183,6 +192,20 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="embDimensions">{t("embedding.dimensions")}</Label>
|
||||
<Input
|
||||
id="embDimensions"
|
||||
type="number"
|
||||
value={embDimensions}
|
||||
onChange={(e) => setEmbDimensions(e.target.value)}
|
||||
placeholder="1536"
|
||||
min={1}
|
||||
className="text-base md:text-sm"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t("embedding.dimensionsHint")}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="embApiBase">{t("embedding.apiBase")}</Label>
|
||||
<Input
|
||||
@@ -207,11 +230,23 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
|
||||
{t("embedding.verify")}
|
||||
</Button>
|
||||
{embResult && (
|
||||
<span className={`flex items-center gap-1 text-xs ${embResult.valid ? "text-success" : "text-destructive"}`}>
|
||||
<span className={`flex items-center gap-1 text-xs ${
|
||||
embResult.valid
|
||||
? embResult.dimension_mismatch
|
||||
? "text-amber-600 dark:text-amber-400"
|
||||
: "text-success"
|
||||
: "text-destructive"
|
||||
}`}>
|
||||
{embResult.valid ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
{embResult.dimensions} dimensions
|
||||
{embResult.dimension_mismatch ? (
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{embResult.dimension_mismatch
|
||||
? t("embedding.dimensionsMismatch", { count: embResult.dimensions })
|
||||
: `${embResult.dimensions} dimensions`}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface EmbeddingSettings {
|
||||
enabled: boolean;
|
||||
model?: string;
|
||||
api_base?: string;
|
||||
dimensions?: number; // truncate output to N dims (e.g. 1536); 0/undefined = model default
|
||||
}
|
||||
|
||||
/** Extract embedding settings from provider.settings */
|
||||
|
||||
Reference in new issue
Block a user