diff --git a/README.md b/README.md index b453f028..40879539 100644 --- a/README.md +++ b/README.md @@ -13,30 +13,30 @@ Run Claude Code / Codex / Gemini sessions locally and control them remotely thro - Track session progress with todo lists. - Supports multiple AI backends: Claude Code, Codex, and Gemini. -## Quickstart (single executable) +## Quickstart -1. Download the prebuilt `hapi` binary for your platform and put it on your PATH. - -2. Start the server on a machine you control: +1. Start the server on a machine you control: ```bash -hapi server +npx @twsxtd/hapi server ``` -3. If the server has no public IP, expose it over HTTPS: +> Alternatively, download the prebuilt binary from [Releases](https://github.com/tiann/hapi/releases) and run `hapi server`. + +2. If the server has no public IP, expose it over HTTPS: - Cloudflare Tunnel: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ - Tailscale: https://tailscale.com/kb/ -4. Run the CLI on the machine where you want sessions: +3. Run the CLI on the machine where you want sessions: ```bash # If the server is not on localhost:3006 export HAPI_BOT_URL="https://your-domain.example" -hapi +npx @twsxtd/hapi ``` -5. Open the UI in a browser at the server URL and log in with `CLI_API_TOKEN`. +4. Open the UI in a browser at the server URL and log in with `CLI_API_TOKEN`. ## Telegram Mini App (optional) @@ -65,9 +65,9 @@ ALLOWED_CHAT_IDS="12345678" ## Multi-agent support -- `hapi` - Start a Claude Code session. -- `hapi codex` - Start an OpenAI Codex session. -- `hapi gemini` - Start a Google Gemini session. +- `npx @twsxtd/hapi` - Start a Claude Code session. +- `npx @twsxtd/hapi codex` - Start an OpenAI Codex session. +- `npx @twsxtd/hapi gemini` - Start a Google Gemini session. ## CLI config file diff --git a/spinner.md b/spinner.md deleted file mode 100644 index 9de8afc3..00000000 --- a/spinner.md +++ /dev/null @@ -1,131 +0,0 @@ -# Loading 元素精简优化计划 - -## 一、当前 Loading 元素汇总 - -| 位置 | 文件 | 当前样式 | 展示时机 | -|------|------|----------|----------| -| 同步横幅 | `SyncingBanner.tsx` | 顶部固定横幅 + 旋转符号 | `isSyncing && isOnline` | -| 权限按钮 | `PermissionFooter.tsx:66-73` | SVG SpinnerIcon | `loading` 状态 | -| 认证加载 | `App.tsx:132-146` | 纯文本 "Loading…" / "Authorizing…" | `isAuthSourceLoading` / `isAuthLoading` | -| 会话加载 | `router.tsx:100-106` | 纯文本 "Loading session…" | `!session` | -| 消息加载 | `HappyThread.tsx:235-258` | 纯文本 "Loading..." + 按钮文本 | `isLoadingMessages` / `isLoadingMoreMessages` | -| Git 状态 | `files.tsx:313-318` | 纯文本 "Loading Git status..." / "Loading files..." | `gitLoading` / `searchResults.isLoading` | -| 文件内容 | `file.tsx:231-232` | 纯文本 "Loading file..." | `diffQuery.isLoading \|\| fileQuery.isLoading` | -| 登录按钮 | `LoginPrompt.tsx:77` | 按钮文本 "Signing in..." | `isLoading` | -| 创建会话 | `NewSession.tsx:119-122, 216` | "Loading machines..." / "Creating..." | `isPending` | -| 问卷提交 | `AskUserQuestionFooter.tsx:385` | 按钮文本 "Submitting…" | `loading` | - -## 二、问题 - -1. **样式不统一** - 纯文本 vs SpinnerIcon vs 旋转符号 -2. **SpinnerIcon 无法复用** - 定义在 PermissionFooter 内部 -3. **省略号不一致** - "..." vs "…" - -## 三、精简方案 - -**核心原则:Spinner 做基础元件,LoadingState 负责语义化加载** - -### 创建唯一的 Spinner 组件(可访问性就绪) - -```tsx -// web/src/components/Spinner.tsx -export function Spinner({ - size = 'md', - className, - label = 'Loading', -}: { - size?: 'sm' | 'md' | 'lg' - className?: string - label?: string -}) { - const sizeClasses = { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6' } - return ( - - - - - ) -} -``` - -### 新增 LoadingState(Spinner + 可选文案) - -```tsx -// web/src/components/LoadingState.tsx -export function LoadingState({ - label = 'Loading…', - className, - spinnerSize = 'md', -}: { - label?: string - className?: string - spinnerSize?: 'sm' | 'md' | 'lg' -}) { - return ( -
- - {label} -
- ) -} -``` - -### SyncingBanner 处理 - -**方案:不改,避免打扰用户** -- 保留顶部横幅与 `useSyncingState` 逻辑 -- 若需要统一视觉,仅替换内部图标为新 `Spinner` - -## 四、实施步骤 - -### Step 1: 创建 Spinner 组件(可访问性) -- [ ] 创建 `web/src/components/Spinner.tsx`,包含 `role="status"` 与 `aria-label` - -### Step 2: 新增 LoadingState 组件 -- [ ] 创建 `web/src/components/LoadingState.tsx` -- [ ] 统一使用 `Loading…`(省略号用 `…`) - -### Step 3: 替换加载场景(保留语义) -- [ ] `App.tsx` - 认证相关使用 `` -- [ ] `router.tsx` - 会话加载使用 `` -- [ ] `HappyThread.tsx` - 消息加载用 `` -- [ ] `files.tsx` - Git/文件列表加载用 `` -- [ ] `file.tsx` - 文件内容加载用 `` -- [ ] `PermissionFooter.tsx` - 使用新 `Spinner`,删除内部 `SpinnerIcon` - -### Step 4: 按钮加载保持可读标签 -- [ ] `LoginPrompt.tsx` / `NewSession.tsx` / `AskUserQuestionFooter.tsx` - 保留文字,旁边加 `Spinner size="sm"` -- [ ] 按钮添加 `aria-busy`,避免替换成纯图标 - -### Step 5: 列表/内容区优先 Skeleton 或占位 -- [ ] `HappyThread.tsx` - 消息列表加载改为 skeleton/placeholder -- [ ] `files.tsx` - 文件列表加载改为 skeleton/placeholder -- [ ] `file.tsx` - 文件内容加载改为 skeleton/placeholder - -### Step 6: SyncingBanner -- [ ] 保留 `SyncingBanner.tsx` -- [ ] 可选:内部图标替换为新 `Spinner`,不改交互与布局 - -## 五、关键文件 - -**新建:** -- `web/src/components/Spinner.tsx` -- `web/src/components/LoadingState.tsx` - -**修改:** -- `web/src/App.tsx` -- `web/src/router.tsx` -- `web/src/components/AssistantChat/HappyThread.tsx` -- `web/src/routes/sessions/files.tsx` -- `web/src/routes/sessions/file.tsx` -- `web/src/components/ToolCard/PermissionFooter.tsx` -- `web/src/components/LoginPrompt.tsx` -- `web/src/components/NewSession.tsx` -- `web/src/components/ToolCard/AskUserQuestionFooter.tsx` -- `web/src/components/SyncingBanner.tsx`