Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog/unreleased/console-fewer-borders-type-scale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### English

- Console: removed redundant nested borders (cards, tables, panels now separate by surface tone and spacing) and unified text sizes on a single type scale with the system UI font, so Chinese and English text match; the browser theme color now matches the page background.
- Console: system settings are grouped into tabs (General, Routing, Security & data, Updates), and the sidebar bottom-left entry opens a version popup with release notes and the same download / apply update actions as settings.

### 中文

- 控制台:去掉多余的嵌套线框(卡片、表格、面板改用底色与间距区分),并统一为一套字号层级,改用系统 UI 字体使中英文字形一致;浏览器主题色与页面背景一致。
- 控制台:系统设置按标签页分组(常规、路由调度、安全与数据、版本更新),侧边栏左下角入口可打开版本弹窗,含更新日志及与设置页一致的下载/立即更新操作。
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon-dark.svg" media="(prefers-color-scheme: dark)" />
<link rel="apple-touch-icon" href="/apple-touch-icon.svg" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#F5F5F5" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#060607" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FAFAFA" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0A0A0A" media="(prefers-color-scheme: dark)" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CLI2API</title>
<meta name="description" content="Qoder CLI → OpenAI-compatible API. Self-hosted gateway." />
Expand Down
20 changes: 0 additions & 20 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
"sync": "npm run build && node ./scripts/sync-static.mjs"
},
"dependencies": {
"@fontsource-variable/geist": "^5.3.0",
"@fontsource-variable/geist-mono": "^5.3.0",
"@heroui/react": "^3.2.4",
"@heroui/styles": "^3.2.4",
"@phosphor-icons/react": "^2.1.10",
Expand Down
30 changes: 15 additions & 15 deletions frontend/src/components/AddAccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function AccountTypeSkeleton({ ariaLabel }: { ariaLabel: string }) {
return (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2" aria-busy="true" aria-label={ariaLabel}>
{Array.from({ length: 6 }, (_, index) => (
<div key={index} className="flex items-center gap-2.5 rounded-xl border border-separator px-3 py-2.5">
<div key={index} className="flex items-center gap-2.5 rounded-xl bg-surface-secondary px-3 py-2.5">
<Skeleton className="size-7 shrink-0 rounded-lg" />
<Skeleton className="h-4 w-20 rounded-lg" />
</div>
Expand Down Expand Up @@ -534,7 +534,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
<Modal.Dialog>
<Modal.Header className="relative items-center justify-center px-12 pt-5 text-center">
<div className="min-w-0">
<Modal.Heading className="text-lg font-semibold tracking-[-0.01em]">{t('addAccountTitle')}</Modal.Heading>
<Modal.Heading className="text-section font-semibold">{t('addAccountTitle')}</Modal.Heading>
<p ref={stepDesc} className="mt-1 text-xs font-normal leading-5 text-muted">
{step === 'method' ? t('addAccountDesc') : (hint || t('addAccountDesc'))}
</p>
Expand All @@ -549,7 +549,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
{typesLoading ? (
<AccountTypeSkeleton ariaLabel={t('accountType')} />
) : !typesReady ? (
<p className="rounded-lg border border-separator bg-surface-secondary/45 px-3.5 py-3 text-xs leading-5 text-muted">{t('accountTypeHint')}</p>
<p className="rounded-lg bg-surface-secondary/45 px-3.5 py-3 text-xs leading-5 text-muted">{t('accountTypeHint')}</p>
) : (
<OptionTiles
ariaLabel={t('accountType')}
Expand All @@ -563,11 +563,11 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</>
) : (
<>
<div className="flex items-center gap-3 rounded-lg border border-separator bg-surface-secondary/45 px-3 py-2.5">
<div className="flex items-center gap-3 rounded-lg bg-surface-secondary/45 px-3 py-2.5">
<span className="shrink-0"><ProviderMark provider={activeOption?.provider} size={18} /></span>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium text-foreground">{activeOption ? optionLabel(activeOption, t) : t('accountType')}</div>
<div className="truncate text-[11px] text-muted">{hint}</div>
<div className="truncate text-body font-medium text-foreground">{activeOption ? optionLabel(activeOption, t) : t('accountType')}</div>
<div className="truncate text-caption text-muted">{hint}</div>
</div>
<Button size="sm" variant="ghost" onPress={() => setStep('method')} isDisabled={busy || Boolean(createdId.current)}>
<CaretLeft size={12} />{t('wizardBack')}
Expand All @@ -594,7 +594,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
{t('wizardAdvanced')}
</button>
{advancedOpen ? (
<div className="space-y-3 rounded-lg border border-separator px-3.5 py-3.5">
<div className="space-y-3 rounded-lg bg-surface-secondary px-3.5 py-3.5">
<FormRow label={t('maxInflight')} hint={t('maxInflightHint')}>
<NumberField
value={maxInFlight}
Expand Down Expand Up @@ -639,7 +639,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
{showDropSystem ? (
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<div className="text-sm font-medium text-muted">{t('dropSystemPrompt')}</div>
<div className="text-body font-medium text-muted">{t('dropSystemPrompt')}</div>
<p className="mt-0.5 text-xs leading-5 text-muted">{t('dropSystemPromptCreateHint')}</p>
</div>
<CompactSwitch
Expand All @@ -657,7 +657,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
{isZhipu ? (
<div className="mt-4 flex flex-col gap-4">
<div className="space-y-2">
<span className="text-sm font-medium text-muted">{t('zhipuAccountMode')}</span>
<span className="text-body font-medium text-muted">{t('zhipuAccountMode')}</span>
<OptionTiles
ariaLabel={t('zhipuAccountMode')}
columns={2}
Expand All @@ -683,7 +683,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</>
) : null}
{message ? (
<p className="flex items-center gap-2 rounded-lg border border-separator bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
<p className="flex items-center gap-2 rounded-lg bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
) : null}
<Button className="w-full" isPending={phase === 'busy'} onPress={() => void runZhipu()} isDisabled={isDone}>
{isDone ? <><CheckCircle size={15} />{t('wizardAccountReady')}</> : <><Key size={15} />{t('zhipuCreate')}</>}
Expand Down Expand Up @@ -730,7 +730,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
/>
) : null}
{authUrl ? (
<div className="rounded-lg border border-separator bg-surface-secondary px-3 py-2.5">
<div className="rounded-lg bg-surface-secondary px-3 py-2.5">
<div className="flex items-center gap-2 text-xs">
<StatusIcon phase={phase} busy={busy} tab={tab} forTab="browser" />
<span className="text-muted">{message || t('loginOpenMsg')}</span>
Expand Down Expand Up @@ -758,11 +758,11 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</div>
) : null}
{message && !authUrl ? (
<p className="flex items-center gap-2 rounded-lg border border-separator bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
<p className="flex items-center gap-2 rounded-lg bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
) : null}
{showCallbackPaste ? (
<div className="space-y-2">
<p className="text-[11px] leading-4 text-muted">{t('wizardCallbackLead')}</p>
<p className="text-caption leading-4 text-muted">{t('wizardCallbackLead')}</p>
<TextArea
className="h-28 w-full resize-none font-mono text-xs leading-5"
value={callbackUrl}
Expand All @@ -786,7 +786,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
<Input type="password" value={pat} onChange={(event) => setPat(event.target.value)} placeholder={t('wizardPatPh')} aria-label={t('wizardPatPh')} disabled={busy} />
</FormRow>
{message ? (
<p className="flex items-center gap-2 rounded-lg border border-separator bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
<p className="flex items-center gap-2 rounded-lg bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
) : null}
<Button className="w-full" isPending={tabPending('pat')} onPress={() => void runPat()}>
{isDone ? <><CheckCircle size={15} />{t('patDone')}</> : <><Key size={15} />{t('wizardCreateAndLogin')}</>}
Expand All @@ -801,7 +801,7 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</div>
<TextArea className="min-h-32 font-mono text-xs" value={json} onChange={(event) => setJson(event.target.value)} placeholder={t('wizardImportPh')} aria-label={t('tabImport')} disabled={busy} />
{message ? (
<p className="flex items-center gap-2 rounded-lg border border-separator bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
<p className="flex items-center gap-2 rounded-lg bg-surface-secondary px-3 py-2 text-xs">{isDone ? <CheckCircle size={14} className="shrink-0 text-success" /> : null}<span className={isDone ? 'font-medium text-foreground' : 'text-muted'}>{message}</span></p>
) : null}
<Button className="w-full" isPending={tabPending('import')} onPress={() => void runImport()}>
{isDone ? <><CheckCircle size={15} />{t('accountImported')}</> : <><FileCode size={15} />{t('importCredential')}</>}
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/EndpointList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export function EndpointList({ access }: EndpointListProps) {
], [access, base, t])

return (
<Card data-gsap-reveal className="overflow-hidden border-border p-0 shadow-none">
<Card data-gsap-reveal className="overflow-hidden p-0">
<div className="flex items-start justify-between gap-4 border-b border-separator px-5 py-4 sm:px-6">
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold tracking-[-0.015em]">{t('endpoints')}</h3>
<h3 className="font-semibold">{t('endpoints')}</h3>
<Chip size="sm" variant="soft">{t('endpointCount', { count: endpoints.length - 1 })}</Chip>
</div>
<p className="mt-1 text-xs leading-5 text-muted">{t('routesHint')}</p>
Expand All @@ -42,15 +42,15 @@ export function EndpointList({ access }: EndpointListProps) {
<div className="flex min-w-0 items-center gap-2.5">
<span className="grid size-7 shrink-0 place-items-center rounded-lg bg-surface text-muted">{item.icon}</span>
<div className="min-w-0">
<div className="truncate text-sm font-semibold">{item.name}</div>
<code className="mono mt-0.5 block truncate text-[11px] text-muted">{item.url}</code>
<div className="truncate text-body font-semibold">{item.name}</div>
<code className="mono mt-0.5 block truncate text-caption text-muted">{item.url}</code>
</div>
</div>
<Chip size="sm" variant="soft">{item.method}</Chip>
</div>
<p className="mt-2 min-h-0 text-xs leading-5 text-muted">{item.hint}</p>
<div className="mt-2 flex items-center justify-between gap-2 border-t border-separator pt-2">
<span className="text-[10px] font-medium text-muted">{item.method === 'BASE' ? t('endpointBaseLabel') : t('endpointAuthLabel')}</span>
<div className="mt-2 flex items-center justify-between gap-2 pt-2">
<span className="text-caption font-medium text-muted">{item.method === 'BASE' ? t('endpointBaseLabel') : t('endpointAuthLabel')}</span>
<div className="flex gap-1">
<Button isIconOnly size="sm" variant="ghost" aria-label={t('copy')} onPress={() => { void copyText(item.url); setCopiedEndpoint(item.name); window.setTimeout(() => setCopiedEndpoint(''), 1100) }}>
<CopyIcon copied={copiedEndpoint === item.name} />
Expand Down
Loading
Loading