Commit graph

60 commits

Author SHA1 Message Date
Naiyuan Qing
ef4e57ffdd refactor(sdk): unify message types with pi-ai source of truth
Replace hand-written message/content types in @multica/sdk with
`import type` from @mariozechner/pi-ai and @mariozechner/pi-agent-core.
This ensures compile-time correctness and eliminates type drift between
backend and frontend (e.g. "tool_use" vs "toolCall", "tool_result" vs
"toolResult").

- stream.ts: re-export TextContent, ThinkingContent, ToolCall,
  ImageContent from pi-ai; use AgentEvent from pi-agent-core
- rpc.ts: AgentMessageItem = pi-ai Message (no more manual mirroring)
- connection-store.ts: use SDK types instead of inline hand-written ones
- ContentBlock now includes ImageContent to match backend reality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:13:01 +08:00
Naiyuan Qing
096b284cef
Merge pull request #80 from multica-ai/feat/web-qr-scanner
feat(ui): add QR code scanner to ConnectPrompt
2026-02-04 15:44:02 +08:00
Naiyuan Qing
2f5dbcdde9 feat(ui): add QR code scanner to ConnectPrompt
Add browser-based QR scanning as an alternative to paste mode in the
ConnectPrompt component. Mobile users can scan the Desktop QR code
directly instead of manually copying connection codes.

- Add qr-scanner dependency (WebWorker-based decoding, BarcodeDetector support)
- Create use-qr-scanner hook wrapping camera lifecycle and cleanup
- Create QrScannerView component with viewfinder overlay
- ConnectPrompt auto-detects mobile (touch + narrow viewport) and defaults to scan mode
- Lazy-load scanner component for zero initial bundle impact
- Graceful fallback to paste mode on permission denial or no camera

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:41:12 +08:00
yushen
dd701a2472 feat(sdk): collect device metadata during verify handshake
Auto-collect navigator.userAgent, platform, and language in the SDK
verify RPC payload. Add DeviceMeta type. Hub receives this metadata
for display in the Desktop UI device list and confirmation dialog.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:43:41 +08:00
yushen
3d13c28cfc feat(store): pass hubId and token to GatewayClient for verification
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:27:33 +08:00
yushen
0eac2b2a23 feat(sdk): add auto-verify handshake after gateway registration
Embed transparent verification logic in GatewayClient that automatically
sends an RPC "verify" request to the Hub after REGISTERED event. Adds
hubId, token, and verifyTimeout options. Upper-layer callers see no change
— "registered" state means both gateway registration and Hub verification
are complete. Failures surface via onError callback.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:27:23 +08:00
Naiyuan Qing
62267aaf19 refactor(ui): split Chat into focused components with skeleton loading
- Extract ConnectPrompt: self-contained connection code input
- Extract MessageList: pure display of messages with streaming support
- Add ChatSkeleton: skeleton placeholder shown during reconnection
- Chat component: three-state rendering (skeleton → connect → messages),
  keeps <main ref> always mounted so useScrollFade works correctly
