mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-07-09 17:18:24 +00:00
Refactor router config and request handling
This commit is contained in:
parent
9eec586e60
commit
ff1a58b6dc
11 changed files with 1138 additions and 453 deletions
4
.idea/shelf/_2025_7_27_00_07____.xml
generated
Normal file
4
.idea/shelf/_2025_7_27_00_07____.xml
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<changelist name="在进行更新之前于_2025_7_27_00_07_取消提交了更改_[更改]" date="1753546032560" recycled="true" deleted="true">
|
||||
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2025_7_27_00_07_取消提交了更改_[更改]/shelved.patch" />
|
||||
<option name="DESCRIPTION" value="在进行更新之前于 2025/7/27 00:07 取消提交了更改 [更改]" />
|
||||
</changelist>
|
||||
312
.idea/shelf/在进行更新之前于_2025_7_27_00_07_取消提交了更改_[更改]/shelved.patch
generated
Normal file
312
.idea/shelf/在进行更新之前于_2025_7_27_00_07_取消提交了更改_[更改]/shelved.patch
generated
Normal file
File diff suppressed because one or more lines are too long
79
.idea/workspace.xml
generated
Normal file
79
.idea/workspace.xml
generated
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="d8f55048-965f-4d85-9edd-2df060b36bda" name="更改" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/cli.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/cli.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/utils/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/utils/processCheck.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/processCheck.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory"><![CDATA[{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "musistudio"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="GithubPullRequestsUISettings"><![CDATA[{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "git@github.com:musistudio/claude-code-router.git",
|
||||
"accountId": "37e12a60-eff0-427f-98f9-9bd34a2cf265"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"customColor": "",
|
||||
"associatedIndex": 4
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="30Q3ZGTbLgOyQYD51sqypPN33wM" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-WS-251.26927.40" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="d8f55048-965f-4d85-9edd-2df060b36bda" name="更改" comment="" />
|
||||
<created>1753546022239</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1753546022239</updated>
|
||||
<workItem from="1753546023362" duration="485000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
1052
package-lock.json
generated
1052
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,7 @@
|
|||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@the-next-ai/ai-gateway": "file:../../next-ai/gateway",
|
||||
"@the-next-ai/ai-gateway": "file:../next-ai/ai-gateway",
|
||||
"node-forge": "^1.4.0",
|
||||
"sql.js": "^1.14.1",
|
||||
"undici": "^7.27.2"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export { IPC_CHANNELS } from "../shared/ipc-channels";
|
|||
export const APP_NAME = "Claude Code Router";
|
||||
export const CONFIGDIR = path.join(app.getPath("home"), ".claude-code-router");
|
||||
export const CONFIG_FILE = path.join(CONFIGDIR, "config.json");
|
||||
export const ONBOARDING_FINISHED_FILE = path.join(CONFIGDIR, ".onboard_finished");
|
||||
export const DATADIR = app.getPath("userData");
|
||||
export const API_KEYS_DB_FILE = path.join(DATADIR, "api-keys.sqlite");
|
||||
export const CERTDIR = path.join(DATADIR, "certs");
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { app, BrowserWindow, dialog, ipcMain, shell, type OpenDialogOptions } from "electron";
|
||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
import { builtInBrowserService } from "./built-in-browser";
|
||||
import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config";
|
||||
import { API_KEYS_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants";
|
||||
import { API_KEYS_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants";
|
||||
import { deepLinkService } from "./deep-link";
|
||||
import { gatewayService } from "./gateway/service";
|
||||
import { probeGatewayProvider } from "./provider-probe";
|
||||
|
|
@ -60,6 +60,7 @@ ipcMain.handle(IPC_CHANNELS.appGetInfo, () => {
|
|||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.appGetConfig, () => loadAppConfig());
|
||||
ipcMain.handle(IPC_CHANNELS.appGetOnboardingFinished, () => existsSync(ONBOARDING_FINISHED_FILE));
|
||||
ipcMain.handle(IPC_CHANNELS.appGetPendingProviderDeepLinks, () => deepLinkService.consumePendingProviderRequests());
|
||||
ipcMain.handle(IPC_CHANNELS.appGetAgentAnalysis, (_event, filter?: AgentAnalysisFilter) => getAgentAnalysis(filter));
|
||||
ipcMain.handle(IPC_CHANNELS.appGetGatewayStatus, () => gatewayService.getStatus());
|
||||
|
|
@ -142,6 +143,11 @@ ipcMain.handle(IPC_CHANNELS.appSaveApiKeys, async (_event, apiKeys: ApiKeyConfig
|
|||
await applyProfileIfServiceRunning(savedConfig, gatewayService.getStatus());
|
||||
return savedConfig;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.appSetOnboardingFinished, () => {
|
||||
mkdirSync(CONFIGDIR, { recursive: true });
|
||||
writeFileSync(ONBOARDING_FINISHED_FILE, `${new Date().toISOString()}\n`, "utf8");
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.appRestartGateway, async () => {
|
||||
const config = await loadAppConfig();
|
||||
const status = await gatewayService.start(config);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ contextBridge.exposeInMainWorld("ccr", {
|
|||
getAppInfo: () => ipcRenderer.invoke(IPC_CHANNELS.appGetInfo) as Promise<AppInfo>,
|
||||
getConfig: () => ipcRenderer.invoke(IPC_CHANNELS.appGetConfig) as Promise<AppConfig>,
|
||||
getGatewayStatus: () => ipcRenderer.invoke(IPC_CHANNELS.appGetGatewayStatus) as Promise<GatewayStatus>,
|
||||
getOnboardingFinished: () => ipcRenderer.invoke(IPC_CHANNELS.appGetOnboardingFinished) as Promise<boolean>,
|
||||
getPendingProviderDeepLinks: () => ipcRenderer.invoke(IPC_CHANNELS.appGetPendingProviderDeepLinks) as Promise<ProviderDeepLinkRequest[]>,
|
||||
getPluginMarketplace: () => ipcRenderer.invoke(IPC_CHANNELS.appGetPluginMarketplace) as Promise<PluginMarketplaceEntry[]>,
|
||||
getProxyCertificateStatus: () => ipcRenderer.invoke(IPC_CHANNELS.appGetProxyCertificateStatus) as Promise<ProxyCertificateStatus>,
|
||||
|
|
@ -50,6 +51,7 @@ contextBridge.exposeInMainWorld("ccr", {
|
|||
saveApiKeys: (apiKeys: ApiKeyConfig[]) => ipcRenderer.invoke(IPC_CHANNELS.appSaveApiKeys, apiKeys) as Promise<AppConfig>,
|
||||
saveConfig: (config: AppConfig) => ipcRenderer.invoke(IPC_CHANNELS.appSaveConfig, config) as Promise<AppConfig>,
|
||||
selectPluginDirectory: () => ipcRenderer.invoke(IPC_CHANNELS.appSelectPluginDirectory) as Promise<PluginDirectorySelection | undefined>,
|
||||
setOnboardingFinished: () => ipcRenderer.invoke(IPC_CHANNELS.appSetOnboardingFinished) as Promise<boolean>,
|
||||
setProxyNetworkCaptureEnabled: (enabled: boolean) => ipcRenderer.invoke(IPC_CHANNELS.appSetProxyNetworkCaptureEnabled, enabled) as Promise<ProxyNetworkSnapshot>,
|
||||
setTrayDetailOpen: (open: boolean, provider?: string) => ipcRenderer.invoke(IPC_CHANNELS.appSetTrayDetailOpen, open, provider) as Promise<void>,
|
||||
showMainWindow: () => ipcRenderer.invoke(IPC_CHANNELS.appShowMainWindow) as Promise<void>,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ type AppCopy = {
|
|||
};
|
||||
|
||||
const languagePreferenceStorageKey = "ccr.ui.language";
|
||||
const onboardingCompletionStorageKey = "ccr.onboarding.completed";
|
||||
|
||||
const appCopy: Record<ResolvedLanguage, AppCopy> = {
|
||||
en: {
|
||||
|
|
@ -839,7 +838,7 @@ const appCopy: Record<ResolvedLanguage, AppCopy> = {
|
|||
"System Proxy": "系统代理",
|
||||
"This action is applied immediately to the draft config and will auto-save with other changes.": "此操作会立即应用到草稿配置,并随其他变更自动保存。",
|
||||
"This provider link came from an external website. Review details before importing.": "这个供应商链接来自外部网站。导入前请确认下面的内容。",
|
||||
"Three steps to connect a provider to your agent.": "三步把供应商接入你的 Agent。",
|
||||
"Welcome to CCR": "欢迎使用CCR",
|
||||
"Trusted": "已信任",
|
||||
"Small fast model": "小模型",
|
||||
"Unknown": "未知",
|
||||
|
|
@ -1014,6 +1013,34 @@ const navigation: Array<{ icon: LucideIcon; id: NavigationId }> = [
|
|||
|
||||
const onboardingStepOrder: OnboardingStepId[] = ["provider", "profile", "enter"];
|
||||
|
||||
function isOnboardingProviderReady(config: AppConfig): boolean {
|
||||
return config.Providers.length > 0;
|
||||
}
|
||||
|
||||
function isOnboardingProfileReady(config: AppConfig): boolean {
|
||||
return config.profile.enabled && config.profile.profiles.some((profile) => profile.enabled);
|
||||
}
|
||||
|
||||
function getDefaultOnboardingStep(config: AppConfig): OnboardingStepId {
|
||||
if (!isOnboardingProviderReady(config)) {
|
||||
return "provider";
|
||||
}
|
||||
if (!isOnboardingProfileReady(config)) {
|
||||
return "profile";
|
||||
}
|
||||
return "enter";
|
||||
}
|
||||
|
||||
function getNextOnboardingStep(activeStep: OnboardingStepId, config: AppConfig): OnboardingStepId | undefined {
|
||||
const activeIndex = onboardingStepOrder.indexOf(activeStep);
|
||||
for (const step of onboardingStepOrder.slice(activeIndex + 1)) {
|
||||
if (step === "enter" || step === getDefaultOnboardingStep(config)) {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const motionEase = [0.22, 1, 0.36, 1] as const;
|
||||
const reducedMotionTransition = { duration: 0.12, ease: "easeOut" } as const;
|
||||
const pageSpringTransition = { damping: 34, mass: 0.78, stiffness: 420, type: "spring" } as const;
|
||||
|
|
@ -1536,10 +1563,12 @@ type AppToast = {
|
|||
};
|
||||
|
||||
function App() {
|
||||
const [activeView, setActiveView] = useState<ViewId>(() => readOnboardingCompleted() ? "overview" : "onboarding");
|
||||
const [onboardingStep, setOnboardingStep] = useState<OnboardingStepId>("provider");
|
||||
const [activeView, setActiveView] = useState<ViewId>("onboarding");
|
||||
const [onboardingStep, setOnboardingStep] = useState<OnboardingStepId>(() => getDefaultOnboardingStep(fallbackConfig));
|
||||
const [appInfo, setAppInfo] = useState<AppInfo>(fallbackInfo);
|
||||
const [draftConfig, setDraftConfig] = useState<AppConfig>(fallbackConfig);
|
||||
const [configLoaded, setConfigLoaded] = useState(() => !window.ccr);
|
||||
const [onboardingStatusLoaded, setOnboardingStatusLoaded] = useState(() => !window.ccr);
|
||||
const [gatewayStatus, setGatewayStatus] = useState<GatewayStatus>(fallbackGatewayStatus);
|
||||
const [proxyCertificateStatus, setProxyCertificateStatus] = useState<ProxyCertificateStatus>(fallbackProxyCertificateStatus);
|
||||
const [proxyNetworkSnapshot, setProxyNetworkSnapshot] = useState<ProxyNetworkSnapshot>(fallbackProxyNetworkSnapshot);
|
||||
|
|
@ -1661,7 +1690,16 @@ function App() {
|
|||
}
|
||||
|
||||
void window.ccr.getAppInfo().then(setAppInfo);
|
||||
void window.ccr.getConfig().then(syncConfigState);
|
||||
void window.ccr.getConfig()
|
||||
.then(syncConfigState)
|
||||
.catch(() => {
|
||||
// Fall back to the bundled defaults; the rest of the UI can still render.
|
||||
})
|
||||
.finally(() => setConfigLoaded(true));
|
||||
void window.ccr.getOnboardingFinished()
|
||||
.then((finished) => setActiveView(finished ? "overview" : "onboarding"))
|
||||
.catch(() => setActiveView("onboarding"))
|
||||
.finally(() => setOnboardingStatusLoaded(true));
|
||||
void window.ccr.getPluginMarketplace().then(setPluginMarketplace).catch(() => setPluginMarketplace([]));
|
||||
void window.ccr.getProxyCertificateStatus().then(setProxyCertificateStatus);
|
||||
const refreshRuntimeStatus = () => {
|
||||
|
|
@ -1907,6 +1945,18 @@ function App() {
|
|||
}
|
||||
}, [activeView, networkCaptureEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeView !== "onboarding" || !configLoaded || !onboardingStatusLoaded) {
|
||||
return;
|
||||
}
|
||||
const defaultStep = getDefaultOnboardingStep(draftConfig);
|
||||
const defaultIndex = onboardingStepOrder.indexOf(defaultStep);
|
||||
setOnboardingStep((current) => {
|
||||
const currentIndex = onboardingStepOrder.indexOf(current);
|
||||
return defaultIndex > currentIndex ? defaultStep : current;
|
||||
});
|
||||
}, [activeView, configLoaded, onboardingStatusLoaded, draftConfig]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (toastTimer.current !== undefined) {
|
||||
window.clearTimeout(toastTimer.current);
|
||||
|
|
@ -2237,7 +2287,7 @@ function App() {
|
|||
setProviderEditIndex(undefined);
|
||||
setProviderAddOpen(false);
|
||||
if (activeView === "onboarding") {
|
||||
setOnboardingStep("profile");
|
||||
setOnboardingStep(getDefaultOnboardingStep(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2277,7 +2327,7 @@ function App() {
|
|||
setProviderDeepLinkRequest(undefined);
|
||||
showToast(`${copy.text["Imported provider"] ?? "Imported provider"} ${importedProviderName}`.trim());
|
||||
if (activeView === "onboarding") {
|
||||
setOnboardingStep("profile");
|
||||
setOnboardingStep(getDefaultOnboardingStep(next));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -2909,8 +2959,15 @@ function App() {
|
|||
}
|
||||
}
|
||||
|
||||
function completeOnboarding() {
|
||||
persistOnboardingCompleted();
|
||||
async function completeOnboarding() {
|
||||
if (window.ccr) {
|
||||
try {
|
||||
await window.ccr.setOnboardingFinished();
|
||||
} catch (error) {
|
||||
setActionError(error instanceof Error ? error.message : String(error));
|
||||
return;
|
||||
}
|
||||
}
|
||||
setActiveView("overview");
|
||||
}
|
||||
|
||||
|
|
@ -3183,18 +3240,20 @@ function App() {
|
|||
{activeView === "onboarding" ? (
|
||||
<main className="relative flex min-h-0 min-w-0 flex-1 overflow-auto bg-background px-5 pb-5 pt-12 max-[720px]:px-3 max-[720px]:pb-3">
|
||||
<div className="app-drag absolute inset-x-0 top-0 z-10 h-10" />
|
||||
<OnboardingView
|
||||
activeStep={onboardingStep}
|
||||
config={draftConfig}
|
||||
endpoint={gatewayEndpoint}
|
||||
gatewayStatus={gatewayStatus}
|
||||
onAddProfile={openAddProfileDialog}
|
||||
onAddProvider={openAddProviderDialog}
|
||||
onComplete={completeOnboarding}
|
||||
onOpenProfiles={() => setActiveView("profile")}
|
||||
onOpenProviders={() => setActiveView("providers")}
|
||||
onSelectStep={setOnboardingStep}
|
||||
/>
|
||||
{configLoaded && onboardingStatusLoaded ? (
|
||||
<OnboardingView
|
||||
activeStep={onboardingStep}
|
||||
config={draftConfig}
|
||||
endpoint={gatewayEndpoint}
|
||||
gatewayStatus={gatewayStatus}
|
||||
onAddProfile={openAddProfileDialog}
|
||||
onAddProvider={openAddProviderDialog}
|
||||
onComplete={completeOnboarding}
|
||||
onOpenProfiles={() => setActiveView("profile")}
|
||||
onOpenProviders={() => setActiveView("providers")}
|
||||
onSelectStep={setOnboardingStep}
|
||||
/>
|
||||
) : null}
|
||||
</main>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -3748,13 +3807,13 @@ function OnboardingView({
|
|||
const t = useAppText();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [selectedAgent, setSelectedAgent] = useState<ProfileConfig["agent"]>("claude-code");
|
||||
const providerReady = config.Providers.length > 0;
|
||||
const profileReady = config.profile.enabled && config.profile.profiles.some((profile) => profile.enabled);
|
||||
const providerReady = isOnboardingProviderReady(config);
|
||||
const profileReady = isOnboardingProfileReady(config);
|
||||
const routeReady = providerReady && profileReady;
|
||||
const activeIndex = Math.max(0, onboardingStepOrder.indexOf(activeStep));
|
||||
const activeDetails = onboardingStepDetails[activeStep];
|
||||
const previousStep = onboardingStepOrder[activeIndex - 1];
|
||||
const nextStep = onboardingStepOrder[activeIndex + 1];
|
||||
const nextStep = getNextOnboardingStep(activeStep, config);
|
||||
const firstProvider = config.Providers[0];
|
||||
const providerNames = config.Providers.map((provider) => provider.name).filter(Boolean).slice(0, 3).join(", ");
|
||||
const selectedAgentProfiles = config.profile.profiles.filter((profile) => profile.agent === selectedAgent);
|
||||
|
|
@ -3791,7 +3850,7 @@ function OnboardingView({
|
|||
transition={{ duration: 0.16 }}
|
||||
>
|
||||
<div className="flex min-w-0 flex-col items-center gap-3 text-center">
|
||||
<h1 className="max-w-[720px] text-[24px] font-semibold tracking-normal text-foreground">{t("Three steps to connect a provider to your agent.")}</h1>
|
||||
<h1 className="max-w-[720px] text-[24px] font-semibold tracking-normal text-foreground">{t("Welcome to CCR")}</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-3">
|
||||
|
|
@ -10590,22 +10649,6 @@ function readLanguagePreference(): AppLanguagePreference {
|
|||
}
|
||||
}
|
||||
|
||||
function readOnboardingCompleted(): boolean {
|
||||
try {
|
||||
return window.localStorage.getItem(onboardingCompletionStorageKey) === "true";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function persistOnboardingCompleted() {
|
||||
try {
|
||||
window.localStorage.setItem(onboardingCompletionStorageKey, "true");
|
||||
} catch {
|
||||
// If storage is unavailable, onboarding can reappear on the next launch.
|
||||
}
|
||||
}
|
||||
|
||||
function persistLanguagePreference(language: AppLanguagePreference) {
|
||||
try {
|
||||
if (language === "system") {
|
||||
|
|
|
|||
2
src/renderer/types/electron.d.ts
vendored
2
src/renderer/types/electron.d.ts
vendored
|
|
@ -34,6 +34,7 @@ declare global {
|
|||
getAppInfo: () => Promise<AppInfo>;
|
||||
getConfig: () => Promise<AppConfig>;
|
||||
getGatewayStatus: () => Promise<GatewayStatus>;
|
||||
getOnboardingFinished: () => Promise<boolean>;
|
||||
getPendingProviderDeepLinks: () => Promise<ProviderDeepLinkRequest[]>;
|
||||
getPluginMarketplace: () => Promise<PluginMarketplaceEntry[]>;
|
||||
getProxyCertificateStatus: () => Promise<ProxyCertificateStatus>;
|
||||
|
|
@ -52,6 +53,7 @@ declare global {
|
|||
saveApiKeys: (apiKeys: ApiKeyConfig[]) => Promise<AppConfig>;
|
||||
saveConfig: (config: AppConfig) => Promise<AppConfig>;
|
||||
selectPluginDirectory: () => Promise<PluginDirectorySelection | undefined>;
|
||||
setOnboardingFinished: () => Promise<boolean>;
|
||||
setProxyNetworkCaptureEnabled: (enabled: boolean) => Promise<ProxyNetworkSnapshot>;
|
||||
setTrayDetailOpen: (open: boolean, provider?: string) => Promise<void>;
|
||||
showMainWindow: () => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export const IPC_CHANNELS = {
|
|||
appGetAgentAnalysis: "ccr:app:get-agent-analysis",
|
||||
appGetGatewayStatus: "ccr:app:get-gateway-status",
|
||||
appGetInfo: "ccr:app:get-info",
|
||||
appGetOnboardingFinished: "ccr:app:get-onboarding-finished",
|
||||
appGetPendingProviderDeepLinks: "ccr:app:get-pending-provider-deep-links",
|
||||
appGetProxyCertificateStatus: "ccr:app:get-proxy-certificate-status",
|
||||
appGetProxyNetworkCaptures: "ccr:app:get-proxy-network-captures",
|
||||
|
|
@ -27,6 +28,7 @@ export const IPC_CHANNELS = {
|
|||
appStartGateway: "ccr:app:start-gateway",
|
||||
appStopGateway: "ccr:app:stop-gateway",
|
||||
appSaveConfig: "ccr:app:save-config",
|
||||
appSetOnboardingFinished: "ccr:app:set-onboarding-finished",
|
||||
appSetTrayDetailOpen: "ccr:app:set-tray-detail-open",
|
||||
appSetProxyNetworkCaptureEnabled: "ccr:app:set-proxy-network-capture-enabled",
|
||||
appSelectPluginDirectory: "ccr:app:select-plugin-directory",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue