File size: 4,928 Bytes
f59fbe2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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 };
}