mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den): let users name their LLM providers explicitly (#1508)
Users were confused when adding or editing an LLM provider because the display name was derived automatically from the catalog or pasted config. This made it impossible to tell two keys for the same provider apart (e.g. 'OpenAI personal' vs 'OpenAI prod'). - Add an explicit 'Name' field to the add + edit LLM provider forms in ee/apps/den-web with a 'Give this key a name' prompt, preload the current name when editing, and require a non-empty value before save. - Require 'name' on the llm-provider write schema in ee/apps/den-api and persist it verbatim instead of falling back to provider.name from the models.dev catalog or the custom config JSON. Verified end-to-end via Chrome DevTools MCP on the Den Docker stack; screenshots captured under ee/apps/den-web/docs/screenshots/. Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
@@ -60,6 +60,7 @@ const customProviderSchema = z.object({
|
||||
}).passthrough()
|
||||
|
||||
const llmProviderWriteSchema = z.object({
|
||||
name: z.string().trim().min(1).max(255),
|
||||
source: z.enum(["models_dev", "custom"]),
|
||||
providerId: z.string().trim().min(1).max(255).optional(),
|
||||
modelIds: z.array(z.string().trim().min(1).max(255)).min(1).optional(),
|
||||
@@ -288,7 +289,7 @@ async function normalizeLlmProviderInput(input: z.infer<typeof llmProviderWriteS
|
||||
return {
|
||||
source: input.source,
|
||||
providerId: provider.id,
|
||||
name: provider.name,
|
||||
name: input.name,
|
||||
providerConfig: provider.config,
|
||||
models: models.map((model) => ({
|
||||
id: model.id,
|
||||
@@ -320,7 +321,7 @@ async function normalizeLlmProviderInput(input: z.infer<typeof llmProviderWriteS
|
||||
return {
|
||||
source: input.source,
|
||||
providerId: customProvider.data.id,
|
||||
name: customProvider.data.name,
|
||||
name: input.name,
|
||||
providerConfig: providerConfig as JsonRecord,
|
||||
models: models.map((model) => ({
|
||||
id: model.id,
|
||||
|
||||
@@ -81,6 +81,7 @@ export function LlmProviderEditorScreen({
|
||||
useState<DenModelsDevProviderDetail | null>(null);
|
||||
const [detailBusy, setDetailBusy] = useState(false);
|
||||
const [detailError, setDetailError] = useState<string | null>(null);
|
||||
const [providerName, setProviderName] = useState("");
|
||||
const [selectedModelIds, setSelectedModelIds] = useState<string[]>([]);
|
||||
const [modelQuery, setModelQuery] = useState("");
|
||||
const [customConfigText, setCustomConfigText] = useState(
|
||||
@@ -131,6 +132,7 @@ export function LlmProviderEditorScreen({
|
||||
if (provider) {
|
||||
setSource(provider.source);
|
||||
setSelectedProviderId(provider.providerId);
|
||||
setProviderName(provider.name);
|
||||
setSelectedModelIds(provider.models.map((entry) => entry.id));
|
||||
setSelectedMemberIds(
|
||||
provider.access.members.map((entry) => entry.orgMembershipId),
|
||||
@@ -149,6 +151,7 @@ export function LlmProviderEditorScreen({
|
||||
|
||||
setSource("models_dev");
|
||||
setSelectedProviderId("");
|
||||
setProviderName("");
|
||||
setSelectedModelIds([]);
|
||||
setSelectedMemberIds(
|
||||
orgContext?.currentMember.id ? [orgContext.currentMember.id] : [],
|
||||
@@ -261,6 +264,11 @@ export function LlmProviderEditorScreen({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!providerName.trim()) {
|
||||
setSaveError("Give this provider a name.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (source === "models_dev") {
|
||||
if (!selectedProviderId) {
|
||||
setSaveError("Select a provider.");
|
||||
@@ -281,6 +289,7 @@ export function LlmProviderEditorScreen({
|
||||
setSaveError(null);
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
name: providerName.trim(),
|
||||
source,
|
||||
memberIds: [...new Set(selectedMemberIds)],
|
||||
teamIds: [...new Set(selectedTeamIds)],
|
||||
@@ -396,7 +405,7 @@ export function LlmProviderEditorScreen({
|
||||
<div>
|
||||
<h1 className="text-[34px] font-semibold tracking-[-0.07em] text-gray-950">
|
||||
{provider
|
||||
? provider.name
|
||||
? (providerName.trim() || provider.name)
|
||||
: "Add a new LLM provider"}
|
||||
</h1>
|
||||
<p className="mt-3 max-w-[720px] text-[16px] leading-8 text-gray-500">
|
||||
@@ -435,6 +444,24 @@ export function LlmProviderEditorScreen({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<section className="mb-8 rounded-[36px] border border-gray-200 bg-white p-8 shadow-[0_18px_48px_-34px_rgba(15,23,42,0.24)]">
|
||||
<label className="grid gap-3">
|
||||
<span className="text-[14px] font-medium text-gray-700">
|
||||
Name
|
||||
</span>
|
||||
<DenInput
|
||||
value={providerName}
|
||||
onChange={(event) => setProviderName(event.target.value)}
|
||||
placeholder="Give this key a name"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
<p className="mt-3 text-[13px] text-gray-500">
|
||||
Pick a clear label so teammates know which key or provider
|
||||
setup they are using.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-8 rounded-[36px] border border-gray-200 bg-white p-8 shadow-[0_18px_48px_-34px_rgba(15,23,42,0.24)]">
|
||||
<h2 className="mb-6 text-[24px] font-semibold tracking-[-0.05em] text-gray-950">
|
||||
Provider type
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 447 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
BIN
ee/apps/den-web/docs/screenshots/llm-provider-new-filled.png
Normal file
BIN
ee/apps/den-web/docs/screenshots/llm-provider-new-filled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 447 KiB |
BIN
ee/apps/den-web/docs/screenshots/llm-provider-new-name-field.png
Normal file
BIN
ee/apps/den-web/docs/screenshots/llm-provider-new-name-field.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 199 KiB |
Reference in New Issue
Block a user