Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/wizard-gui/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
{
"title": "CoreInfra Wizard",
"create": false,
"width": 1000,
"height": 700
"width": 1100,
"height": 720
}
],
"security": {
Expand Down
10 changes: 10 additions & 0 deletions crates/wizard-gui/wizard-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/wizard-gui/wizard-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@tauri-apps/api": "^2.11.1",
"@tauri-apps/plugin-log": "^2.9.2",
"clsx": "^2.1.1",
Expand Down
69 changes: 30 additions & 39 deletions crates/wizard-gui/wizard-ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,51 @@
import { AgentList } from "./agents/AgentList";
import { Text } from "./components/catalyst/text";
import { useEffect, useState } from "react";
import { AppSidebar } from "./AppSidebar";
import { AgentPage } from "./agents/AgentPage";
import { type AgentId, useAgentState } from "./agents/agents_state";
import { useProxy } from "./agents/proxy";
import { AuthLayout } from "./components/catalyst/auth-layout";
import { Badge } from "./components/catalyst/badge";
import { SidebarLayout } from "./components/catalyst/sidebar-layout";
import { ErrorText } from "./components/ErrorText";
import { HubAccount } from "./settings/HubAccount";
import { OpenLogsButton } from "./logs/OpenLogsButton";
import { useAgentState } from "./agents/agents_state";
import { useHubAccountControl } from "./settings/HubAccount";
import { useUpdateState } from "./updates/update";
import { UpdateScreen } from "./updates/UpdateScreen";
import { UpdateTile } from "./updates/UpdateTile";

function AgentPanel() {
const state = useAgentState();

if (state.status === "loading") return <Text>Загрузка...</Text>;
if (state.status === "error") return <ErrorText>Не удалось загрузить состояние агентов: {state.message}</ErrorText>;

return <AgentList agents={state.data} />;
}

function App() {
const update = useUpdateState();
const agents = useAgentState();
const proxy = useProxy();
const account = useHubAccountControl();
const [page, setPage] = useState<AgentId>("claude_desktop");
const state = update.state;

// Pages share the window scroll; start each one from the top.
useEffect(() => {
window.scrollTo(0, 0);
}, [page]);

if (update.error === undefined && (!state || state.status === "checking" || state.status === "installing")) {
return (
<main className={!state || state.status === "checking"
? "mx-auto flex min-h-dvh max-w-6xl flex-col gap-6 px-6 pt-2 pb-6 sm:px-8 sm:pb-8"
: "flex min-h-dvh flex-col gap-6 p-6"}
>
<div className={state?.status === "installing" ? "flex flex-1 items-center justify-center" : undefined}>
<AuthLayout>
<div className="flex flex-col items-center gap-8">
<UpdateScreen state={state} />
</div>
<div className="mt-auto shrink-0">
<OpenLogsButton />
</div>
</main>
</AuthLayout>
);
}

const updateTile = update.error !== undefined || state?.status === "failed" || state?.status === "available" ? (
<UpdateTile state={update} />
) : null;

return (
<main className="mx-auto flex min-h-dvh flex-col gap-6 px-6 pt-2 pb-6 sm:px-8 sm:pb-8">
{/* Keep inline-size containment off the auto-fit grid to avoid collapsed columns in WebKit. */}
<div className="@container">
<div className="grid grid-cols-[repeat(auto-fit,minmax(350px,1fr))] gap-6">
<HubAccount className="@min-[724px]:col-span-2" />
{updateTile}
<AgentPanel />
</div>
</div>
<div className="mt-auto shrink-0">
<OpenLogsButton />
</div>
</main>
<SidebarLayout
navbar={null}
sidebar={<AppSidebar page={page} onSelect={setPage} agents={agents.status === "ready" ? agents.data : undefined}
proxy={proxy} account={account} update={update} />}
>
{agents.status === "loading" && <Badge color="sky" className="animate-pulse">Загрузка…</Badge>}
{agents.status === "error" && <ErrorText>Не удалось загрузить состояние агентов: {agents.message}</ErrorText>}
{agents.status === "ready" && <AgentPage agents={agents.data} page={page} proxy={proxy} account={account} />}
</SidebarLayout>
);
}

Expand Down
76 changes: 76 additions & 0 deletions crates/wizard-gui/wizard-ui/src/AppSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { ComponentType, ComponentPropsWithoutRef } from "react";
import { useClose } from "@headlessui/react";
import type { AgentId, AgentStates } from "./agents/agents_state";
import type { useProxy } from "./agents/proxy";
import { AppLogo } from "./components/AppLogo";
import { ClaudeIcon, OpenAiIcon, OpenCodeIcon, PiIcon } from "./components/BrandIcons";
import { Badge } from "./components/catalyst/badge";
import { Sidebar, SidebarBody, SidebarFooter, SidebarHeader, SidebarHeading, SidebarItem, SidebarLabel, SidebarSection, SidebarSpacer } from "./components/catalyst/sidebar";
import { OpenLogsItem } from "./logs/OpenLogsButton";
import { type HubAccountControl, HubAccountMenu } from "./settings/HubAccount";
import type { UpdateObservation } from "./updates/update";
import { UpdateItem } from "./updates/UpdateItem";

// Short routing state of an agent, if it is worth showing next to its name.
function AgentBadge({ agents, id, proxy }: { agents: AgentStates; id: AgentId; proxy: ReturnType<typeof useProxy> }) {
const agent = agents[id];
if (agent.status === "error") return <Badge color="red" className="ml-auto">Ошибка</Badge>;
if (agent.status === "not_found") return null;

const mode = agent.data.routing_mode;
const chatGptProxyStopped = agents.codex.status === "found" && agents.codex.data.desktop.status === "found"
&& proxy.proxy?.chatgpt === false;
if (mode === "disabled") return null;
if (id === "codex" && chatGptProxyStopped) return <Badge color="amber" className="ml-auto">Прокси</Badge>;
return <Badge color="green" className="ml-auto">{mode === "sub" ? "Подписка" : "Hub"}</Badge>;
}

export function AppSidebar({ page, onSelect, agents, proxy, account, update }: {
page: AgentId;
onSelect: (page: AgentId) => void;
agents: AgentStates | undefined;
proxy: ReturnType<typeof useProxy>;
account: HubAccountControl;
update: UpdateObservation;
}) {
// Closes the sidebar dialog on narrow windows; does nothing on the desktop layout.
const close = useClose();

function item(id: AgentId, label: string, Icon: ComponentType<ComponentPropsWithoutRef<"svg">>) {
return (
<SidebarItem current={page === id} aria-current={page === id ? "page" : undefined} onClick={() => { onSelect(id); close(); }}>
<Icon />
<SidebarLabel>{label}</SidebarLabel>
{agents && <AgentBadge agents={agents} id={id} proxy={proxy} />}
</SidebarItem>
);
}

return (
<Sidebar>
<SidebarHeader>
<div className="px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1.5)-1px)]" role="img" aria-label="CoreInfra Wizard">
<AppLogo className="h-9 max-w-full" />
</div>
</SidebarHeader>
<SidebarBody>
<SidebarSection>
<SidebarHeading className="text-zinc-700 dark:text-zinc-200!">Агенты</SidebarHeading>
{item("claude_desktop", "Claude Desktop", ClaudeIcon)}
{item("claude", "Claude Code", ClaudeIcon)}
{item("codex", "Codex", OpenAiIcon)}
{item("opencode", "OpenCode", OpenCodeIcon)}
{item("pi", "Pi", PiIcon)}
</SidebarSection>
<SidebarSpacer />
<SidebarSection>
<UpdateItem state={update} />
<OpenLogsItem />
</SidebarSection>
</SidebarBody>
<SidebarFooter>
<HubAccountMenu hub={account} />
</SidebarFooter>
</Sidebar>
);
}
Loading
Loading