Download apps/vis/web/src/components/layout/AppShell.tsx from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 4.99 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vis/web/src/components/layout/AppShell.tsx
- Command line
-
hf download hf://SaylorTwift/kimi-code/apps/vis/web/src/components/layout/AppShell.tsx
-
curl -L -o AppShell.tsx https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vis/web/src/components/layout/AppShell.tsx
4.99 kB
| import type { ReactNode } from 'react'; | |
| import { useQueryClient } from '@tanstack/react-query'; | |
| import { Link } from 'react-router-dom'; | |
| import { SessionRail } from '../sessions/SessionRail'; | |
| import { ZipDropOverlay } from '../shared/ZipDropOverlay'; | |
| import { useTheme, type ThemeChoice, type ResolvedTheme } from '../../hooks/useTheme'; | |
| interface AppShellProps { | |
| children: ReactNode; | |
| } | |
| export function AppShell({ children }: AppShellProps) { | |
| const qc = useQueryClient(); | |
| const { choice, resolved, cycle } = useTheme(); | |
| return ( | |
| <div className="flex h-full flex-col"> | |
| <header className="flex h-10 shrink-0 items-center justify-between border-b border-border bg-surface-1 px-4"> | |
| <Link to="/" className="flex items-center gap-2"> | |
| <LogoMark /> | |
| <span className="font-mono text-[12px] uppercase tracking-[0.12em] text-fg-0"> | |
| kimi <span className="text-fg-2">vis</span> | |
| </span> | |
| <span className="ml-2 font-mono text-[10px] uppercase tracking-[0.12em] text-fg-3"> | |
| debug · local files | |
| </span> | |
| </Link> | |
| <div className="flex items-center gap-2 text-[11px] text-fg-2"> | |
| <ThemeToggle choice={choice} resolved={resolved} onCycle={cycle} /> | |
| <button | |
| onClick={() => { | |
| void qc.invalidateQueries(); | |
| }} | |
| className="flex items-center gap-1.5 border border-border px-2 py-0.5 font-mono text-[11px] text-fg-1 transition-colors hover:border-border-strong hover:text-fg-0" | |
| title="Refresh — re-read all session data from disk" | |
| > | |
| <RefreshIcon /> | |
| refresh | |
| </button> | |
| </div> | |
| </header> | |
| <div className="flex min-h-0 flex-1"> | |
| <SessionRail /> | |
| {/* min-w-0 lets the main column shrink below its content's intrinsic | |
| width; without it a flex child defaults to min-width:auto and wide | |
| tab content (e.g. the Timeline's flex-wrap rows) blows the layout | |
| out horizontally instead of wrapping. */} | |
| <main className="flex min-h-0 min-w-0 flex-1 flex-col">{children}</main> | |
| </div> | |
| <ZipDropOverlay /> | |
| </div> | |
| ); | |
| } | |
| function ThemeToggle({ | |
| choice, | |
| resolved, | |
| onCycle, | |
| }: { | |
| choice: ThemeChoice; | |
| resolved: ResolvedTheme; | |
| onCycle: () => void; | |
| }) { | |
| const label = choice === 'auto' ? `auto · ${resolved}` : choice; | |
| const title = `Theme: ${label}. Click to cycle (auto → light → dark → auto).`; | |
| return ( | |
| <button | |
| onClick={onCycle} | |
| className="flex items-center gap-1.5 border border-border px-2 py-0.5 font-mono text-[11px] text-fg-1 transition-colors hover:border-border-strong hover:text-fg-0" | |
| title={title} | |
| aria-label={`Theme ${label}`} | |
| > | |
| {choice === 'auto' ? <AutoIcon /> : resolved === 'light' ? <SunIcon /> : <MoonIcon />} | |
| <span className="tabular">{label}</span> | |
| </button> | |
| ); | |
| } | |
| function LogoMark() { | |
| return ( | |
| <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"> | |
| <rect x="1" y="1" width="6" height="6" fill="var(--color-cat-conversation)" /> | |
| <rect x="9" y="1" width="6" height="6" fill="var(--color-cat-subagent)" /> | |
| <rect x="1" y="9" width="6" height="6" fill="var(--color-cat-ephemeral)" /> | |
| <rect x="9" y="9" width="6" height="6" fill="var(--color-cat-approval)" /> | |
| </svg> | |
| ); | |
| } | |
| function RefreshIcon() { | |
| return ( | |
| <svg width="10" height="10" viewBox="0 0 12 12" aria-hidden="true"> | |
| <path | |
| d="M10 3 A5 5 0 1 0 10.8 7" | |
| stroke="currentColor" | |
| strokeWidth="1.2" | |
| fill="none" | |
| strokeLinecap="square" | |
| /> | |
| <path d="M7 1 L10 3 L7 5" stroke="currentColor" strokeWidth="1.2" fill="none" strokeLinecap="square" strokeLinejoin="miter" /> | |
| </svg> | |
| ); | |
| } | |
| function SunIcon() { | |
| return ( | |
| <svg width="11" height="11" viewBox="0 0 12 12" aria-hidden="true"> | |
| <circle cx="6" cy="6" r="2" fill="currentColor" /> | |
| <g stroke="currentColor" strokeWidth="1" strokeLinecap="round"> | |
| <line x1="6" y1="0.5" x2="6" y2="2" /> | |
| <line x1="6" y1="10" x2="6" y2="11.5" /> | |
| <line x1="0.5" y1="6" x2="2" y2="6" /> | |
| <line x1="10" y1="6" x2="11.5" y2="6" /> | |
| <line x1="2" y1="2" x2="3" y2="3" /> | |
| <line x1="9" y1="9" x2="10" y2="10" /> | |
| <line x1="10" y1="2" x2="9" y2="3" /> | |
| <line x1="3" y1="9" x2="2" y2="10" /> | |
| </g> | |
| </svg> | |
| ); | |
| } | |
| function MoonIcon() { | |
| return ( | |
| <svg width="11" height="11" viewBox="0 0 12 12" aria-hidden="true"> | |
| <path | |
| d="M9 7.5 A4.5 4.5 0 1 1 4.5 3 A3.6 3.6 0 0 0 9 7.5 Z" | |
| fill="currentColor" | |
| /> | |
| </svg> | |
| ); | |
| } | |
| function AutoIcon() { | |
| return ( | |
| <svg width="11" height="11" viewBox="0 0 12 12" aria-hidden="true"> | |
| <path | |
| d="M6 1 A5 5 0 1 1 6 11 Z" | |
| fill="currentColor" | |
| /> | |
| <circle cx="6" cy="6" r="4.5" fill="none" stroke="currentColor" strokeWidth="1" /> | |
| </svg> | |
| ); | |
| } | |