mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-09-21 06:06:24 +08:00
Merge pull request #321 from router-for-me/feat/config-editor-simple-full-mode
Feat/config editor simple full mode
This commit is contained in:
@@ -140,6 +140,9 @@
|
||||
.overviewHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
|
||||
@include mobile {
|
||||
@@ -172,6 +175,240 @@
|
||||
background: var(--warning-bg);
|
||||
}
|
||||
|
||||
.modeSwitch {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
padding: 3px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.modeButton {
|
||||
@include button-reset;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 30px;
|
||||
padding: 0 12px;
|
||||
border-radius: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.modeButtonActive {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 1px 2px color-mix(in srgb, var(--text-primary) 10%, transparent);
|
||||
}
|
||||
|
||||
// ── Global field search (P1) ────────────────────────────────────────────────
|
||||
.searchBox {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.searchControl {
|
||||
padding-right: 38px !important;
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--text-tertiary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.searchResults {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: calc(100% + 6px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 14px 32px color-mix(in srgb, var(--text-primary) 16%, transparent);
|
||||
}
|
||||
|
||||
.searchResultItem {
|
||||
@include button-reset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 9px 10px;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
transition: background-color 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard/pointer "active" option — the aria-activedescendant target.
|
||||
.searchResultItemActive {
|
||||
background: color-mix(in srgb, var(--text-primary) 6%, transparent);
|
||||
}
|
||||
|
||||
.searchResultLabel {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.searchResultQualifier {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.searchResultSection {
|
||||
flex: 0 0 auto;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.searchEmpty {
|
||||
padding: 12px 10px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Stateless wrapper around each searchable field; the search jump scrolls to and
|
||||
// pulse-highlights it.
|
||||
.fieldAnchor {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
scroll-margin-top: calc(var(--header-height, 64px) + 16px);
|
||||
}
|
||||
|
||||
.fieldHighlightActive {
|
||||
border-radius: 10px;
|
||||
animation: cfgFieldHighlight 1.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes cfgFieldHighlight {
|
||||
0% {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-primary) 55%, transparent);
|
||||
}
|
||||
55% {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-primary) 32%, transparent);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 3px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.simpleView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
max-width: 760px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.simpleForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// Wrap bare inputs so they read as cards consistent with toggleRow / API key blocks.
|
||||
.simpleField {
|
||||
padding: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.simpleBanner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--warning-border);
|
||||
border-radius: 8px;
|
||||
background: var(--warning-bg);
|
||||
color: var(--warning-text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.simpleBannerAction {
|
||||
@include button-reset;
|
||||
flex: 0 0 auto;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--warning-border);
|
||||
border-radius: 6px;
|
||||
color: var(--warning-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--warning-text) 10%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.simpleMore {
|
||||
@include button-reset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
padding: 12px 16px;
|
||||
border: 1px dashed var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--text-primary);
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.workspace {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,499 @@
|
||||
// Search index for the visual config editor's global "jump to field" search.
|
||||
//
|
||||
// IMPORTANT: this index is maintained by hand and is NOT what drives field
|
||||
// rendering — it only powers search. When you add, remove, or move a field in
|
||||
// VisualConfigEditor.tsx, update the matching entry here (and wrap the field's
|
||||
// JSX in <FieldAnchor fieldId="..."> using the same `fieldId`).
|
||||
|
||||
export type VisualSectionId =
|
||||
| 'connectivity'
|
||||
| 'network'
|
||||
| 'logging'
|
||||
| 'quota'
|
||||
| 'streaming'
|
||||
| 'advanced'
|
||||
| 'payload';
|
||||
|
||||
export interface ConfigFieldSearchEntry {
|
||||
/** Stable anchor id; matches FieldAnchor's `fieldId` and the rendered DOM id. */
|
||||
fieldId: string;
|
||||
sectionId: VisualSectionId;
|
||||
/** i18n key resolved with t() at search time so matching follows the active language. */
|
||||
labelKey: string;
|
||||
/** Optional secondary i18n key shown next to the label to disambiguate duplicates
|
||||
* (e.g. Claude vs Codex "User-Agent"). Also searchable. */
|
||||
qualifierKey?: string;
|
||||
/** Optional hint i18n key — searchable but not shown in results. */
|
||||
hintKey?: string;
|
||||
/** Backend YAML key aliases, e.g. ['proxy-url']. Static strings (language-agnostic). */
|
||||
yamlKeys?: string[];
|
||||
/** Extra synonyms to match against (language-agnostic, lowercase). */
|
||||
keywords?: string[];
|
||||
}
|
||||
|
||||
/** DOM id for a field anchor — kept in one place so the index and the anchors agree. */
|
||||
export const configFieldDomId = (fieldId: string) => `cfg-field-${fieldId}`;
|
||||
|
||||
type Translate = (key: string) => string;
|
||||
|
||||
// Compact helper: every label/hint key lives under config_management.visual.
|
||||
const L = (key: string) => `config_management.visual.${key}`;
|
||||
|
||||
export const CONFIG_FIELD_SEARCH_INDEX: ConfigFieldSearchEntry[] = [
|
||||
// ── connectivity ──────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'host',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.server.host'),
|
||||
yamlKeys: ['host'],
|
||||
},
|
||||
{
|
||||
fieldId: 'port',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.server.port'),
|
||||
yamlKeys: ['port'],
|
||||
},
|
||||
{
|
||||
fieldId: 'authDir',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.auth.auth_dir'),
|
||||
hintKey: L('sections.auth.auth_dir_hint'),
|
||||
yamlKeys: ['auth-dir'],
|
||||
},
|
||||
{
|
||||
fieldId: 'apiKeys',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('api_keys.label'),
|
||||
yamlKeys: ['api-keys'],
|
||||
keywords: ['api key', 'apikey', 'token'],
|
||||
},
|
||||
{
|
||||
fieldId: 'tlsEnable',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.tls.enable'),
|
||||
hintKey: L('sections.tls.enable_desc'),
|
||||
yamlKeys: ['tls'],
|
||||
keywords: ['tls', 'ssl', 'https'],
|
||||
},
|
||||
{
|
||||
fieldId: 'tlsCert',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.tls.cert'),
|
||||
yamlKeys: ['tls', 'cert'],
|
||||
keywords: ['tls', 'ssl', 'certificate'],
|
||||
},
|
||||
{
|
||||
fieldId: 'tlsKey',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.tls.key'),
|
||||
yamlKeys: ['tls', 'key'],
|
||||
keywords: ['tls', 'ssl', 'private key'],
|
||||
},
|
||||
{
|
||||
fieldId: 'rmAllowRemote',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.remote.allow_remote'),
|
||||
hintKey: L('sections.remote.allow_remote_desc'),
|
||||
yamlKeys: ['remote-management', 'allow-remote'],
|
||||
},
|
||||
{
|
||||
fieldId: 'rmDisableControlPanel',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.remote.disable_panel'),
|
||||
yamlKeys: ['remote-management', 'disable-control-panel'],
|
||||
},
|
||||
{
|
||||
fieldId: 'rmDisableAutoUpdatePanel',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.remote.disable_auto_update_panel'),
|
||||
yamlKeys: ['remote-management', 'disable-auto-update-panel'],
|
||||
},
|
||||
{
|
||||
fieldId: 'rmSecretKey',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.remote.secret_key'),
|
||||
yamlKeys: ['remote-management', 'secret-key'],
|
||||
},
|
||||
{
|
||||
fieldId: 'rmPanelRepo',
|
||||
sectionId: 'connectivity',
|
||||
labelKey: L('sections.remote.panel_repo'),
|
||||
yamlKeys: ['remote-management', 'panel-github-repository'],
|
||||
},
|
||||
// ── network ───────────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'proxyUrl',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.proxy_url'),
|
||||
yamlKeys: ['proxy-url'],
|
||||
},
|
||||
{
|
||||
fieldId: 'requestRetry',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.request_retry'),
|
||||
yamlKeys: ['request-retry'],
|
||||
},
|
||||
{
|
||||
fieldId: 'maxRetryCredentials',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.max_retry_credentials'),
|
||||
hintKey: L('sections.network.max_retry_credentials_hint'),
|
||||
yamlKeys: ['max-retry-credentials'],
|
||||
},
|
||||
{
|
||||
fieldId: 'maxRetryInterval',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.max_retry_interval'),
|
||||
yamlKeys: ['max-retry-interval'],
|
||||
},
|
||||
{
|
||||
fieldId: 'authAutoRefreshWorkers',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.auth_auto_refresh_workers'),
|
||||
hintKey: L('sections.network.auth_auto_refresh_workers_hint'),
|
||||
yamlKeys: ['auth-auto-refresh-workers'],
|
||||
},
|
||||
{
|
||||
fieldId: 'routingStrategy',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.routing_strategy'),
|
||||
hintKey: L('sections.network.routing_strategy_hint'),
|
||||
yamlKeys: ['routing', 'strategy'],
|
||||
keywords: ['round-robin', 'fill-first'],
|
||||
},
|
||||
{
|
||||
fieldId: 'disableImageGeneration',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.disable_image_generation'),
|
||||
hintKey: L('sections.network.disable_image_generation_hint'),
|
||||
yamlKeys: ['disable-image-generation'],
|
||||
},
|
||||
{
|
||||
fieldId: 'gptImage2BaseModel',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.gpt_image_2_base_model'),
|
||||
hintKey: L('sections.network.gpt_image_2_base_model_hint'),
|
||||
yamlKeys: ['gpt-image-2-base-model'],
|
||||
},
|
||||
{
|
||||
fieldId: 'routingSessionAffinityTTL',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.session_affinity_ttl'),
|
||||
yamlKeys: ['routing', 'session-affinity-ttl'],
|
||||
},
|
||||
{
|
||||
fieldId: 'forceModelPrefix',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.force_model_prefix'),
|
||||
hintKey: L('sections.network.force_model_prefix_desc'),
|
||||
yamlKeys: ['force-model-prefix'],
|
||||
},
|
||||
{
|
||||
fieldId: 'passthroughHeaders',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.passthrough_headers'),
|
||||
hintKey: L('sections.network.passthrough_headers_desc'),
|
||||
yamlKeys: ['passthrough-headers'],
|
||||
},
|
||||
{
|
||||
fieldId: 'disableCooling',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.disable_cooling'),
|
||||
hintKey: L('sections.network.disable_cooling_desc'),
|
||||
yamlKeys: ['disable-cooling'],
|
||||
},
|
||||
{
|
||||
fieldId: 'routingSessionAffinity',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.session_affinity'),
|
||||
yamlKeys: ['routing', 'session-affinity'],
|
||||
},
|
||||
{
|
||||
fieldId: 'wsAuth',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.ws_auth'),
|
||||
hintKey: L('sections.network.ws_auth_desc'),
|
||||
yamlKeys: ['ws-auth'],
|
||||
keywords: ['websocket'],
|
||||
},
|
||||
{
|
||||
fieldId: 'enableGeminiCliEndpoint',
|
||||
sectionId: 'network',
|
||||
labelKey: L('sections.network.enable_gemini_cli_endpoint'),
|
||||
hintKey: L('sections.network.enable_gemini_cli_endpoint_desc'),
|
||||
yamlKeys: ['enable-gemini-cli-endpoint'],
|
||||
},
|
||||
// ── logging ───────────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'debug',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.debug'),
|
||||
hintKey: L('sections.system.debug_desc'),
|
||||
yamlKeys: ['debug'],
|
||||
},
|
||||
{
|
||||
fieldId: 'commercialMode',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.commercial_mode'),
|
||||
hintKey: L('sections.system.commercial_mode_desc'),
|
||||
yamlKeys: ['commercial-mode'],
|
||||
},
|
||||
{
|
||||
fieldId: 'loggingToFile',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.logging_to_file'),
|
||||
hintKey: L('sections.system.logging_to_file_desc'),
|
||||
yamlKeys: ['logging-to-file'],
|
||||
},
|
||||
{
|
||||
fieldId: 'logsMaxTotalSizeMb',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.logs_max_size'),
|
||||
yamlKeys: ['logs-max-total-size-mb'],
|
||||
},
|
||||
{
|
||||
fieldId: 'errorLogsMaxFiles',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.error_logs_max_files'),
|
||||
yamlKeys: ['error-logs-max-files'],
|
||||
},
|
||||
{
|
||||
fieldId: 'redisUsageQueueRetentionSeconds',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.redis_usage_retention'),
|
||||
hintKey: L('sections.system.redis_usage_retention_hint'),
|
||||
yamlKeys: ['redis-usage-queue-retention-seconds'],
|
||||
},
|
||||
{
|
||||
fieldId: 'usageStatisticsEnabled',
|
||||
sectionId: 'logging',
|
||||
labelKey: L('sections.system.usage_statistics_enabled'),
|
||||
hintKey: L('sections.system.usage_statistics_enabled_desc'),
|
||||
yamlKeys: ['usage-statistics-enabled'],
|
||||
},
|
||||
// ── quota ─────────────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'quotaSwitchProject',
|
||||
sectionId: 'quota',
|
||||
labelKey: L('sections.quota.switch_project'),
|
||||
hintKey: L('sections.quota.switch_project_desc'),
|
||||
yamlKeys: ['quota-exceeded', 'switch-project'],
|
||||
},
|
||||
{
|
||||
fieldId: 'quotaSwitchPreviewModel',
|
||||
sectionId: 'quota',
|
||||
labelKey: L('sections.quota.switch_preview_model'),
|
||||
hintKey: L('sections.quota.switch_preview_model_desc'),
|
||||
yamlKeys: ['quota-exceeded', 'switch-preview-model'],
|
||||
},
|
||||
{
|
||||
fieldId: 'quotaAntigravityCredits',
|
||||
sectionId: 'quota',
|
||||
labelKey: L('sections.quota.antigravity_credits'),
|
||||
yamlKeys: ['quota-exceeded', 'antigravity-credits'],
|
||||
},
|
||||
// ── streaming ─────────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'streamingKeepaliveSeconds',
|
||||
sectionId: 'streaming',
|
||||
labelKey: L('sections.streaming.keepalive_seconds'),
|
||||
hintKey: L('sections.streaming.keepalive_hint'),
|
||||
yamlKeys: ['streaming', 'keepalive-seconds'],
|
||||
},
|
||||
{
|
||||
fieldId: 'streamingBootstrapRetries',
|
||||
sectionId: 'streaming',
|
||||
labelKey: L('sections.streaming.bootstrap_retries'),
|
||||
hintKey: L('sections.streaming.bootstrap_hint'),
|
||||
yamlKeys: ['streaming', 'bootstrap-retries'],
|
||||
},
|
||||
{
|
||||
fieldId: 'streamingNonstreamKeepalive',
|
||||
sectionId: 'streaming',
|
||||
labelKey: L('sections.streaming.nonstream_keepalive'),
|
||||
hintKey: L('sections.streaming.nonstream_keepalive_hint'),
|
||||
yamlKeys: ['streaming', 'nonstream-keepalive-interval'],
|
||||
},
|
||||
// ── advanced ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
fieldId: 'pluginsEnabled',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.system.plugins_enabled'),
|
||||
hintKey: L('sections.system.plugins_enabled_desc'),
|
||||
yamlKeys: ['plugins'],
|
||||
},
|
||||
{
|
||||
fieldId: 'pluginStoreSources',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.system.plugin_store_sources'),
|
||||
hintKey: L('sections.system.plugin_store_sources_hint'),
|
||||
yamlKeys: ['plugins', 'store-sources'],
|
||||
},
|
||||
{
|
||||
fieldId: 'antigravitySignatureCacheEnabled',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.system.antigravity_signature_cache'),
|
||||
hintKey: L('sections.system.antigravity_signature_cache_desc'),
|
||||
yamlKeys: ['antigravity-signature-cache-enabled'],
|
||||
},
|
||||
{
|
||||
fieldId: 'antigravitySignatureBypassStrict',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.system.antigravity_signature_strict'),
|
||||
hintKey: L('sections.system.antigravity_signature_strict_desc'),
|
||||
yamlKeys: ['antigravity-signature-bypass-strict'],
|
||||
},
|
||||
// Claude header defaults — qualifierKey disambiguates the shared "User-Agent" label.
|
||||
{
|
||||
fieldId: 'claudeHeaderUserAgent',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.user_agent'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'user-agent'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderPackageVersion',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.package_version'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'package-version'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderRuntimeVersion',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.runtime_version'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'runtime-version'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderOs',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.os'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'os'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderArch',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.arch'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'arch'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderTimeout',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.timeout'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
yamlKeys: ['claude-header-defaults', 'timeout'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
{
|
||||
fieldId: 'claudeHeaderStabilizeDeviceProfile',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.stabilize_device'),
|
||||
qualifierKey: L('sections.headers.claude_title'),
|
||||
hintKey: L('sections.headers.stabilize_device_desc'),
|
||||
yamlKeys: ['claude-header-defaults', 'stabilize-device-profile'],
|
||||
keywords: ['claude'],
|
||||
},
|
||||
// Codex header defaults.
|
||||
{
|
||||
fieldId: 'codexHeaderUserAgent',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.user_agent'),
|
||||
qualifierKey: L('sections.headers.codex_title'),
|
||||
yamlKeys: ['codex-header-defaults', 'user-agent'],
|
||||
keywords: ['codex'],
|
||||
},
|
||||
{
|
||||
fieldId: 'codexHeaderBetaFeatures',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.beta_features'),
|
||||
qualifierKey: L('sections.headers.codex_title'),
|
||||
yamlKeys: ['codex-header-defaults', 'beta-features'],
|
||||
keywords: ['codex'],
|
||||
},
|
||||
{
|
||||
fieldId: 'codexIdentityConfuse',
|
||||
sectionId: 'advanced',
|
||||
labelKey: L('sections.headers.codex_identity_confuse'),
|
||||
qualifierKey: L('sections.headers.codex_title'),
|
||||
hintKey: L('sections.headers.codex_identity_confuse_desc'),
|
||||
yamlKeys: ['codex-header-defaults', 'identity-confuse'],
|
||||
keywords: ['codex'],
|
||||
},
|
||||
// ── payload (coarse: one entry per rule group) ──────────────────────────────
|
||||
{
|
||||
fieldId: 'payloadDefaultRules',
|
||||
sectionId: 'payload',
|
||||
labelKey: L('sections.payload.default_rules'),
|
||||
hintKey: L('sections.payload.default_rules_desc'),
|
||||
keywords: ['payload', 'rule'],
|
||||
},
|
||||
{
|
||||
fieldId: 'payloadDefaultRawRules',
|
||||
sectionId: 'payload',
|
||||
labelKey: L('sections.payload.default_raw_rules'),
|
||||
hintKey: L('sections.payload.default_raw_rules_desc'),
|
||||
keywords: ['payload', 'rule', 'json'],
|
||||
},
|
||||
{
|
||||
fieldId: 'payloadOverrideRules',
|
||||
sectionId: 'payload',
|
||||
labelKey: L('sections.payload.override_rules'),
|
||||
hintKey: L('sections.payload.override_rules_desc'),
|
||||
keywords: ['payload', 'rule'],
|
||||
},
|
||||
{
|
||||
fieldId: 'payloadOverrideRawRules',
|
||||
sectionId: 'payload',
|
||||
labelKey: L('sections.payload.override_raw_rules'),
|
||||
hintKey: L('sections.payload.override_raw_rules_desc'),
|
||||
keywords: ['payload', 'rule', 'json'],
|
||||
},
|
||||
{
|
||||
fieldId: 'payloadFilterRules',
|
||||
sectionId: 'payload',
|
||||
labelKey: L('sections.payload.filter_rules'),
|
||||
hintKey: L('sections.payload.filter_rules_desc'),
|
||||
keywords: ['payload', 'rule', 'filter'],
|
||||
},
|
||||
];
|
||||
|
||||
const MAX_RESULTS = 8;
|
||||
|
||||
/**
|
||||
* Lowercase substring search over label + qualifier + hint + YAML keys + keywords.
|
||||
* Returns the best ~8 matches, label/qualifier hits ranked above alias-only hits.
|
||||
*/
|
||||
export function searchConfigFields(query: string, t: Translate): ConfigFieldSearchEntry[] {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return [];
|
||||
|
||||
const scored: { entry: ConfigFieldSearchEntry; score: number }[] = [];
|
||||
|
||||
for (const entry of CONFIG_FIELD_SEARCH_INDEX) {
|
||||
const label = t(entry.labelKey).toLowerCase();
|
||||
const qualifier = entry.qualifierKey ? t(entry.qualifierKey).toLowerCase() : '';
|
||||
const hint = entry.hintKey ? t(entry.hintKey).toLowerCase() : '';
|
||||
const yaml = (entry.yamlKeys ?? []).join(' ').toLowerCase();
|
||||
const keywords = (entry.keywords ?? []).join(' ').toLowerCase();
|
||||
|
||||
let score = Number.POSITIVE_INFINITY;
|
||||
if (label.startsWith(q)) score = 0;
|
||||
else if (label.includes(q)) score = 1;
|
||||
else if (qualifier.includes(q) || keywords.includes(q)) score = 2;
|
||||
else if (yaml.includes(q)) score = 3;
|
||||
else if (hint.includes(q)) score = 4;
|
||||
|
||||
if (Number.isFinite(score)) scored.push({ entry, score });
|
||||
}
|
||||
|
||||
scored.sort((a, b) => a.score - b.score);
|
||||
return scored.slice(0, MAX_RESULTS).map((item) => item.entry);
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||
import {
|
||||
useState,
|
||||
type HTMLAttributes,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { IconChevronDown } from '../icons';
|
||||
import styles from './Collapsible.module.scss';
|
||||
|
||||
@@ -22,12 +27,23 @@ export function Collapsible({
|
||||
className,
|
||||
...rest
|
||||
}: PropsWithChildren<CollapsibleProps>) {
|
||||
const detailsProps =
|
||||
open !== undefined ? { open } : { defaultOpen };
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
||||
const resolvedOpen = open ?? uncontrolledOpen;
|
||||
const cls = [styles.root, className].filter(Boolean).join(' ');
|
||||
const contentCls = flush ? styles.contentFlush : styles.content;
|
||||
|
||||
return (
|
||||
<details className={cls} onToggle={onToggle} {...detailsProps} {...rest}>
|
||||
<details
|
||||
className={cls}
|
||||
open={resolvedOpen}
|
||||
onToggle={(event) => {
|
||||
if (open === undefined) {
|
||||
setUncontrolledOpen(event.currentTarget.open);
|
||||
}
|
||||
onToggle?.(event);
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<summary className={styles.summary}>
|
||||
<span className={styles.summaryLabel}>
|
||||
<span>{label}</span>
|
||||
|
||||
@@ -909,7 +909,33 @@
|
||||
"visual": {
|
||||
"notice": "Visual mode covers common fields. Review or edit unsupported config.yaml entries in source mode.",
|
||||
"quick_jump": "Quick Jump",
|
||||
"mode": {
|
||||
"simple": "Simple",
|
||||
"full": "Full",
|
||||
"label": "Editor mode",
|
||||
"more_settings": "Switch to full mode to see all {{total}} sections",
|
||||
"validation_banner": "Some advanced settings have validation errors and are blocking save. Switch to full mode to fix them.",
|
||||
"switch_to_full": "Switch to full mode"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search settings (label or YAML key)",
|
||||
"no_results": "No matching settings"
|
||||
},
|
||||
"sections": {
|
||||
"connectivity": {
|
||||
"title": "Access & Authentication",
|
||||
"description": "Server address, port, auth directory, and API keys"
|
||||
},
|
||||
"logging": {
|
||||
"title": "Logging & Diagnostics",
|
||||
"description": "Debug, log output, and usage statistics"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced & Experimental",
|
||||
"description": "Header spoofing, signature cache, plugin sources, and other rarely changed settings",
|
||||
"plugins_title": "Plugins",
|
||||
"signature_title": "Antigravity Signature"
|
||||
},
|
||||
"server": {
|
||||
"title": "Server Configuration",
|
||||
"description": "Basic server settings",
|
||||
|
||||
@@ -896,7 +896,33 @@
|
||||
"visual": {
|
||||
"notice": "Визуальный режим охватывает основные поля. Остальные параметры config.yaml по-прежнему нужно проверять или редактировать в режиме исходника.",
|
||||
"quick_jump": "Быстрый переход",
|
||||
"mode": {
|
||||
"simple": "Простой",
|
||||
"full": "Полный",
|
||||
"label": "Режим редактора",
|
||||
"more_settings": "Переключитесь в полный режим, чтобы увидеть все разделы ({{total}})",
|
||||
"validation_banner": "В некоторых дополнительных параметрах есть ошибки проверки, сохранение заблокировано. Переключитесь в полный режим, чтобы исправить их.",
|
||||
"switch_to_full": "Перейти в полный режим"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Поиск настроек (по названию или ключу YAML)",
|
||||
"no_results": "Нет подходящих настроек"
|
||||
},
|
||||
"sections": {
|
||||
"connectivity": {
|
||||
"title": "Доступ и аутентификация",
|
||||
"description": "Адрес сервера, порт, каталог аутентификации и ключи API"
|
||||
},
|
||||
"logging": {
|
||||
"title": "Логи и диагностика",
|
||||
"description": "Отладка, вывод логов и статистика использования"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Дополнительно и эксперименты",
|
||||
"description": "Подмена заголовков, кэш подписей, источники плагинов и другие редко изменяемые настройки",
|
||||
"plugins_title": "Плагины",
|
||||
"signature_title": "Подпись Antigravity"
|
||||
},
|
||||
"server": {
|
||||
"title": "Настройки сервера",
|
||||
"description": "Базовые параметры сервера",
|
||||
|
||||
@@ -909,7 +909,33 @@
|
||||
"visual": {
|
||||
"notice": "可视化模式覆盖常用字段,未覆盖的配置仍需在源文件模式中查看或编辑。",
|
||||
"quick_jump": "快速跳转",
|
||||
"mode": {
|
||||
"simple": "简单",
|
||||
"full": "完整",
|
||||
"label": "编辑模式",
|
||||
"more_settings": "切换到完整模式,查看全部 {{total}} 个配置分区",
|
||||
"validation_banner": "部分高级配置项存在校验错误,已阻止保存。请切换到完整模式进行修复。",
|
||||
"switch_to_full": "切换完整模式"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "搜索配置项(标签或 YAML 键名)",
|
||||
"no_results": "没有匹配的配置项"
|
||||
},
|
||||
"sections": {
|
||||
"connectivity": {
|
||||
"title": "接入与认证",
|
||||
"description": "服务地址、端口、认证目录与 API 密钥"
|
||||
},
|
||||
"logging": {
|
||||
"title": "日志与诊断",
|
||||
"description": "调试、日志输出与使用统计"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高级与实验",
|
||||
"description": "请求头伪装、签名缓存、插件源等不常修改的设置",
|
||||
"plugins_title": "插件",
|
||||
"signature_title": "Antigravity 签名"
|
||||
},
|
||||
"server": {
|
||||
"title": "服务器配置",
|
||||
"description": "基础服务器设置",
|
||||
|
||||
@@ -935,7 +935,33 @@
|
||||
"visual": {
|
||||
"notice": "視覺化模式涵蓋常用欄位,未涵蓋的設定仍需在原始檔模式中查看或編輯。",
|
||||
"quick_jump": "快速跳轉",
|
||||
"mode": {
|
||||
"simple": "簡單",
|
||||
"full": "完整",
|
||||
"label": "編輯模式",
|
||||
"more_settings": "切換到完整模式,檢視全部 {{total}} 個設定分區",
|
||||
"validation_banner": "部分進階設定項存在驗證錯誤,已阻止儲存。請切換到完整模式進行修正。",
|
||||
"switch_to_full": "切換完整模式"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "搜尋設定項(標籤或 YAML 鍵名)",
|
||||
"no_results": "沒有符合的設定項"
|
||||
},
|
||||
"sections": {
|
||||
"connectivity": {
|
||||
"title": "接入與認證",
|
||||
"description": "服務位址、連接埠、認證目錄與 API 金鑰"
|
||||
},
|
||||
"logging": {
|
||||
"title": "日誌與診斷",
|
||||
"description": "除錯、日誌輸出與使用統計"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "進階與實驗",
|
||||
"description": "請求標頭偽裝、簽章快取、外掛來源等不常修改的設定",
|
||||
"plugins_title": "外掛",
|
||||
"signature_title": "Antigravity 簽章"
|
||||
},
|
||||
"server": {
|
||||
"title": "伺服器設定",
|
||||
"description": "基本伺服器設定",
|
||||
|
||||
Reference in New Issue
Block a user