From e2595bcab33b30ecc996ea9501a09c9cb75064a9 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:17:51 +0800 Subject: [PATCH] Merge pull request #204 from Shinku-Chen/rand-sk-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生成密钥的时候,能随机生成,同时能复制token --- src/web/pages/DownstreamKeys.tsx | 105 +++++++++++++++++- src/web/pages/Settings.tsx | 98 +++++++++++----- .../pages/helpers/generateDownstreamSkKey.ts | 6 + 3 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 src/web/pages/helpers/generateDownstreamSkKey.ts diff --git a/src/web/pages/DownstreamKeys.tsx b/src/web/pages/DownstreamKeys.tsx index 133be75..e13b061 100644 --- a/src/web/pages/DownstreamKeys.tsx +++ b/src/web/pages/DownstreamKeys.tsx @@ -7,10 +7,13 @@ import { useToast } from '../components/Toast.js'; import ModernSelect from '../components/ModernSelect.js'; import { useAnimatedVisibility } from '../components/useAnimatedVisibility.js'; import { tr } from '../i18n.js'; +import { generateDownstreamSkKey } from './helpers/generateDownstreamSkKey.js'; const DownstreamKeyTrendChart = lazy(() => import('../components/charts/DownstreamKeyTrendChart.js')); type DownstreamKeyTrendBucket = import('../components/charts/DownstreamKeyTrendChart.js').DownstreamKeyTrendBucket; +const PROXY_TOKEN_PREFIX = 'sk-'; + type Range = '24h' | '7d' | 'all'; type Status = 'all' | 'enabled' | 'disabled'; @@ -259,6 +262,86 @@ function tagChipStyle(kind: 'normal' | 'accent' = 'normal'): React.CSSProperties }; } +async function copyToClipboard(text: string): Promise { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return; + } + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.left = '-9999px'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); +} + +function DownstreamKeyCopyIconButton({ fullKey }: { fullKey: string | undefined }) { + const toast = useToast(); + const [pressed, setPressed] = useState(false); + + const disabled = !fullKey?.trim(); + const release = () => setPressed(false); + + return ( + + ); +} + function buildEditorForm(item?: ManagedItem | DownstreamApiKeyItem | null): EditorForm { return { name: item?.name || '', @@ -877,7 +960,22 @@ function EditorModal({
下游密钥
- onChange((prev) => ({ ...prev, key: e.target.value }))} placeholder="sk-..." style={{ ...inputStyle, fontFamily: 'var(--font-mono)' }} /> +
+ onChange((prev) => ({ ...prev, key: e.target.value }))} + placeholder="sk-..." + style={{ ...inputStyle, flex: 1, minWidth: 0, fontFamily: 'var(--font-mono)' }} + /> + +
主分组
@@ -1637,7 +1735,10 @@ export default function DownstreamKeys() { {row.name}
-
{row.keyMasked}
+
+ {row.keyMasked} + +
{row.description ?
{row.description}
: null}
diff --git a/src/web/pages/Settings.tsx b/src/web/pages/Settings.tsx index 51d263b..c378e32 100644 --- a/src/web/pages/Settings.tsx +++ b/src/web/pages/Settings.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { api } from '../api.js'; @@ -17,6 +17,7 @@ import { clearAuthSession } from '../authSession.js'; import { clearAppInstallationState } from '../appLocalState.js'; import { tr } from '../i18n.js'; import { ROUTE_ICON_NONE_VALUE } from './token-routes/utils.js'; +import { generateDownstreamSkKey } from './helpers/generateDownstreamSkKey.js'; const PROXY_TOKEN_PREFIX = 'sk-'; const ROUTE_BRAND_ICON_PREFIX = 'brand:'; @@ -1164,42 +1165,83 @@ export default function Settings() {
- - {PROXY_TOKEN_PREFIX} - - setProxyTokenSuffix(normalizeProxyTokenSuffix(e.target.value))} - placeholder="请输入 sk- 后的令牌内容" + + {PROXY_TOKEN_PREFIX} + + setProxyTokenSuffix(normalizeProxyTokenSuffix(e.target.value))} + placeholder="请输入 sk- 后的令牌内容" + style={{ + flex: 1, + minWidth: 0, + border: 'none', + outline: 'none', + background: 'transparent', + color: 'var(--color-text-primary)', + fontFamily: 'var(--font-mono)', + fontSize: 13, + padding: '10px 12px', + }} + /> +
+