kimi-code / apps /vscode /webview-ui /src /hooks /useAppInit.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f59fbe2 verified
Raw
History Blame Contribute Delete
4.93 kB
import { useState, useEffect, useCallback } from "react";
import { bridge, Events } from "@/services";
import { requiresManagedProviderLogin, useSettingsStore } from "@/stores";
import type { ExtensionConfig } from "shared/types";
export type AppStatus = "loading" | "no-workspace" | "runtime-error" | "not-logged-in" | "no-models" | "ready";
export type ConfigErrorStatus = "loading" | "no-workspace" | "runtime-error" | "no-models";
export type AppViewResolution =
| { readonly view: "login" }
| {
readonly view: "status";
readonly status: ConfigErrorStatus;
/** True when the status screen must offer a path to the sign-in screen. */
readonly canGoToLogin: boolean;
}
| { readonly view: "main" };
/**
* Pure view router for App. The `no-models` status (a managed OAuth token
* exists but config.toml has no models — e.g. a first login whose model
* provisioning failed after the device flow already persisted the token)
* must always keep a path back to the sign-in screen: Reload alone cannot
* change the on-disk state, so without it the user is stranded and the
* login UI becomes unreachable.
*/
export function resolveAppView(input: {
readonly status: AppStatus;
readonly modelsCount: number;
readonly skippedLogin: boolean;
readonly showLogin: boolean;
}): AppViewResolution {
const { status, modelsCount, skippedLogin, showLogin } = input;
if (showLogin || (status === "not-logged-in" && !skippedLogin)) {
return { view: "login" };
}
if (skippedLogin && modelsCount === 0) {
return { view: "status", status: "no-models", canGoToLogin: true };
}
if (status !== "ready" && status !== "not-logged-in") {
return { view: "status", status, canGoToLogin: status === "no-models" };
}
return { view: "main" };
}
export interface AppInitState {
status: AppStatus;
errorMessage: string | null;
modelsCount: number;
refresh: () => void;
}
export function useAppInit(): AppInitState {
const [state, setState] = useState<Omit<AppInitState, "refresh">>({
status: "loading",
errorMessage: null,
modelsCount: 0,
});
const [initKey, setInitKey] = useState(0);
const { initModels, setExtensionConfig, setWireSlashCommands, setIsLoggedIn, setWorkspaceRoot } = useSettingsStore();
const refresh = useCallback(() => {
setState({ status: "loading", errorMessage: null, modelsCount: 0 });
setInitKey((k) => k + 1);
}, []);
useEffect(() => {
return bridge.on<{ config: ExtensionConfig; changedKeys: string[] }>(Events.ExtensionConfigChanged, ({ config }) => {
setExtensionConfig(config);
});
}, [setExtensionConfig, refresh]);
useEffect(() => {
let cancelled = false;
async function init() {
try {
const workspace = await bridge.checkWorkspace();
if (cancelled) {
return;
}
if (!workspace.hasWorkspace) {
setState({ status: "no-workspace", errorMessage: null, modelsCount: 0 });
return;
}
setWorkspaceRoot(workspace.workspaceRoot ?? workspace.path ?? null);
const [extensionConfig, slashCommands] = await Promise.all([
bridge.getExtensionConfig(),
bridge.getSlashCommands(),
]);
if (cancelled) {
return;
}
setExtensionConfig(extensionConfig);
setWireSlashCommands(slashCommands);
const [loginStatus, kimiConfig] = await Promise.all([bridge.checkLoginStatus(), bridge.getModels()]);
if (cancelled) {
return;
}
console.log("[AppInit] Login status:", loginStatus, "kimiConfig:", kimiConfig);
setIsLoggedIn(loginStatus.loggedIn);
initModels(kimiConfig.models, kimiConfig.defaultModel, kimiConfig.defaultThinking, kimiConfig.defaultThinkingEffort);
const modelsCount = kimiConfig.models?.length ?? 0;
if (modelsCount === 0 && !loginStatus.loggedIn) {
setState({ status: "not-logged-in", errorMessage: null, modelsCount });
return;
}
if (modelsCount === 0) {
setState({ status: "no-models", errorMessage: null, modelsCount: 0 });
return;
}
if (requiresManagedProviderLogin(kimiConfig.models, kimiConfig.defaultModel, loginStatus.loggedIn)) {
setState({ status: "not-logged-in", errorMessage: null, modelsCount });
return;
}
setState({ status: "ready", errorMessage: null, modelsCount });
} catch (err) {
if (!cancelled) {
setState({
status: "runtime-error",
errorMessage: err instanceof Error ? err.message : "Failed to initialize",
modelsCount: 0,
});
}
}
}
void init();
return () => {
cancelled = true;
};
}, [initKey, initModels, setExtensionConfig, setWireSlashCommands, setIsLoggedIn]);
return { ...state, refresh };
}