Merge remote-tracking branch 'origin/master' into mergebot/pr1016
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-width: 720px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.title {
|
||||
@@ -14,13 +15,13 @@
|
||||
.intro {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary, #888);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-warning, #a15c00);
|
||||
color: var(--dsw-alias-state-warn-label);
|
||||
}
|
||||
|
||||
.rows {
|
||||
@@ -33,13 +34,13 @@
|
||||
}
|
||||
|
||||
.rowCard {
|
||||
border: 1px solid var(--border, #e2e2e2);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
background: var(--surface, #fff);
|
||||
background: var(--dsw-alias-bg-layer-3);
|
||||
}
|
||||
|
||||
.rowHead {
|
||||
@@ -53,58 +54,27 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.badgeOk {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: var(--text-success, #0a7d33);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badgeOk::before {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: currentcolor;
|
||||
}
|
||||
|
||||
.badgeMuted {
|
||||
color: var(--text-tertiary, #999);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badgeWarn {
|
||||
color: var(--text-warning, #a15c00);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rowActions {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.primaryButton {
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
padding: 8px 18px;
|
||||
background: var(--accent-strong, #111);
|
||||
color: var(--text-inverse, #fff);
|
||||
background: var(--dsw-alias-button-primary-fill);
|
||||
color: var(--dsw-alias-label-primary-foreground);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.secondaryButton {
|
||||
border: 1px solid var(--border, #d9d9d9);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 999px;
|
||||
padding: 6px 14px;
|
||||
background: var(--surface, #fff);
|
||||
background: var(--dsw-alias-bg-layer-3);
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
@@ -113,7 +83,7 @@
|
||||
.dangerButton {
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-danger, #c0392b);
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -126,9 +96,9 @@
|
||||
}
|
||||
|
||||
.editor {
|
||||
border: 1px solid var(--border, #e6e6e6);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 12px;
|
||||
background: var(--surface-secondary, #f7f7f8);
|
||||
background: var(--dsw-alias-bg-layer-2);
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -148,7 +118,7 @@
|
||||
|
||||
.editorRoute {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #999);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.field {
|
||||
@@ -163,14 +133,14 @@
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #555);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
|
||||
.linkButton {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--text-tertiary, #888);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
text-decoration: underline;
|
||||
@@ -185,7 +155,7 @@
|
||||
.advancedHint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #999);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.editorActions {
|
||||
@@ -202,12 +172,12 @@
|
||||
|
||||
.addButton {
|
||||
align-self: flex-start;
|
||||
border: 1px solid var(--border, #d9d9d9);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 999px;
|
||||
padding: 8px 16px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
background: var(--surface, #fff);
|
||||
background: var(--dsw-alias-bg-layer-3);
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -219,9 +189,9 @@
|
||||
|
||||
.addCard,
|
||||
.setupCard {
|
||||
border: 1px solid var(--border, #e6e6e6);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 12px;
|
||||
background: var(--surface-secondary, #f7f7f8);
|
||||
background: var(--dsw-alias-bg-layer-3);
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -237,7 +207,7 @@
|
||||
}
|
||||
|
||||
.customized {
|
||||
border-top: 1px solid var(--border, #ececec);
|
||||
border-top: 1px solid var(--dsw-alias-border-l2);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@@ -245,7 +215,7 @@
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #555);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
list-style: revert;
|
||||
}
|
||||
|
||||
@@ -259,25 +229,38 @@
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
padding: 9px 12px;
|
||||
border: 1px solid var(--border, #d9d9d9);
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 10px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
background: var(--surface, #fff);
|
||||
color: inherit;
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-strong, #111);
|
||||
border-color: var(--dsw-alias-brand-primary);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--text-tertiary, #aaa);
|
||||
color: var(--dsw-alias-label-dimmed);
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-danger, #c0392b);
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
}
|
||||
|
||||
.deleteDialog {
|
||||
width: min(480px, 100%);
|
||||
}
|
||||
|
||||
.deleteConfirm:not(:disabled) {
|
||||
border-color: var(--dsw-alias-state-error-primary);
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
}
|
||||
|
||||
.deleteConfirm:hover:not(:disabled) {
|
||||
background: var(--dsw-alias-interactive-bg-hover-danger);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
* card at a time. A whole-section provider without a configured key (the
|
||||
* unconfigured DeepSeek posture) renders as its open setup card instead of a
|
||||
* row; the add flow is a card carrying the dormant-provider select. Every
|
||||
* mutation writes through the wire; the page re-renders from the pushed
|
||||
* invalidations or the post-apply reload.
|
||||
* mutation writes through the wire, while a provider removal first requires
|
||||
* confirmation; the page re-renders from pushed invalidations or the
|
||||
* post-apply reload.
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-web-react'
|
||||
import { messageOf } from './store.ts'
|
||||
import type { ModelsSettingsState, ModelsSettingsStore, ProviderRow } from './store.ts'
|
||||
@@ -114,6 +116,8 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
|
||||
const state = injected.useSnapshot(snapshot => snapshot)
|
||||
const [editing, setEditing] = useState<EditorTarget | undefined>(undefined)
|
||||
const [adding, setAdding] = useState(false)
|
||||
const [deleteTarget, setDeleteTarget] = useState<EditorTarget | undefined>(undefined)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
const closeEditor = (changed: boolean): void => {
|
||||
setEditing(undefined)
|
||||
@@ -121,6 +125,26 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
|
||||
if (changed) void controller.load()
|
||||
}
|
||||
|
||||
const closeDelete = (): void => {
|
||||
if (deleting) return
|
||||
setDeleteTarget(undefined)
|
||||
}
|
||||
|
||||
const confirmDelete = (): void => {
|
||||
/* v8 ignore next -- the action only renders with a target and is disabled while a deletion is pending */
|
||||
if (deleteTarget === undefined || deleting) return
|
||||
setDeleting(true)
|
||||
void removeProviderProfile(api, controller, deleteTarget)
|
||||
.then((failure) => {
|
||||
if (failure !== undefined) {
|
||||
controller.fail(failure)
|
||||
return
|
||||
}
|
||||
setDeleteTarget(undefined)
|
||||
})
|
||||
.finally(() => { setDeleting(false) })
|
||||
}
|
||||
|
||||
if (state.status === 'idle') void controller.load()
|
||||
if (state.status === 'error') {
|
||||
/* v8 ignore next -- an error status always carries text; the fallback satisfies the nullable type */
|
||||
@@ -174,11 +198,6 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
|
||||
<li key={row.entry.provider} className={styles['rowCard']}>
|
||||
<div className={styles['rowHead']}>
|
||||
<span className={styles['rowName']}>{row.entry.displayName}</span>
|
||||
<span className={styles['badges']}>
|
||||
{row.entry.active
|
||||
? <span className={styles['badgeOk']}>{t('active')}</span>
|
||||
: <span className={styles['badgeMuted']}>{t('dormant')}</span>}
|
||||
</span>
|
||||
<span className={styles['rowActions']}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -193,11 +212,7 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
|
||||
type="button"
|
||||
className={styles['dangerButton']}
|
||||
disabled={!state.writable}
|
||||
onClick={() => {
|
||||
void removeProviderProfile(api, controller, target).then((failure) => {
|
||||
if (failure !== undefined) controller.fail(failure)
|
||||
})
|
||||
}}
|
||||
onClick={() => { setDeleteTarget(target) }}
|
||||
>
|
||||
{t('remove')}
|
||||
</button>
|
||||
@@ -276,6 +291,29 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Modal
|
||||
open={deleteTarget !== undefined}
|
||||
onClose={closeDelete}
|
||||
title={t('deleteTitle')}
|
||||
closeLabel={t('close')}
|
||||
description={t('deleteDescription')}
|
||||
className={styles['deleteDialog'] as string}
|
||||
footer={(
|
||||
<>
|
||||
<Button variant="outline" autoFocus disabled={deleting} onClick={closeDelete}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={styles['deleteConfirm']}
|
||||
disabled={deleting}
|
||||
onClick={confirmDelete}
|
||||
>
|
||||
{deleting ? t('deleting') : t('deleteConfirm')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ export const en = {
|
||||
nav: 'Models',
|
||||
title: 'Models',
|
||||
intro: 'Enter your API keys to use models from the following providers.',
|
||||
active: 'Active',
|
||||
dormant: 'Inactive',
|
||||
edit: 'Edit',
|
||||
remove: 'Delete',
|
||||
deleteTitle: 'Delete model provider?',
|
||||
deleteDescription: 'Deleting this model provider removes its configuration. You will not be able to use its models until you add the provider again.',
|
||||
deleteConfirm: 'Delete provider',
|
||||
deleting: 'Deleting provider…',
|
||||
add: 'Add provider',
|
||||
provider: 'Provider',
|
||||
close: 'Close',
|
||||
cancel: 'Cancel',
|
||||
apply: 'Apply',
|
||||
applying: 'Applying…',
|
||||
@@ -42,12 +45,15 @@ export const zh: typeof en = {
|
||||
nav: '模型',
|
||||
title: '模型',
|
||||
intro: '填入各提供方的 API 密钥即可使用其模型。',
|
||||
active: '已启用',
|
||||
dormant: '未启用',
|
||||
edit: '编辑',
|
||||
remove: '删除',
|
||||
deleteTitle: '删除模型提供方?',
|
||||
deleteDescription: '删除此模型提供方会移除其配置。在重新添加前,你将无法继续使用其模型。',
|
||||
deleteConfirm: '删除提供方',
|
||||
deleting: '正在删除提供方…',
|
||||
add: '添加提供方',
|
||||
provider: '提供方',
|
||||
close: '关闭',
|
||||
cancel: '取消',
|
||||
apply: '保存',
|
||||
applying: '保存中…',
|
||||
|
||||
Reference in New Issue
Block a user