- Remove hub-sidebar.tsx (dead code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:12:51 +08:00
Naiyuan Qing
162a86dff4 refactor(store): consolidate 3 stores into ConnectionStore + MessagesStore
Replace useGatewayStore, useHubStore, useDeviceId, and useHubInit with:
- ConnectionStore: WebSocket lifecycle, deviceId persistence via Zustand persist,
  fetch message history on registration via getAgentMessages RPC
- MessagesStore: simplified to current-agent-only, sendMessage accepts SendContext
  to break circular import with ConnectionStore
- useAutoConnect: returns { loading } for skeleton UI, skips connect if already
  connected (fixes Electron tab-switch reconnect), no cleanup disconnect

Removes: gateway.ts, hub.ts, hub-init.ts, device-id.ts, sonner dep from store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:12:45 +08:00
Naiyuan Qing
dacbfa9e3d refactor(ui): purify Chat component and move header to app layouts
- Remove all props from Chat (showHeader, headerActions) making it a
  zero-config pure chat component with only connection input, messages,
  and send functionality
- Create AppHeader client component for web app with brand, theme
  toggle, disconnect button, and useHubInit
- Add disconnect button to desktop layout header
- Add reset() action to hub store to eliminate duplicated state reset
- Remove unused token field from gateway store
- Remove dead code: connection-bar.tsx
- Guard handleConnect against empty deviceId race condition

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:31:24 +08:00
Naiyuan Qing
b6d7626239 fix(store): use string prefix + URLSearchParams for connection URL parsing
The URL constructor parses non-standard protocols (multica://)
differently across engines — Chromium may return empty hostname
while Node.js returns "connect". Replace new URL() with simple
startsWith check and URLSearchParams to ensure consistent behavior
in both web and Electron environments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:13:51 +08:00
Naiyuan Qing
ff80cf0732 feat(desktop): integrate Chat component into desktop app
Add @multica/store and zustand to desktop dependencies. Replace
placeholder chat page with the shared Chat component. Add Toaster
for toast notifications and remove padding on the chat route.
Change Chat root from h-dvh to h-full for container adaptability.
Add showHeader prop to Chat; desktop passes showHeader={false}
since it has its own layout header.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:08:30 +08:00
Naiyuan Qing
a088875118 refactor(ui): move theme toggle from Chat to web app layout
Chat component no longer depends on next-themes, making it safe to use
in the desktop app. Theme toggle is now a fixed button in the web layout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:02:48 +08:00
Naiyuan Qing
bb223d8a8c Merge branch 'main' into feat/connection-code 2026-02-03 19:42:41 +08:00
Naiyuan Qing
2bd87ccf5c theme buttom 2026-02-03 19:42:14 +08:00
Naiyuan Qing
6add5fc9c3 refactor(ui): remove sidebar, integrate connection flow into Chat
- Remove ConnectionBar sidebar from layout
- Chat component now handles connection code input inline (centered)
- Header: Multica branding left, Disconnect button right (when connected)
- Chat input disabled until connected

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 19:01:48 +08:00
Jiang Bohan
dafbf856ac merge: resolve conflicts with main branch
- Merge auth-profiles feature from main into runner.ts
- Merge closeCallbacks feature from main into async-agent.ts
- Regenerate pnpm-lock.yaml with new dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:57:05 +08:00
Naiyuan Qing
e7521b4cb0 feat(web): replace sidebar with connection code flow
- Add connection code parsing (JSON + base64) with localStorage persistence
- Add connectWithCode() to gateway store for code-driven connections
- Replace Sidebar with simple ConnectionBar (paste code / status / disconnect)
- Simplify Chat component by removing header clutter
- Auto-restore connection on page refresh from saved code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:53:49 +08:00
Jiang Bohan
03bcd853d3 feat(desktop): add UI components for Tools and Skills management
- Create tool-list.tsx with collapsible groups and toggle switches
- Create skill-list.tsx with status badges and action dialogs
- Create qr-code.tsx for connection QR code display
- Add dialog.tsx component to @multica/ui

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:25:30 +08:00
Naiyuan Qing
54ee2f3b00 feat(ui): add streaming indicator to StreamingMarkdown
Show a spinner with "Generating..." label while assistant messages are
being streamed. The indicator appears immediately when content is empty
and persists below the content until streaming completes.

Also fix duplicate React key warning by including block index in the key
to handle blocks with identical content.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:56:22 +08:00
Naiyuan Qing
d5b31eeddb fix(sdk,store): align frontend streaming protocol with new AgentEvent format
Backend Hub now sends raw AgentEvent in stream payloads instead of the
old delta/final/error state machine. Update StreamPayload type to use
event field, add extractTextFromEvent helper, and rewrite gateway store
handler to dispatch on event.type (message_start/update/end).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:54:34 +08:00
Naiyuan Qing
a8143735e9 feat(desktop): initialize electron app with routing and cleanup
Remove template boilerplate (sample SVGs, test IPC message, comments),
add react-router-dom v7 with createHashRouter, scaffold home and chat
pages using @multica/ui components, and add READMEs for desktop, ui,
and store packages documenting import conventions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:17:31 +08:00
Naiyuan Qing
86d00bb134 feat(ui): render AI messages with streaming markdown
Messages store gains streamingIds set and startStream/appendStream/
endStream actions. Gateway store routes stream action payloads to
these new actions. Chat component switches to StreamingMarkdown
for in-progress messages, providing incremental block-level rendering.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:18:35 +08:00
Naiyuan Qing
d04bed8175 feat(agent): add streaming support for AI message generation
AsyncAgent now subscribes to pi-agent-core events (message_start,
message_update, message_end) and forwards incremental text deltas
through a stream callback. Hub registers the callback and sends
stream payloads to the requesting client via Gateway.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:18:10 +08:00
Naiyuan Qing
441c2059a8 fix(sdk): point exports to source and remove .js import suffixes
- Change package exports from dist/ to src/ (consistent with @multica/store and @multica/ui)
- Add private: true since SDK is an internal monorepo package
- Switch tsconfig from NodeNext to bundler moduleResolution
- Remove .js suffixes from all internal imports for Turbopack compatibility

Eliminates the need to build SDK before running dev server or Next.js build.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:58:23 +08:00
Naiyuan Qing
a62ac2e075 chore: remove @multica/fetch package
No consumers remain after migrating to pure WebSocket RPC.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:29:27 +08:00
Naiyuan Qing
e491949749 refactor(store): remove @multica/fetch dependency
- Move gatewayUrl into gateway store state with setGatewayUrl action
- layout.tsx uses useGatewayStore.getState().setGatewayUrl() directly
- Remove @multica/fetch from store and web package.json dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:23:06 +08:00
Naiyuan Qing
a1c28b3d04 feat(ui): add auto-scroll to bottom for chat messages
- Add useAutoScroll hook using ResizeObserver + MutationObserver
- Observes content children for size changes (streaming, images)
- Watches for new DOM nodes (new messages, history load)
- Respects user scroll position: no force-scroll when reading above
- Integrate in Chat component alongside existing useScrollFade

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:14:18 +08:00
Naiyuan Qing
4d6d57187c feat(store): load agent message history via RPC
- Add typed AgentMessageItem to SDK with proper content block types
- Add fetchAgentMessages action to hub store using getAgentMessages RPC
- Extract text from complex content blocks (user string, assistant array)
- Auto-load history when selecting an agent with no local messages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:13:59 +08:00
Naiyuan Qing
3622f82a7b feat(gateway): add auto-connect, hub discovery via list-devices
- Add LIST_DEVICES event and "hub" device type to SDK
- Add listDevices() method to GatewayClient
- Add handleListDevices handler in Gateway
- Change Hub deviceType from "client" to "hub"
- Refactor gateway store: auto-connect WS, separate hubId selection
- Hub-init: auto-connect on mount, discover hubs on registered
- Hub-sidebar: show discovered hubs list with connect/disconnect UI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:13:40 +08:00
Naiyuan Qing
150c87a496 refactor(frontend): replace HTTP API with pure WebSocket RPC
- gateway store: add hubId state and request() RPC method
- hub store: replace consoleApi calls with gateway.request() RPC
- hub-init: simplify to react on gwState === "registered"
- hub-sidebar: add hub-id input + connect/disconnect flow
- chat: use gateway hubId for message routing
- layout: remove consoleUrl config (no longer needed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:27:07 +08:00
Naiyuan Qing
1f22c3e578 feat(hub): register RPC handlers for hub, agent, and gateway operations
Add 5 new RPC methods (getHubInfo, listAgents, createAgent, deleteAgent,
updateGateway) mirroring the existing Console HTTP API, enabling pure
WebSocket communication from the frontend.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:27:07 +08:00
yushen
2541745152 feat(sdk): add RPC request method and types to GatewayClient
Add request<T>() method that handles full request/response lifecycle
with auto-generated requestId, timeout handling, and pending request
cleanup on disconnect. Also add GetAgentMessagesParams and
GetAgentMessagesResult type definitions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:10:00 +08:00
Naiyuan Qing
3d7b13555f refactor(sdk): unify gateway-sdk into @multica/sdk package
Sync latest code from src/shared/gateway-sdk/ into packages/sdk/,
update all backend imports to use @multica/sdk, and remove the
duplicate src/shared/gateway-sdk/ directory.

- Translate Chinese comments to English in SDK source
- Fix package.json exports with default condition
- Add @multica/sdk as workspace dependency for backend
- Update imports in gateway, test-client, and hub

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:43:52 +08:00
Naiyuan Qing
4a7a9800f2 fix(frontend): apply code review fixes across store and UI
- Move setConfig() after imports in layout.tsx
- Add gateway disconnect cleanup in hub-init useEffect
- Replace silent catches with toast error notifications in hub store
- Optimize chat selectors with useMemo and useCallback/getState()
- Remove unused getMessagesByAgent from messages store
- Add clipboard try/catch with error toast
- Add skeleton loading states for hub sidebar and chat header

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:52:42 +08:00
Naiyuan Qing
eef26d8675 refactor(frontend): complete migration of all hooks and components to packages
- Create gateway store in @multica/store (WS connection independent of components)
- Gateway auto-connects when hub is ready, messages handled internally
- Move scroll-fade hook to @multica/ui/hooks
- Move Chat component to @multica/ui/components
- Add setConfig() call in web layout for URL injection
- Delete all web-local hooks, components, and lib/config
- Web app is now a pure shell: layout.tsx + page.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:07:57 +08:00
Naiyuan Qing
97fce5b113 refactor(store): migrate messages hook to Zustand store
- Move useMessages from apps/web to packages/store/src/messages.ts
- Convert useState to Zustand store for global message persistence
- Add reserved interfaces: updateMessage, loadMessages, getMessagesByAgent
- Update chat.tsx imports to use @multica/store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 10:49:52 +08:00
Naiyuan Qing
5275bc55fc refactor(store): migrate device-id hook to @multica/store
- Move useDeviceId from apps/web to packages/store/src/device-id.ts
- Update imports in chat.tsx and use-gateway.ts to use @multica/store
- Add uuid dependency to @multica/store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 10:45:20 +08:00
Naiyuan Qing
63861d03c6 refactor(frontend): extract shared stores and components into packages
- Create @multica/fetch package for HTTP client and URL config
- Migrate hub store and hub-init hook to @multica/store
- Move HubSidebar component to @multica/ui for web/desktop reuse
- Update web app imports to use shared packages
- Remove counter store example and its component-example usage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 10:37:17 +08:00
Naiyuan Qing
bcaf14464a fix(ui): improve disabled state cursor and no-agent empty state
- Add cursor-not-allowed and reduced opacity to ChatInput when disabled
- Show user icon in empty state when no agent is selected
- Pass contextual placeholder text based on agent selection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:45:53 +08:00
Naiyuan Qing
7e5bc7b7ae fix(ui): prevent chat submit during IME composition
Check e.nativeEvent.isComposing so pressing Enter to confirm
CJK input does not accidentally submit the message.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:42:54 +08:00
Naiyuan Qing
4ff227685e feat(ui): add onSubmit/disabled props to ChatInput, convert AppSidebar to children slot
ChatInput now accepts onSubmit callback and disabled state.
AppSidebar uses children instead of items prop for flexible content injection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:40:56 +08:00
Naiyuan Qing
cdcce47141 feat(ui): add brand icon and artistic font to sidebar header
- Display Multica icon alongside brand name in sidebar header
- Use Playfair Display serif font for an artistic brand feel
- Expose --font-brand CSS variable from layout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:35:48 +08:00
Naiyuan Qing
8018290fba fix(ui): add semantic colors to sonner toast icons
success → emerald-500, info → blue-500, warning → amber-500,
error → red-500, loading → muted-foreground.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:22:27 +08:00
Naiyuan Qing
9af889a9bb fix(web): import toast from @multica/ui instead of bare sonner
sonner is a dependency of @multica/ui, not web. Re-export toast from
the ui sonner module and update the import path in chat.tsx.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:21:24 +08:00
Naiyuan Qing
5f367fb6b7 fix(web): replace Zustand device store with local useDeviceId hook
Move device ID logic from @multica/store (Zustand persist) into a
simple useDeviceId hook in the web app. SSR returns empty string,
client reads/writes localStorage directly — no hydration mismatch,
no suppressHydrationWarning needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:16:54 +08:00
Naiyuan Qing
7d326695c1 feat(web): add scroll fade hint effect to chat message list
Use CSS mask-image gradients to hint at scrollable overflow in the chat
area. Adds useScrollFade hook that dynamically applies top/bottom fade
based on scroll position via scroll events and ResizeObserver.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:14:19 +08:00
Naiyuan Qing
047de2b431 fix(web): suppress deviceId hydration mismatch warning
Zustand persist generates a new deviceId on the server (no localStorage),
then hydrates a different value from localStorage on the client. Add
suppressHydrationWarning to the deviceId span since this mismatch is expected.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:04:57 +08:00
Naiyuan Qing
905e8c8ae3 fix(ui): remove Shiki span background-color causing unwanted highlights
The dual-theme CSS applied background-color to .shiki span elements,
which picked up token-level bg variables from github-dark/light themes
and created visible purple/blue highlights on code lines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 21:48:29 +08:00
Naiyuan Qing
88d2e9340b feat(ui): add light/dark theme toggle in sidebar
Add next-themes integration with a ThemeProvider wrapper and a
ThemeToggle dropdown (Light / Dark / System) in the sidebar footer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 21:21:31 +08:00
Naiyuan Qing
ffaed12048 feat(ui): add global scrollbar styling with light/dark support
Thin 6px scrollbars with transparent tracks and themed thumbs using
CSS standard properties + WebKit pseudo-elements for max compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 21:09:05 +08:00