{"repo": "aula-id/koma", "files": {"src-webgui/src/components/panels/connector/ProviderForm.tsx": {"3ea22924c1ad420c5a70bd887398726870103575": {"t": 1785674600.4391553, "n": 5798, "h": "49c90c625cb23cc7", "c": "import { useState } from 'react'\nimport { ChevronRight, Server, SlidersHorizontal } from 'lucide-react'\nimport { Field, TextInput } from '../form'\nimport { FormActions } from '../helpers'\nimport type { Provider } from '../../../types/config'\n\n// The daemon never sends the plaintext key back (see `Provider.hasKey`), so\n// the form can't prefill it. The save payload carries only the TYPED value —\n// empty means \"leave unchanged\" (daemon-side blank-keeps-existing).\nexport type ProviderSavePayload = { id: string; name: string; endpoint: string; apiKey: string }\n\n// Predefined marketplaces — a name + verified OpenAI-compatible base URL that\n// pre-fills the form so the user only pastes a key. Base URLs are WEB-VERIFIED\n// canonical endpoints (note the non-obvious ones: Groq `/openai/v1`, Fireworks\n// `/inference/v1`, DeepInfra `/v1/openai`, Mimo token-plan host). \"Custom\" is\n// handled separately (blank manual form).\nexport const PREDEFINED: { name: string; endpoint: string }[] = [\n { name: 'Koma', endpoint: 'https://koma.run/api/v1/koma-gateway' },\n { name: 'OpenRouter', endpoint: 'https://openrouter.ai/api/v1' },\n { name: 'DeepSeek', endpoint: 'https://api.deepseek.com' },\n { name: 'Mimo (token plan)', endpoint: 'https://token-plan-sgp.xiaomimimo.com/v1' },\n { name: 'OpenAI', endpoint: 'https://api.openai.com/v1' },\n { name: 'Groq', endpoint: 'https://api.groq.com/openai/v1' },\n { name: 'Together', endpoint: 'https://api.together.xyz/v1' },\n { name: 'Fireworks', endpoint: 'https://api.fireworks.ai/inference/v1' },\n { name: 'Mistral', endpoint: 'https://api.mistral.ai/v1' },\n { name: 'DeepInfra', endpoint: 'https://api.deepinfra.com/v1/openai' },\n]\n\nexport function ProviderForm({\n draft,\n onSave,\n onCancel,\n preset,\n}: {\n draft: Provider\n onSave: (d: ProviderSavePayload) => void\n onCancel: () => void\n // Onboarding pre-selects a preset (or an explicit \"custom\") on its own pick\n // screen and hands it in here — when supplied, skip the internal 'pick'\n // step entirely and pre-fill name/endpoint from the preset (undefined for\n // \"custom\", leaving the draft's blank name/endpoint as-is).\n preset?: { name: string; endpoint: string } | 'custom'\n}) {\n const [d, setD] = useState(() =>\n preset && preset !== 'custom' ? { ...draft, name: preset.name, endpoint: preset.endpoint } : draft,\n )\n // Always starts empty — the real key is never available to prefill.\n const [apiKey, setApiKey] = useState('')\n // A brand-new provider (empty name + endpoint) leads with the marketplace\n // picker; editing an existing one jumps straight to the manual form. A\n // caller-supplied `preset` (onboarding) always skips straight to the form.\n const isNewDraft = draft.name.trim() === '' && draft.endpoint.trim() === ''\n const [step, setStep] = useState<'pick' | 'form'>(preset !== undefined ? 'form' : isNewDraft ? 'pick' : 'form')\n const patch = (p: Partial) => setD((x) => ({ ...x, ...p }))\n const keyPlaceholder = d.hasKey ? '•••••••• (unchanged — leave blank to keep)' : 'sk-…'\n\n const pickPreset = (name: string, endpoint: string) => {\n patch({ name, endpoint })\n setStep('form')\n }\n\n if (step === 'pick') {\n return (\n
\n
\n Choose a provider\n
\n
\n {PREDEFINED.map((p) => (\n pickPreset(p.name, p.endpoint)}\n className=\"flex items-center gap-2 rounded px-2 py-1.5 text-left transition-colors hover:bg-koma-hover\"\n >\n \n \n {p.name}\n {p.endpoint}\n \n \n \n ))}\n setStep('form')}\n className=\"mt-1 flex items-center gap-2 rounded border border-dashed border-koma-border px-2 py-1.5 text-left transition-colors hover:bg-koma-hover\"\n >\n \n \n Custom\n Enter name + endpoint manually\n \n \n \n
\n
\n )\n }\n\n return (\n <>\n
\n \n patch({ name: e.target.value })} />\n \n \n patch({ endpoint: e.target.value })} />\n \n \n setApiKey(e.target.value)} />\n \n
\n onSave({ id: d.id, name: d.name, endpoint: d.endpoint, apiKey })}\n saveDisabled={!d.name.trim()}\n />\n \n )\n}\n"}, "a14d029250efb9a566fba4dafaa74d802462a6bf": {"t": 1785846899.227603, "n": 5798, "h": "49c90c625cb23cc7", "c": "import { useState } from 'react'\nimport { ChevronRight, Server, SlidersHorizontal } from 'lucide-react'\nimport { Field, TextInput } from '../form'\nimport { FormActions } from '../helpers'\nimport type { Provider } from '../../../types/config'\n\n// The daemon never sends the plaintext key back (see `Provider.hasKey`), so\n// the form can't prefill it. The save payload carries only the TYPED value —\n// empty means \"leave unchanged\" (daemon-side blank-keeps-existing).\nexport type ProviderSavePayload = { id: string; name: string; endpoint: string; apiKey: string }\n\n// Predefined marketplaces — a name + verified OpenAI-compatible base URL that\n// pre-fills the form so the user only pastes a key. Base URLs are WEB-VERIFIED\n// canonical endpoints (note the non-obvious ones: Groq `/openai/v1`, Fireworks\n// `/inference/v1`, DeepInfra `/v1/openai`, Mimo token-plan host). \"Custom\" is\n// handled separately (blank manual form).\nexport const PREDEFINED: { name: string; endpoint: string }[] = [\n { name: 'Koma', endpoint: 'https://koma.run/api/v1/koma-gateway' },\n { name: 'OpenRouter', endpoint: 'https://openrouter.ai/api/v1' },\n { name: 'DeepSeek', endpoint: 'https://api.deepseek.com' },\n { name: 'Mimo (token plan)', endpoint: 'https://token-plan-sgp.xiaomimimo.com/v1' },\n { name: 'OpenAI', endpoint: 'https://api.openai.com/v1' },\n { name: 'Groq', endpoint: 'https://api.groq.com/openai/v1' },\n { name: 'Together', endpoint: 'https://api.together.xyz/v1' },\n { name: 'Fireworks', endpoint: 'https://api.fireworks.ai/inference/v1' },\n { name: 'Mistral', endpoint: 'https://api.mistral.ai/v1' },\n { name: 'DeepInfra', endpoint: 'https://api.deepinfra.com/v1/openai' },\n]\n\nexport function ProviderForm({\n draft,\n onSave,\n onCancel,\n preset,\n}: {\n draft: Provider\n onSave: (d: ProviderSavePayload) => void\n onCancel: () => void\n // Onboarding pre-selects a preset (or an explicit \"custom\") on its own pick\n // screen and hands it in here — when supplied, skip the internal 'pick'\n // step entirely and pre-fill name/endpoint from the preset (undefined for\n // \"custom\", leaving the draft's blank name/endpoint as-is).\n preset?: { name: string; endpoint: string } | 'custom'\n}) {\n const [d, setD] = useState(() =>\n preset && preset !== 'custom' ? { ...draft, name: preset.name, endpoint: preset.endpoint } : draft,\n )\n // Always starts empty — the real key is never available to prefill.\n const [apiKey, setApiKey] = useState('')\n // A brand-new provider (empty name + endpoint) leads with the marketplace\n // picker; editing an existing one jumps straight to the manual form. A\n // caller-supplied `preset` (onboarding) always skips straight to the form.\n const isNewDraft = draft.name.trim() === '' && draft.endpoint.trim() === ''\n const [step, setStep] = useState<'pick' | 'form'>(preset !== undefined ? 'form' : isNewDraft ? 'pick' : 'form')\n const patch = (p: Partial) => setD((x) => ({ ...x, ...p }))\n const keyPlaceholder = d.hasKey ? '•••••••• (unchanged — leave blank to keep)' : 'sk-…'\n\n const pickPreset = (name: string, endpoint: string) => {\n patch({ name, endpoint })\n setStep('form')\n }\n\n if (step === 'pick') {\n return (\n
\n
\n Choose a provider\n
\n
\n {PREDEFINED.map((p) => (\n pickPreset(p.name, p.endpoint)}\n className=\"flex items-center gap-2 rounded px-2 py-1.5 text-left transition-colors hover:bg-koma-hover\"\n >\n \n \n {p.name}\n {p.endpoint}\n \n \n \n ))}\n setStep('form')}\n className=\"mt-1 flex items-center gap-2 rounded border border-dashed border-koma-border px-2 py-1.5 text-left transition-colors hover:bg-koma-hover\"\n >\n \n \n Custom\n Enter name + endpoint manually\n \n \n \n
\n
\n )\n }\n\n return (\n <>\n
\n \n patch({ name: e.target.value })} />\n \n \n patch({ endpoint: e.target.value })} />\n \n \n setApiKey(e.target.value)} />\n \n
\n onSave({ id: d.id, name: d.name, endpoint: d.endpoint, apiKey })}\n saveDisabled={!d.name.trim()}\n />\n \n )\n}\n"}}}}