refactor(config): reorganize network settings and remove plugin controls

This commit is contained in:
lzt404 committed 2026-07-30 23:38:47 +08:00
1 parent 0baff99d6e
commit aafe0c8533
7 files changed
+207 -254

No files matched your search

+2 -4
View File
@@ -256,8 +256,6 @@ export const jaOverrides = {
'config.notice.keyDeleted': '認証キーを削除しました',
'config.notice.keyCopied': '認証キーをコピーしました',
'config.notice.keyCopyFailed': '認証キーをコピーできませんでした',
'config.notice.pluginsEnabled': 'プラグインシステムを有効にしました',
'config.notice.pluginsDisabled': 'プラグインシステムを無効にしました',
'config.notice.routingUpdated': 'ルーティング方式を更新しました',
'config.notice.networkUpdated': 'ネットワークとセッションルーティング設定を保存しました',
'config.notice.networkRestarted': 'ネットワーク設定を保存し、コアを再起動しました',
@@ -279,14 +277,14 @@ export const jaOverrides = {
'config.keys.delete': '認証キーを削除',
'config.keys.deleteNth': '{number} 番目の認証キーを削除',
'config.keys.empty': '認証キーがありません',
'config.plugins.title': 'プラグインシステム',
'common.loading': '読み込み中',
'common.unavailable': '利用不可',
'common.enabled': '有効',
'common.disabled': '無効',
'config.plugins.enable': 'プラグインシステムを有効にする',
'config.routing.title': 'ルーティング方式',
'config.network.title': 'ネットワークとセッションルーティング',
'config.network.networkSection': 'ネットワーク設定',
'config.network.routingSection': 'セッションルーティング',
'config.network.saving': '保存中',
'config.network.saved': '保存済み',
'config.network.unsaved': '未保存の変更あり',
+5 -9
View File
@@ -259,8 +259,6 @@ export const zhCN = {
'config.notice.keyDeleted': '鉴权密钥已删除',
'config.notice.keyCopied': '鉴权密钥已复制',
'config.notice.keyCopyFailed': '复制鉴权密钥失败',
'config.notice.pluginsEnabled': '插件系统已启用',
'config.notice.pluginsDisabled': '插件系统已停用',
'config.notice.routingUpdated': '路由策略已更新',
'config.notice.networkUpdated': '网络与会话路由设置已保存',
'config.notice.networkRestarted': '网络设置已保存,内核已重启',
@@ -282,14 +280,14 @@ export const zhCN = {
'config.keys.delete': '删除鉴权密钥',
'config.keys.deleteNth': '删除第 {number} 个鉴权密钥',
'config.keys.empty': '暂无鉴权密钥',
'config.plugins.title': '插件系统',
'common.loading': '读取中',
'common.unavailable': '不可用',
'common.enabled': '已启用',
'common.disabled': '已停用',
'config.plugins.enable': '启用插件系统',
'config.routing.title': '路由策略',
'config.network.title': '网络与会话路由',
'config.network.networkSection': '网络设置',
'config.network.routingSection': '会话路由',
'config.network.saving': '正在保存',
'config.network.saved': '已保存',
'config.network.unsaved': '有未保存修改',
@@ -297,7 +295,7 @@ export const zhCN = {
'config.network.port': '端口',
'config.network.portHint': '范围 1–65535;内核运行时修改会自动重启',
'config.network.allowLan': '允许局域网',
'config.network.allowLanHint': '允许其他局域网设备访问本机代理',
'config.network.allowLanHint': '启用后将',
'config.network.proxyUrl': '代理 URL',
'config.network.proxyPlaceholder': '例如 socks5://127.0.0.1:7890',
'config.network.proxyHint': '全局上游代理;留空表示不使用',
@@ -1109,8 +1107,6 @@ export const en: Record<MessageKey, string> = {
'config.notice.keyDeleted': 'Authentication key deleted',
'config.notice.keyCopied': 'Authentication key copied',
'config.notice.keyCopyFailed': 'Failed to copy the authentication key',
'config.notice.pluginsEnabled': 'Plugin system enabled',
'config.notice.pluginsDisabled': 'Plugin system disabled',
'config.notice.routingUpdated': 'Routing strategy updated',
'config.notice.networkUpdated': 'Network and session routing settings saved',
'config.notice.networkRestarted': 'Network settings saved and the core restarted',
@@ -1132,14 +1128,14 @@ export const en: Record<MessageKey, string> = {
'config.keys.delete': 'Delete authentication key',
'config.keys.deleteNth': 'Delete authentication key {number}',
'config.keys.empty': 'No authentication keys',
'config.plugins.title': 'Plugin System',
'common.loading': 'Loading',
'common.unavailable': 'Unavailable',
'common.enabled': 'Enabled',
'common.disabled': 'Disabled',
'config.plugins.enable': 'Enable plugin system',
'config.routing.title': 'Routing Strategy',
'config.network.title': 'Network & Session Routing',
'config.network.networkSection': 'Network Settings',
'config.network.routingSection': 'Session Routing',
'config.network.saving': 'Saving',
'config.network.saved': 'Saved',
'config.network.unsaved': 'Unsaved changes',
+161 -166
View File
@@ -13,7 +13,6 @@ import {
Network,
Pencil,
Plus,
Plug,
RefreshCw,
Route,
Sparkles,
@@ -28,7 +27,6 @@ type CoreConfigSettings = {
apiKeys: CoreApiKey[];
port: number;
allowLan: boolean;
pluginsEnabled: boolean;
routingStrategy: string;
proxyUrl: string;
routingSessionAffinity: boolean;
@@ -44,7 +42,6 @@ type ConfigAction =
| 'add-key'
| 'update-key'
| 'delete-key'
| 'plugins'
| 'routing'
| 'network'
| null;
@@ -79,6 +76,7 @@ export function ConfigPanelPage() {
const [sessionAffinityDraft, setSessionAffinityDraft] = useState(false);
const [sessionTtlDraft, setSessionTtlDraft] = useState('');
const [portError, setPortError] = useState('');
const [savedStatusVisible, setSavedStatusVisible] = useState(false);
const noticeTimerRef = useRef<number | null>(null);
const copyTimerRef = useRef<number | null>(null);
@@ -268,25 +266,18 @@ export function ConfigPanelPage() {
}
};
const togglePlugins = async (enabled: boolean) => {
await runMutation(
'plugins',
'set_core_plugins_enabled',
{ enabled },
enabled ? t('config.notice.pluginsEnabled') : t('config.notice.pluginsDisabled'),
);
};
const changeRoutingStrategy = async (strategy: string) => {
if (strategy === settings?.routingStrategy) {
return;
}
await runMutation(
setSavedStatusVisible(false);
const saved = await runMutation(
'routing',
'set_core_routing_strategy',
{ strategy },
t('config.notice.routingUpdated'),
);
if (saved) setSavedStatusVisible(true);
};
const saveNetworkRoutingSettings = async () => {
@@ -302,6 +293,7 @@ export function ConfigPanelPage() {
const routingSessionAffinityTtl = sessionTtlDraft.trim();
const networkChanged = port !== settings.port || allowLanDraft !== settings.allowLan;
setPortError('');
setSavedStatusVisible(false);
setBusyAction('network');
try {
const result = await invoke<CoreConfigSettings>('save_network_routing_settings', {
@@ -315,6 +307,7 @@ export function ConfigPanelPage() {
});
applySettings(result);
setLoadError('');
setSavedStatusVisible(true);
if (networkChanged && coreStatus?.running) {
try {
@@ -347,6 +340,22 @@ export function ConfigPanelPage() {
|| sessionAffinityDraft !== settings?.routingSessionAffinity
|| sessionTtlDraft.trim() !== settings?.routingSessionAffinityTtl
);
const networkStatusLabel = loading
? t('common.loading')
: settings === null
? t('common.unavailable')
: busyAction === 'network' || busyAction === 'routing'
? t('config.network.saving')
: networkRoutingDirty
? t('config.network.unsaved')
: savedStatusVisible
? t('config.network.saved')
: '';
const networkStatusIsSaved = !loading
&& settings !== null
&& busyAction === null
&& !networkRoutingDirty
&& savedStatusVisible;
const selectedDeleteKey =
deleteIndex === null ? '' : settings?.apiKeys[deleteIndex]?.apiKey || '';
const deletingLastKey = deleteIndex !== null && settings?.apiKeys.length === 1;
@@ -400,7 +409,6 @@ export function ConfigPanelPage() {
role="tabpanel"
aria-labelledby="config-subpage-tab-general"
>
<div className="config-workspace-grid">
<section className="panel config-keys-panel">
<div className="config-panel-heading">
<div className="config-heading-title">
@@ -502,40 +510,6 @@ export function ConfigPanelPage() {
)}
</div>
</section>
<div className="config-side-stack single">
<section className="panel config-setting-panel">
<div className="config-panel-heading">
<div className="config-heading-title">
<Plug size={18} aria-hidden="true" />
<h2>{t('config.plugins.title')}</h2>
</div>
<span className={`state-pill ${settings?.pluginsEnabled ? 'success' : ''}`}>
{loading
? t('common.loading')
: settings === null
? t('common.unavailable')
: settings.pluginsEnabled
? t('common.enabled')
: t('common.disabled')}
</span>
</div>
<div className="config-single-control">
<span>{t('config.plugins.enable')}</span>
<label className="switch-control" title={t('config.plugins.enable')}>
<input
type="checkbox"
aria-label={t('config.plugins.enable')}
checked={Boolean(settings?.pluginsEnabled)}
disabled={controlsDisabled}
onChange={(event) => void togglePlugins(event.currentTarget.checked)}
/>
<span className="switch-track" />
</label>
</div>
</section>
</div>
</div>
</div>
) : activeSubpage === 'network' ? (
<div
@@ -544,37 +518,6 @@ export function ConfigPanelPage() {
role="tabpanel"
aria-labelledby="config-subpage-tab-network"
>
<section className="panel config-setting-panel">
<div className="config-panel-heading">
<div className="config-heading-title">
<Route size={18} aria-hidden="true" />
<h2>{t('config.routing.title')}</h2>
</div>
<span className="state-pill" title={settings?.routingStrategy || undefined}>
{loading
? t('common.loading')
: settings === null
? t('common.unavailable')
: routingStrategyLabel(settings.routingStrategy, t)}
</span>
</div>
<div className="routing-segmented" role="group" aria-label={t('config.routing.title')}>
{ROUTING_OPTIONS.map((option) => (
<button
type="button"
key={option.value}
className={settings?.routingStrategy === option.value ? 'active' : ''}
aria-pressed={settings?.routingStrategy === option.value}
disabled={controlsDisabled}
onClick={() => void changeRoutingStrategy(option.value)}
title={option.value}
>
{t(option.labelKey)}
</button>
))}
</div>
</section>
<section className="panel config-network-panel">
<div className="config-panel-heading">
<div className="config-heading-title">
@@ -582,17 +525,11 @@ export function ConfigPanelPage() {
<h2>{t('config.network.title')}</h2>
</div>
<div className="config-heading-actions">
<span className={`state-pill ${settings !== null && !networkRoutingDirty ? 'success' : ''}`}>
{loading
? t('common.loading')
: settings === null
? t('common.unavailable')
: busyAction === 'network'
? t('config.network.saving')
: networkRoutingDirty
? t('config.network.unsaved')
: t('config.network.saved')}
</span>
{networkStatusLabel ? (
<span className={`state-pill ${networkStatusIsSaved ? 'success' : ''}`}>
{networkStatusLabel}
</span>
) : null}
<button
type="button"
className="primary-button compact-button"
@@ -607,8 +544,14 @@ export function ConfigPanelPage() {
</div>
</div>
<div className="config-network-grid">
<label className="config-network-field config-network-port-field">
<div className="config-network-sections">
<section className="config-network-section" aria-labelledby="config-network-section-title">
<div className="config-network-section-heading">
<Network size={16} aria-hidden="true" />
<h3 id="config-network-section-title">{t('config.network.networkSection')}</h3>
</div>
<div className="config-network-grid">
<label className="config-network-field config-network-port-field">
<span>{t('config.network.port')}</span>
<input
className={`config-network-input ${portError ? 'error' : ''}`}
@@ -621,6 +564,7 @@ export function ConfigPanelPage() {
aria-invalid={Boolean(portError)}
title={portError || t('config.network.portHint')}
onChange={(event) => {
setSavedStatusVisible(false);
setPortDraft(event.currentTarget.value.replace(/\D/g, '').slice(0, 5));
setPortError('');
}}
@@ -633,85 +577,136 @@ export function ConfigPanelPage() {
}}
/>
<small>{t('config.network.portHint')}</small>
</label>
</label>
<div className="config-network-field config-network-toggle">
<div>
<span>{t('config.network.allowLan')}</span>
<small>{t('config.network.allowLanHint')}</small>
<div className="config-network-field config-network-toggle">
<div>
<span>{t('config.network.allowLan')}</span>
<small>{t('config.network.allowLanHint')}</small>
</div>
<label className="switch-control" title={t('config.network.allowLan')}>
<input
type="checkbox"
aria-label={t('config.network.allowLan')}
checked={allowLanDraft}
disabled={controlsDisabled}
onChange={(event) => {
setSavedStatusVisible(false);
setAllowLanDraft(event.currentTarget.checked);
}}
/>
<span className="switch-track" />
</label>
</div>
<label className="config-network-field">
<span className="config-network-label">
<Link2 size={15} aria-hidden="true" />
{t('config.network.proxyUrl')}
</span>
<input
className="config-network-input"
type="text"
value={proxyUrlDraft}
disabled={controlsDisabled}
placeholder={t('config.network.proxyPlaceholder')}
onChange={(event) => {
setSavedStatusVisible(false);
setProxyUrlDraft(event.currentTarget.value);
}}
onKeyDown={(event) => {
if (event.key === 'Escape' && settings) {
setProxyUrlDraft(settings.proxyUrl);
event.currentTarget.blur();
}
}}
/>
<small>{t('config.network.proxyHint')}</small>
</label>
</div>
<label className="switch-control" title={t('config.network.allowLan')}>
<input
type="checkbox"
aria-label={t('config.network.allowLan')}
checked={allowLanDraft}
disabled={controlsDisabled}
onChange={(event) => setAllowLanDraft(event.currentTarget.checked)}
/>
<span className="switch-track" />
</label>
</div>
</section>
<label className="config-network-field">
<span className="config-network-label">
<Link2 size={15} aria-hidden="true" />
{t('config.network.proxyUrl')}
</span>
<input
className="config-network-input"
type="text"
value={proxyUrlDraft}
disabled={controlsDisabled}
placeholder={t('config.network.proxyPlaceholder')}
onChange={(event) => setProxyUrlDraft(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === 'Escape' && settings) {
setProxyUrlDraft(settings.proxyUrl);
event.currentTarget.blur();
}
}}
/>
<small>{t('config.network.proxyHint')}</small>
</label>
<div className="config-network-field config-network-toggle">
<div>
<span>{t('config.network.sessionAffinity')}</span>
<small>{t('config.network.sessionAffinityHint')}</small>
<section className="config-network-section" aria-labelledby="config-routing-section-title">
<div className="config-network-section-heading">
<Route size={16} aria-hidden="true" />
<h3 id="config-routing-section-title">{t('config.network.routingSection')}</h3>
</div>
<label className="switch-control" title={t('config.network.sessionAffinity')}>
<input
type="checkbox"
aria-label={t('config.network.sessionAffinity')}
checked={sessionAffinityDraft}
disabled={controlsDisabled}
onChange={(event) => setSessionAffinityDraft(event.currentTarget.checked)}
/>
<span className="switch-track" />
</label>
</div>
<div className="config-network-grid">
<div className="config-network-field config-network-toggle">
<div>
<span>{t('config.network.sessionAffinity')}</span>
<small>{t('config.network.sessionAffinityHint')}</small>
</div>
<label className="switch-control" title={t('config.network.sessionAffinity')}>
<input
type="checkbox"
aria-label={t('config.network.sessionAffinity')}
checked={sessionAffinityDraft}
disabled={controlsDisabled}
onChange={(event) => {
setSavedStatusVisible(false);
setSessionAffinityDraft(event.currentTarget.checked);
}}
/>
<span className="switch-track" />
</label>
</div>
<label className="config-network-field">
<span className="config-network-label">
<Clock3 size={15} aria-hidden="true" />
{t('config.network.sessionTtl')}
</span>
<input
className="config-network-input"
type="text"
value={sessionTtlDraft}
disabled={controlsDisabled}
placeholder="1h"
onChange={(event) => setSessionTtlDraft(event.currentTarget.value)}
onKeyDown={(event) => {
if (event.key === 'Escape' && settings) {
setSessionTtlDraft(settings.routingSessionAffinityTtl);
event.currentTarget.blur();
}
}}
/>
<small>{t('config.network.sessionTtlHint')}</small>
</label>
<label className="config-network-field">
<span className="config-network-label">
<Clock3 size={15} aria-hidden="true" />
{t('config.network.sessionTtl')}
</span>
<input
className="config-network-input"
type="text"
value={sessionTtlDraft}
disabled={controlsDisabled}
placeholder="1h"
onChange={(event) => {
setSavedStatusVisible(false);
setSessionTtlDraft(event.currentTarget.value);
}}
onKeyDown={(event) => {
if (event.key === 'Escape' && settings) {
setSessionTtlDraft(settings.routingSessionAffinityTtl);
event.currentTarget.blur();
}
}}
/>
<small>{t('config.network.sessionTtlHint')}</small>
</label>
<div className="config-network-field config-network-routing-field">
<span className="config-network-label">
<Route size={15} aria-hidden="true" />
{t('config.routing.title')}
</span>
<div className="routing-segmented" role="group" aria-label={t('config.routing.title')}>
{ROUTING_OPTIONS.map((option) => (
<button
type="button"
key={option.value}
className={settings?.routingStrategy === option.value ? 'active' : ''}
aria-pressed={settings?.routingStrategy === option.value}
disabled={controlsDisabled}
onClick={() => void changeRoutingStrategy(option.value)}
title={option.value}
>
{t(option.labelKey)}
</button>
))}
</div>
<small title={settings?.routingStrategy || undefined}>
{loading
? t('common.loading')
: settings === null
? t('common.unavailable')
: routingStrategyLabel(settings.routingStrategy, t)}
</small>
</div>
</div>
</section>
</div>
</section>
</div>
+36 -72
View File
@@ -853,24 +853,12 @@ input:focus-visible {
gap: 18px;
}
.config-workspace-grid {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
align-items: stretch;
gap: 18px;
height: 350px;
}
.config-subpage-panel {
display: grid;
min-width: 0;
gap: 18px;
}
.config-network-subpage > .config-setting-panel {
width: min(420px, 100%);
}
.control-panel {
grid-column: auto;
}
@@ -1494,8 +1482,7 @@ input:focus-visible {
transform: translateX(20px);
}
.config-keys-panel,
.config-setting-panel {
.config-keys-panel {
display: grid;
min-height: 0;
}
@@ -1506,30 +1493,46 @@ input:focus-visible {
height: 350px;
}
.config-side-stack {
display: grid;
grid-template-rows: repeat(2, 166px);
gap: 18px;
height: 350px;
}
.config-side-stack.single {
grid-template-rows: 166px;
align-content: start;
height: 166px;
}
.config-setting-panel {
grid-template-rows: 42px minmax(0, 1fr);
gap: 16px;
height: 166px;
}
.config-network-panel {
display: grid;
gap: 18px;
}
.config-network-sections {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
gap: 18px;
}
.config-network-section {
display: grid;
min-width: 0;
gap: 14px;
padding: 16px;
border: 1px solid var(--theme-d5d2cb);
border-radius: 8px;
background: var(--theme-fffdf8);
}
.config-network-section-heading {
display: flex;
align-items: center;
gap: 8px;
color: var(--theme-39352f);
}
.config-network-section-heading svg {
flex: none;
color: var(--theme-777067);
}
.config-network-section-heading h3 {
margin: 0;
font-size: 14px;
line-height: 1.4;
}
.config-network-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -1867,24 +1870,6 @@ input:focus-visible {
font-size: 13px;
}
.config-single-control {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 4px;
}
.config-single-control > span {
overflow: hidden;
color: var(--theme-2d2a26);
font-size: 14px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.routing-segmented {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -6695,11 +6680,6 @@ input:focus-visible {
grid-template-columns: 1fr;
}
.config-workspace-grid {
grid-template-columns: 1fr;
height: auto;
}
.provider-workbench {
grid-template-columns: 1fr;
}
@@ -6735,16 +6715,6 @@ input:focus-visible {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.config-side-stack {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: 166px;
height: 166px;
}
.config-side-stack.single {
grid-template-columns: 1fr;
}
.control-panel,
.version-panel {
grid-column: auto;
@@ -7002,12 +6972,6 @@ input:focus-visible {
.agent-modification-buttons.codex {
grid-template-columns: 1fr;
}
.config-side-stack {
grid-template-columns: 1fr;
grid-template-rows: repeat(2, 166px);
height: 350px;
}
}
@media (max-width: 520px) {