Chat Phase 2b-1 — threads, attachments, shared file picker, chat guide#236
Chat Phase 2b-1 — threads, attachments, shared file picker, chat guide#236
Conversation
… + shared file picker 19 tasks covering attachments column migration, from-path endpoint, thread messages query + GET endpoint, thread recipient resolver, router integration, /help command + intercept, bridge event payload + attachment footer, VfsBrowser refactor, SharedFilePickerDialog shell primitive, chat-attachments-api client, AttachmentsBar + Gallery + Lightbox, hover actions + thread indicator + panel, chat-guide.md, MessagesApp integration, bundle rebuild, Playwright E2E.
…licy + thread context
…dPanel + use-thread-panel + GET message-by-id
📝 WalkthroughWalkthroughThis PR adds comprehensive chat enhancements including thread panel UI for message discussions, multi-file attachment support with upload/preview/gallery components, a file picker UI for selecting files from disk/workspace/agent sources, and supporting chat API helpers. Includes new components, hooks, tests, and documentation. Changes
Sequence DiagramssequenceDiagram
participant User
participant Frontend
participant FileAPI
participant Backend as Backend API
participant Storage
User->>Frontend: Select files (disk/workspace)
activate Frontend
Frontend->>Frontend: Queue files in pendingAttachments
Frontend->>User: Display AttachmentsBar with progress
User->>Frontend: Click Send
activate FileAPI
loop For each pending attachment
FileAPI->>Backend: POST /api/chat/upload (FormData)
activate Backend
Backend->>Storage: Save file
Backend-->>FileAPI: AttachmentRecord {url, filename, mime_type}
deactivate Backend
Frontend->>Frontend: Update attachment state (ready)
end
deactivate FileAPI
Frontend->>Backend: POST /api/chat/messages with attachments[]
activate Backend
Backend-->>Frontend: Message created
deactivate Backend
Frontend->>User: Clear composer, show message
deactivate Frontend
sequenceDiagram
participant User
participant Frontend
participant ThreadPanel
participant Backend as Backend API
User->>Frontend: Click ThreadIndicator
activate Frontend
Frontend->>Frontend: Set openThread state
Frontend->>ThreadPanel: Render with channelId, parentId
activate ThreadPanel
ThreadPanel->>Backend: GET /api/chat/messages/{parentId}
Backend-->>ThreadPanel: Parent message
ThreadPanel->>Backend: GET /api/chat/channels/{channelId}/threads/{parentId}/messages
Backend-->>ThreadPanel: Thread messages list
ThreadPanel->>User: Display thread conversation
deactivate ThreadPanel
User->>ThreadPanel: Type reply + click Send
ThreadPanel->>Backend: POST /api/chat/messages {thread_id, content, attachments}
activate Backend
Backend-->>ThreadPanel: Message created
deactivate Backend
ThreadPanel->>Frontend: Call onSend callback
Frontend->>Frontend: Refetch thread/close panel
deactivate Frontend
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (18 files)
Fix these issues in Kilo Cloud Reviewed by seed-2-0-pro-260328 · 204,884 tokens |
There was a problem hiding this comment.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
desktop/src/apps/MessagesApp.tsx (1)
603-621:⚠️ Potential issue | 🟠 MajorSuccessful slash POSTs still fall through to WebSocket send.
That was safe only while the POST was acting as validation. In this PR
/helpis handled in/api/chat/messages, so a 200 here can already have posted the system message before the same slash text is emitted over WS.Suggested fix
if (text.startsWith("/")) { try { const r = await fetch("/api/chat/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ channel_id: selectedChannel, content: text }), }); if (r.status === 400) { const body = await r.json().catch(() => ({})); setSendError((body as { error?: string }).error || "couldn't send message"); return; } + const body = await r.json().catch(() => ({})); + if ((body as { handled?: string }).handled) { + setSendError(null); + setInput(""); + autoScrollRef.current = true; + if (inputRef.current) inputRef.current.style.height = "auto"; + return; + } } catch { /* network error — fall through to WS send */ } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/MessagesApp.tsx` around lines 603 - 621, The current slash-command flow posts to /api/chat/messages but always falls through to wsRef.current.send, causing duplicate handling when the POST succeeded; update the slash handling in MessagesApp (the block that calls fetch for "/api/chat/messages" and then later calls wsRef.current.send) so that when the POST returns a success status (e.g., 200) you treat the command as handled and return early (do not call wsRef.current.send), keep the existing catch behavior to fall through on network errors, and continue to use setSendError for 4xx responses; ensure selectedChannel and setSendError are preserved in the early-return path.
🟡 Minor comments (9)
static/desktop/assets/CalendarApp-DSaV9uPb.js-1-1 (1)
1-1:⚠️ Potential issue | 🟡 Minor
Todayuses a render-captured date, which can become stale.On Line 1,
y()readssfrom render scope. If the window stays open across midnight, clicking Today can reset using yesterday’s date context. Use a freshDateinside the handler.💡 Proposed fix
- function y(){i(s.getFullYear()),a(s.getMonth())} + function y(){const e=new Date;i(e.getFullYear()),a(e.getMonth())}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@static/desktop/assets/CalendarApp-DSaV9uPb.js` at line 1, The Today button handler y() captures the render-time Date stored in s, so if the app stays open across midnight it may reset to the previous day; update y() in the CalendarApp component (function T) to construct a fresh Date() inside the handler and then call the state setters i(...) and a(...) with that new date's getFullYear() and getMonth() instead of using the render-scoped s.static/desktop/assets/MobileSplitView-CtNEF6zb.js-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorHandle
undefinedselectedIdin mobile selection logic.Line 1 uses
const w = l !== null, which treatsundefinedas “selected” and can open the detail pane unexpectedly on mobile. Use a nullish check instead.💡 Suggested fix
-const w=l!==null; +const w=l!=null;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@static/desktop/assets/MobileSplitView-CtNEF6zb.js` at line 1, The mobile selection boolean currently uses const w = l !== null inside function b (where l is the selectedId), which treats undefined as a selected value; change that check to a nullish check (e.g., const w = l != null) so both null and undefined are considered "no selection" and the detail pane won't open unexpectedly on mobile.static/desktop/assets/RedditApp-BOuG46mh.js-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorThe thread detail keeps stale saved-state after a successful save.
ve()refreshes the library and then immediately looks up the saved item in the oldvsnapshot, sohcan staynulluntil the thread is reopened. The detail view will keep showing the unsaved actions even after the ingest succeeds.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@static/desktop/assets/RedditApp-BOuG46mh.js` at line 1, The detail view uses a stale v snapshot after saving because ve() calls await b() but then still searches the old v; update b (the useCallback that fetches library items) to return the fetched items (the t array) and change ve() to use the returned array (e.g. const updated = await b()) and then find the saved item from updated (updated.find(...)) before calling B(...), so the new saved item is picked from the fresh data rather than the stale v; reference: ve, b, v, B, and i.post.url.desktop/src/apps/chat/ThreadIndicator.tsx-11-13 (1)
11-13:⚠️ Potential issue | 🟡 MinorUse nullish check for
lastReplyAtinstead of truthy check.Line 11 treats
0as absent. Prefer an explicit null/undefined check so valid numeric timestamps are handled consistently.Suggested fix
- const label = lastReplyAt + const label = lastReplyAt != null ? `💬 ${replyCount} repl${replyCount === 1 ? "y" : "ies"} · last reply ${relative(lastReplyAt)}` : `💬 ${replyCount} repl${replyCount === 1 ? "y" : "ies"}`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/chat/ThreadIndicator.tsx` around lines 11 - 13, The conditional for building label uses a truthy check on lastReplyAt which treats 0 as absent; update the ternary that assigns label to test explicitly for null/undefined (e.g., lastReplyAt != null) so numeric timestamps like 0 are treated as present while leaving replyCount and the relative(lastReplyAt) usage unchanged; locate the label declaration in ThreadIndicator.tsx to update the condition.desktop/src/apps/chat/MessageHoverActions.tsx-16-18 (1)
16-18:⚠️ Potential issue | 🟡 MinorSet explicit button types to prevent accidental form submission.
Line 16–18 buttons currently default to
type="submit". If this toolbar is ever rendered inside a form, clicks can trigger unintended submits.Suggested fix
- <button aria-label="Add reaction" onClick={onReact} className="p-1 hover:bg-white/5">😀</button> - <button aria-label="Reply in thread" onClick={onReplyInThread} className="p-1 hover:bg-white/5">💬</button> - <button aria-label="More" onClick={onMore} className="p-1 hover:bg-white/5">⋯</button> + <button type="button" aria-label="Add reaction" onClick={onReact} className="p-1 hover:bg-white/5">😀</button> + <button type="button" aria-label="Reply in thread" onClick={onReplyInThread} className="p-1 hover:bg-white/5">💬</button> + <button type="button" aria-label="More" onClick={onMore} className="p-1 hover:bg-white/5">⋯</button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/chat/MessageHoverActions.tsx` around lines 16 - 18, The three toolbar buttons rendered in MessageHoverActions (the elements with onClick handlers onReact, onReplyInThread, and onMore) lack an explicit type and can act as type="submit" inside forms; update each button to include type="button" to avoid accidental form submissions while keeping their existing aria-labels onClick handlers and classes unchanged.docs/chat-guide.md-317-320 (1)
317-320:⚠️ Potential issue | 🟡 MinorAdd languages to these fenced code blocks.
markdownlint is already flagging both fences with MD040.
textwould fit the attachment footer example, andbashortextwould fit the/helpcommand examples.Also applies to: 334-346
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/chat-guide.md` around lines 317 - 320, Add explicit fenced code block languages for the examples that currently use plain triple-backticks: change the attachment footer block containing "User attached: doc.pdf (application/pdf, 200 KB)..." to use ```text (or ```text+markdown) and change the /help command example blocks to use ```bash (or ```text if non-shell) so markdownlint MD040 is satisfied; look for the fenced blocks containing the attachment lines and the blocks showing the `/help` command output and update their opening fences accordingly.desktop/src/shell/VfsBrowser.tsx-54-59 (1)
54-59:⚠️ Potential issue | 🟡 MinorReset the browser state when
rootchanges.
currentPathandselectedsurvive arootprop change, so switching from one workspace root to another can reopen the new root inside a stale subdirectory and immediately 404. This shows up in the agent-workspace picker when the selected agent changes.Suggested fix
export function VfsBrowser({ root, onSelect, multi = false }: VfsBrowserProps) { const [currentPath, setCurrentPath] = useState(""); const [entries, setEntries] = useState<VfsEntry[]>([]); const [selected, setSelected] = useState<Set<string>>(new Set()); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); + + useEffect(() => { + setCurrentPath(""); + setSelected(new Set()); + setError(null); + }, [root]); useEffect(() => {Also applies to: 61-95
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/shell/VfsBrowser.tsx` around lines 54 - 59, When the VfsBrowser's root prop changes the component doesn't reset state, so currentPath and selected (and related state) can point into the previous root; update VfsBrowser to reset state when root changes by adding a useEffect that watches root and calls setCurrentPath(""), setSelected(new Set()), setEntries([]) and setError(null) (and optionally setLoading(false)) to clear stale state; reference the VfsBrowser component and the state setters setCurrentPath, setSelected, setEntries, setError, setLoading so you can locate and update the logic.desktop/src/apps/MessagesApp.tsx-1335-1340 (1)
1335-1340:⚠️ Potential issue | 🟡 MinorThe retry affordance is wired as a no-op.
Clicking Retry upload never re-runs the upload; it only replaces the error text. Either keep the original upload payload so this can actually retry, or hide the retry button until the behavior exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/MessagesApp.tsx` around lines 1335 - 1340, The Retry button is currently a no-op—onRetry just replaces the error text instead of re-running the upload. Fix by ensuring each pendingAttachments item retains the original upload payload (e.g., file/blob under a property like file or payload) and implement onRetry to set that item to uploading: true, clear error, and call the existing upload routine (e.g., uploadAttachment(item) or the same function used when initially adding attachments) so the upload is retried; if no upload routine exists yet, remove or conditionally hide the retry affordance in AttachmentsBar until retry behavior is implemented. Reference: AttachmentsBar, pendingAttachments, setPendingAttachments, and the upload function used for initial uploads.docs/superpowers/plans/2026-04-19-chat-phase-2b-1-threads-attachments.md-1649-1663 (1)
1649-1663:⚠️ Potential issue | 🟡 MinorThe agent-workspace example uses the wrong VFS root.
VfsBrowseronly special-cases/workspaces/agent/<slug>, but this snippet uses/workspaces/${selectedAgent}. If someone copies it verbatim, it falls through to the user-workspace endpoint.Suggested fix
- <VfsBrowser root={`/workspaces/${selectedAgent}`} onSelect={onAgentWorkspacePick} multi={multi} /> + <VfsBrowser root={`/workspaces/agent/${selectedAgent}`} onSelect={onAgentWorkspacePick} multi={multi} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/superpowers/plans/2026-04-19-chat-phase-2b-1-threads-attachments.md` around lines 1649 - 1663, The VfsBrowser root is incorrect — it should use the agent namespace so the component hits the agent-specific endpoint; change the root prop on the VfsBrowser (where selectedAgent is used) from `/workspaces/${selectedAgent}` to `/workspaces/agent/${selectedAgent}` so VfsBrowser's special-case for `/workspaces/agent/<slug>` is exercised (leave onAgentWorkspacePick and multi as-is).
🧹 Nitpick comments (3)
desktop/src/apps/chat/ThreadIndicator.tsx (1)
15-19: Set explicit button type for safety in form contexts.This button should be
type="button"to avoid accidental form submission if placement changes later.Suggested fix
- <button + <button + type="button" onClick={onOpen} className="mt-1 px-2 py-0.5 text-xs text-sky-200 hover:bg-white/5 rounded" aria-label="Open thread"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/chat/ThreadIndicator.tsx` around lines 15 - 19, The button in ThreadIndicator.tsx (the JSX element using onClick={onOpen} and rendering {label}) needs an explicit type to avoid accidental form submission; update the <button> element to include type="button" (keeping existing props like onClick={onOpen}, className, and aria-label) so it behaves safely if moved inside a form.desktop/src/apps/chat/AttachmentsBar.tsx (1)
34-36: Set explicit button types for action controls.This prevents accidental submit behavior if the bar is ever rendered inside a
<form>.Proposed small hardening change
- <button aria-label="Retry upload" onClick={() => onRetry(it.id)} className="text-red-300">retry</button> + <button type="button" aria-label="Retry upload" onClick={() => onRetry(it.id)} className="text-red-300">retry</button> ... - <button aria-label={`Remove ${it.filename}`} onClick={() => onRemove(it.id)} className="opacity-70 hover:opacity-100">×</button> + <button type="button" aria-label={`Remove ${it.filename}`} onClick={() => onRemove(it.id)} className="opacity-70 hover:opacity-100">×</button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/chat/AttachmentsBar.tsx` around lines 34 - 36, The buttons in AttachmentsBar (the retry and remove buttons rendering with onRetry(it.id) and onRemove(it.id)) lack explicit types which can cause them to act as submit buttons inside a form; update the JSX for those <button> elements to include type="button" so they are explicit action controls and won't trigger form submission.desktop/src/apps/chat/AttachmentLightbox.tsx (1)
25-43: Strengthen dialog accessibility semantics.
role="dialog"is present, butaria-modalis missing; adding it improves screen-reader behavior for modal overlays.Proposed accessibility tweak
return ( <div role="dialog" aria-label="Image viewer" + aria-modal="true" className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center" onClick={onClose} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@desktop/src/apps/chat/AttachmentLightbox.tsx` around lines 25 - 43, The dialog container div with role="dialog" in AttachmentLightbox is missing aria-modal; update the outer div (the one using role="dialog", aria-label="Image viewer", onClose handler and rendering current/images/idx) to include aria-modal="true" (i.e., <div role="dialog" aria-modal="true" aria-label="Image viewer" ...>) so screen readers treat it as a modal; keep existing onClick/onClose and stopPropagation handlers intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@desktop/src/apps/chat/ThreadPanel.tsx`:
- Around line 28-42: Both useEffect blocks (the parent loader that calls
fetch(`/api/chat/messages/${parentId}`) and the thread messages loader that
calls fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`)) can
fail silently and leak in-flight requests; update each to use an AbortController
to cancel the fetch on cleanup, check response.ok before calling r.json(), add
.catch handlers to handle network/JSON errors, and in error cases explicitly
clear or set a safe state (e.g., setParent(null) or setMsgs([])) and optionally
set an error flag to avoid stale UI and unhandled promise rejections instead of
leaving the existing setParent/setMsgs calls as-is.
- Around line 44-55: submit() currently clears the input before onSend completes
and may surface unhandled rejections from Enter key handling; change submit() to
await onSend(content, []) first, only clear input (setInput("")) after a
successful await, catch and handle errors (e.g., show an error state and
re-populate input) to avoid losing drafts, and return the send result; update
the local thread view by appending the returned message from onSend (or calling
a provided refresh/update callback) so replies appear immediately; also make
handleKeyDown await submit() (or call submit().catch(...)) so Enter-triggered
failures are not unhandled.
In `@desktop/src/apps/MessagesApp.tsx`:
- Around line 1497-1510: The onSend callback in MessagesApp.tsx currently POSTs
to "/api/chat/messages" but never checks the fetch Response, so failed 4xx/5xx
responses are treated as success and ThreadPanel doesn't get an exception to
preserve drafts or display errors; update the onSend handler (the async function
passed to onSend) to inspect the fetch Response (response.ok) and, if not ok,
read error details (e.g., response.text() or response.json()) and throw an Error
containing that information so ThreadPanel receives a thrown error and can
handle the failure appropriately.
- Around line 556-560: The sendMessage function currently returns early when
wsRef.current.readyState !== 1, which incorrectly blocks REST-backed flows
(e.g., pendingAttachments) — remove the global readyState guard and instead only
require wsRef.current.readyState === 1 for the text-only WebSocket fallback
path. Concretely: in sendMessage, keep the checks for input/pendingAttachments
and selectedChannel, but do not return based on wsRef.readyState; later, when
choosing the send path, branch so that attachment/HTTP flows call the REST
functions regardless of wsRef state and only the WS fallback path checks
wsRef.current.readyState before sending.
In `@desktop/src/lib/chat-attachments-api.ts`:
- Around line 16-23: The uploadDiskFile function returns raw server JSON which
uses content_type and omits source, breaking the AttachmentRecord shape; update
uploadDiskFile to parse the response JSON, map response.content_type to
mime_type (or set mime_type = response.content_type if present), ensure a source
field exists (e.g., source = "disk" or "upload") and any other required
AttachmentRecord properties are present/normalized, then return that normalized
object instead of raw r.json(); keep the function name uploadDiskFile and
preserve the existing fetch/_ensureOk flow.
In `@desktop/src/shell/FilePicker.tsx`:
- Around line 56-68: The issue is that onWorkspacePick and onAgentWorkspacePick
append the incoming full selection array to the existing queued state, causing
duplicates and preventing clean deselection; instead, when multi is true replace
queued with the new selections array (do not spread prev), i.e. setQueued should
set selections directly for multi-mode so the VfsBrowser-provided full selection
becomes authoritative; keep the existing single-select behavior (when multi is
false) and continue to include the selectedAgent slug in onAgentWorkspacePick.
In `@static/desktop/assets/AgentBrowsersApp-CFn8GY-5.js`:
- Line 1: ze() currently uses y() which expects JSON, so screenshot fetch always
falls back; change ze to perform a raw fetch that reads response.blob() (or
arrayBuffer -> base64) and returns an image URL/data URL instead of JSON so
P[s.id] becomes a usable src; update any callers (b and state setter ce that
stores P) to accept the returned URL/string and consider revoking object URLs
when a screenshot is replaced or the component unmounts.
- Line 1: The J callback replaces full profile objects with the small start/stop
API response (only id and status), corrupting profile shape and breaking status
checks; update J so when it receives l from Ce/Se it merges l onto the existing
profile instead of replacing it (e.g. in the h state update use
h(prev=>prev.map(H=>H.id===l.id?{...H,...l}:H))) and when updating the selected
profile (n) also merge l with the current selected profile rather than assigning
l directly so fields like profile_name, node and agent_name are preserved.
In `@static/desktop/assets/GitHubApp-IYMAlDty.js`:
- Line 1: The unauthenticated "Connect GitHub" buttons render with empty onClick
handlers (no-op) so users can't start auth; wire the two places that render the
CTA— the small connect button in the nav (the button rendered where
R.authenticated is false, currently onClick:()=>{}) and the banner button stored
in ne (button with onClick:()=>{}) — to actually start the GitHub auth flow (for
example by opening the app's GitHub auth endpoint or calling the existing auth
start endpoint) or remove the buttons; update those handlers to call the auth
start URL (window.open or navigate) and ensure aria-labels remain correct.
- Line 1: The sidebar controls (Watched, Content type S, and status filter pe)
are wired to state (X, S, pe) but never applied to the displayed list g and X is
never populated; fix by (1) populating X when the view is "watched" (call the
appropriate fetch in I or a new fetchWatched function and set X via set state X)
so Watched shows data, (2) update the useMemo that computes g (the variable g
declared with i.useMemo) to branch on S (repos/issues/prs/releases) and pe
(status) and filter the appropriate source array (J for repos, Y for
notifications/issues, X for watched, etc.) rather than always returning J/X/J,
and (3) ensure the effect that reacts to n (the view selector set via u)
triggers the correct fetch (I for starred, fetchWatched for watched, _ for
notifications) so toggling those sidebar buttons actually updates g; refer to
symbols X, J, Y, n, S, pe, I, _, and the useMemo that defines g.
In `@static/desktop/assets/ImportApp-DBAV17Xb.js`:
- Line 1: The embed call fails because ImportApp's upload flow (function T)
discards the filenames returned by /api/import/upload and the embed action
(function P) sends only {agent} instead of the required {agent_name, files}; fix
by capturing each upload response JSON in T (await response.json()), collect the
returned filenames into a new state (e.g., [uploadedFiles, setUploadedFiles]),
and append those filenames when all uploads finish; then change P to POST
JSON.stringify({agent_name: r, files: uploadedFiles}) and guard P to require
uploadedFiles.length>0; update references to functions T and P and state names i
(queued files) to wire the new uploadedFiles state.
- Line 1: The upload loop in T currently swallows errors and always increments
the progress and final success message; update the T function to check each
fetch response.ok (for the POST to "/api/import/upload"), treat non-ok and
caught exceptions as failures (do not increment the successful-count), collect
names/ids of successfully uploaded files (use the existing i array entries), and
compute progress based on total files but report and set state o(...) and u
accordingly to reflect successes vs failures; ensure h (uploading) is still
toggled off on completion and that the final message uses the actual successful
count (or lists failed filenames) rather than assuming all files succeeded.
In `@static/desktop/assets/SettingsApp-Bjcx0zeF.js`:
- Line 1: The restart dialog crashes because W's local state variable l can
exist while l.agents is undefined; change the computation of d (currently `const
d = l ? Object.entries(l.agents) : []`) to safely handle missing agents by using
a guard like checking l?.agents or falling back to an empty object before
calling Object.entries (i.e., compute d from Object.entries(l?.agents || {})),
update references in function W where d is used so the dialog no longer throws
when agents is absent.
---
Outside diff comments:
In `@desktop/src/apps/MessagesApp.tsx`:
- Around line 603-621: The current slash-command flow posts to
/api/chat/messages but always falls through to wsRef.current.send, causing
duplicate handling when the POST succeeded; update the slash handling in
MessagesApp (the block that calls fetch for "/api/chat/messages" and then later
calls wsRef.current.send) so that when the POST returns a success status (e.g.,
200) you treat the command as handled and return early (do not call
wsRef.current.send), keep the existing catch behavior to fall through on network
errors, and continue to use setSendError for 4xx responses; ensure
selectedChannel and setSendError are preserved in the early-return path.
---
Minor comments:
In `@desktop/src/apps/chat/MessageHoverActions.tsx`:
- Around line 16-18: The three toolbar buttons rendered in MessageHoverActions
(the elements with onClick handlers onReact, onReplyInThread, and onMore) lack
an explicit type and can act as type="submit" inside forms; update each button
to include type="button" to avoid accidental form submissions while keeping
their existing aria-labels onClick handlers and classes unchanged.
In `@desktop/src/apps/chat/ThreadIndicator.tsx`:
- Around line 11-13: The conditional for building label uses a truthy check on
lastReplyAt which treats 0 as absent; update the ternary that assigns label to
test explicitly for null/undefined (e.g., lastReplyAt != null) so numeric
timestamps like 0 are treated as present while leaving replyCount and the
relative(lastReplyAt) usage unchanged; locate the label declaration in
ThreadIndicator.tsx to update the condition.
In `@desktop/src/apps/MessagesApp.tsx`:
- Around line 1335-1340: The Retry button is currently a no-op—onRetry just
replaces the error text instead of re-running the upload. Fix by ensuring each
pendingAttachments item retains the original upload payload (e.g., file/blob
under a property like file or payload) and implement onRetry to set that item to
uploading: true, clear error, and call the existing upload routine (e.g.,
uploadAttachment(item) or the same function used when initially adding
attachments) so the upload is retried; if no upload routine exists yet, remove
or conditionally hide the retry affordance in AttachmentsBar until retry
behavior is implemented. Reference: AttachmentsBar, pendingAttachments,
setPendingAttachments, and the upload function used for initial uploads.
In `@desktop/src/shell/VfsBrowser.tsx`:
- Around line 54-59: When the VfsBrowser's root prop changes the component
doesn't reset state, so currentPath and selected (and related state) can point
into the previous root; update VfsBrowser to reset state when root changes by
adding a useEffect that watches root and calls setCurrentPath(""),
setSelected(new Set()), setEntries([]) and setError(null) (and optionally
setLoading(false)) to clear stale state; reference the VfsBrowser component and
the state setters setCurrentPath, setSelected, setEntries, setError, setLoading
so you can locate and update the logic.
In `@docs/chat-guide.md`:
- Around line 317-320: Add explicit fenced code block languages for the examples
that currently use plain triple-backticks: change the attachment footer block
containing "User attached: doc.pdf (application/pdf, 200 KB)..." to use ```text
(or ```text+markdown) and change the /help command example blocks to use ```bash
(or ```text if non-shell) so markdownlint MD040 is satisfied; look for the
fenced blocks containing the attachment lines and the blocks showing the `/help`
command output and update their opening fences accordingly.
In `@docs/superpowers/plans/2026-04-19-chat-phase-2b-1-threads-attachments.md`:
- Around line 1649-1663: The VfsBrowser root is incorrect — it should use the
agent namespace so the component hits the agent-specific endpoint; change the
root prop on the VfsBrowser (where selectedAgent is used) from
`/workspaces/${selectedAgent}` to `/workspaces/agent/${selectedAgent}` so
VfsBrowser's special-case for `/workspaces/agent/<slug>` is exercised (leave
onAgentWorkspacePick and multi as-is).
In `@static/desktop/assets/CalendarApp-DSaV9uPb.js`:
- Line 1: The Today button handler y() captures the render-time Date stored in
s, so if the app stays open across midnight it may reset to the previous day;
update y() in the CalendarApp component (function T) to construct a fresh Date()
inside the handler and then call the state setters i(...) and a(...) with that
new date's getFullYear() and getMonth() instead of using the render-scoped s.
In `@static/desktop/assets/MobileSplitView-CtNEF6zb.js`:
- Line 1: The mobile selection boolean currently uses const w = l !== null
inside function b (where l is the selectedId), which treats undefined as a
selected value; change that check to a nullish check (e.g., const w = l != null)
so both null and undefined are considered "no selection" and the detail pane
won't open unexpectedly on mobile.
In `@static/desktop/assets/RedditApp-BOuG46mh.js`:
- Line 1: The detail view uses a stale v snapshot after saving because ve()
calls await b() but then still searches the old v; update b (the useCallback
that fetches library items) to return the fetched items (the t array) and change
ve() to use the returned array (e.g. const updated = await b()) and then find
the saved item from updated (updated.find(...)) before calling B(...), so the
new saved item is picked from the fresh data rather than the stale v; reference:
ve, b, v, B, and i.post.url.
---
Nitpick comments:
In `@desktop/src/apps/chat/AttachmentLightbox.tsx`:
- Around line 25-43: The dialog container div with role="dialog" in
AttachmentLightbox is missing aria-modal; update the outer div (the one using
role="dialog", aria-label="Image viewer", onClose handler and rendering
current/images/idx) to include aria-modal="true" (i.e., <div role="dialog"
aria-modal="true" aria-label="Image viewer" ...>) so screen readers treat it as
a modal; keep existing onClick/onClose and stopPropagation handlers intact.
In `@desktop/src/apps/chat/AttachmentsBar.tsx`:
- Around line 34-36: The buttons in AttachmentsBar (the retry and remove buttons
rendering with onRetry(it.id) and onRemove(it.id)) lack explicit types which can
cause them to act as submit buttons inside a form; update the JSX for those
<button> elements to include type="button" so they are explicit action controls
and won't trigger form submission.
In `@desktop/src/apps/chat/ThreadIndicator.tsx`:
- Around line 15-19: The button in ThreadIndicator.tsx (the JSX element using
onClick={onOpen} and rendering {label}) needs an explicit type to avoid
accidental form submission; update the <button> element to include type="button"
(keeping existing props like onClick={onOpen}, className, and aria-label) so it
behaves safely if moved inside a form.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6167ff11-caa3-44ad-9568-dc679df95c2e
📒 Files selected for processing (102)
desktop/src/apps/MessagesApp.tsxdesktop/src/apps/chat/AttachmentGallery.tsxdesktop/src/apps/chat/AttachmentLightbox.tsxdesktop/src/apps/chat/AttachmentsBar.tsxdesktop/src/apps/chat/MessageHoverActions.tsxdesktop/src/apps/chat/ThreadIndicator.tsxdesktop/src/apps/chat/ThreadPanel.tsxdesktop/src/apps/chat/__tests__/AttachmentGallery.test.tsxdesktop/src/apps/chat/__tests__/AttachmentsBar.test.tsxdesktop/src/apps/chat/__tests__/MessageHoverActions.test.tsxdesktop/src/apps/chat/__tests__/ThreadIndicator.test.tsxdesktop/src/lib/__tests__/chat-attachments-api.test.tsdesktop/src/lib/chat-attachments-api.tsdesktop/src/lib/use-thread-panel.tsdesktop/src/shell/FilePicker.tsxdesktop/src/shell/VfsBrowser.tsxdesktop/src/shell/__tests__/FilePicker.test.tsxdesktop/src/shell/__tests__/VfsBrowser.test.tsxdesktop/src/shell/file-picker-api.tsdesktop/tsconfig.tsbuildinfodocs/chat-guide.mddocs/superpowers/plans/2026-04-19-chat-phase-2b-1-threads-attachments.mdstatic/desktop/assets/ActivityApp-CG-PW6E_.jsstatic/desktop/assets/AgentBrowsersApp-CFn8GY-5.jsstatic/desktop/assets/AgentBrowsersApp-wWjBRYht.jsstatic/desktop/assets/AgentsApp-1_BLyIy2.jsstatic/desktop/assets/BrowserApp-FjtUA0FW.jsstatic/desktop/assets/CalendarApp-BJnvuKGY.jsstatic/desktop/assets/CalendarApp-DSaV9uPb.jsstatic/desktop/assets/ChannelsApp-BMXzpUI6.jsstatic/desktop/assets/ClusterApp-DzgzEDRn.jsstatic/desktop/assets/ContactsApp-CmwPWf7s.jsstatic/desktop/assets/FilesApp-Bm-rxwrE.jsstatic/desktop/assets/GitHubApp-CJvVZ0RH.jsstatic/desktop/assets/GitHubApp-IYMAlDty.jsstatic/desktop/assets/ImageViewerApp-D7vhXACc.jsstatic/desktop/assets/ImagesApp-DfCeUrhn.jsstatic/desktop/assets/ImportApp-AV3jmR5U.jsstatic/desktop/assets/ImportApp-DBAV17Xb.jsstatic/desktop/assets/LibraryApp-Cdo_EHou.jsstatic/desktop/assets/LibraryApp-NzJAyw3P.jsstatic/desktop/assets/MCPApp-BNAfIIM4.jsstatic/desktop/assets/MemoryApp-eDECkdBk.jsstatic/desktop/assets/MessagesApp-C7hv44-7.jsstatic/desktop/assets/MessagesApp-DJJbqaHc.jsstatic/desktop/assets/MobileSplitView-CtNEF6zb.jsstatic/desktop/assets/MobileSplitView-qc4KfHBU.jsstatic/desktop/assets/ModelsApp-COpOwo4V.jsstatic/desktop/assets/ProvidersApp-eQBYuExS.jsstatic/desktop/assets/RedditApp-BOuG46mh.jsstatic/desktop/assets/RedditApp-CkwARPpU.jsstatic/desktop/assets/SecretsApp-C1umTVfg.jsstatic/desktop/assets/SettingsApp-Bjcx0zeF.jsstatic/desktop/assets/StoreApp-CNUGjBHW.jsstatic/desktop/assets/TasksApp-BLKBbvXY.jsstatic/desktop/assets/TextEditorApp-US6Eef1_.jsstatic/desktop/assets/XApp-E7cm6999.jsstatic/desktop/assets/YouTubeApp-Bv-vMHrm.jsstatic/desktop/assets/YouTubeApp-DPW-GRB6.jsstatic/desktop/assets/chat-CpqzVKkW.jsstatic/desktop/assets/index-0OnUwbQt.jsstatic/desktop/assets/index-5RjMGAa1.jsstatic/desktop/assets/index-BEgWFDZf.jsstatic/desktop/assets/index-B_XPm7mm.jsstatic/desktop/assets/index-C7isKigO.jsstatic/desktop/assets/index-CH8xqmNE.jsstatic/desktop/assets/index-CTe7-jHC.jsstatic/desktop/assets/index-C_KJzFJ_.jsstatic/desktop/assets/index-C_qAIZSt.jsstatic/desktop/assets/index-CoNKmJJQ.jsstatic/desktop/assets/index-CoQ45O6-.jsstatic/desktop/assets/index-D-E10IgF.jsstatic/desktop/assets/index-DTh72AYJ.jsstatic/desktop/assets/index-DdCLyul1.jsstatic/desktop/assets/index-Dw2m-Rvd.jsstatic/desktop/assets/index-DwzRNNkz.jsstatic/desktop/assets/index-Dza7_6d-.jsstatic/desktop/assets/main-DgK4yEp2.jsstatic/desktop/assets/tokens-8UM84fY1.cssstatic/desktop/assets/tokens-BWEexfPB.jsstatic/desktop/assets/tokens-ib1qRNqW.cssstatic/desktop/assets/vendor-codemirror-CL2HhW7v.jsstatic/desktop/assets/vendor-icons-wm645Jsx.jsstatic/desktop/chat.htmlstatic/desktop/index.htmltests/e2e/test_chat_phase2b1.pytests/test_agent_chat_router.pytests/test_bridge_session_phase1.pytests/test_chat_attachments.pytests/test_chat_help.pytests/test_chat_threads.pytinyagentos/agent_chat_router.pytinyagentos/chat/help.pytinyagentos/chat/message_store.pytinyagentos/chat/threads.pytinyagentos/routes/chat.pytinyagentos/scripts/install_hermes.shtinyagentos/scripts/install_langroid.shtinyagentos/scripts/install_openai-agents-sdk.shtinyagentos/scripts/install_openai_agents_sdk.shtinyagentos/scripts/install_pocketflow.shtinyagentos/scripts/install_smolagents.sh
💤 Files with no reviewable changes (8)
- static/desktop/assets/MobileSplitView-qc4KfHBU.js
- static/desktop/assets/GitHubApp-CJvVZ0RH.js
- static/desktop/assets/CalendarApp-BJnvuKGY.js
- static/desktop/assets/AgentBrowsersApp-wWjBRYht.js
- static/desktop/assets/ImportApp-AV3jmR5U.js
- static/desktop/assets/RedditApp-CkwARPpU.js
- static/desktop/assets/LibraryApp-NzJAyw3P.js
- static/desktop/assets/MessagesApp-DJJbqaHc.js
| useEffect(() => { | ||
| let alive = true; | ||
| fetch(`/api/chat/messages/${parentId}`) | ||
| .then((r) => (r.ok ? r.json() : null)) | ||
| .then((d) => { if (alive) setParent(d); }); | ||
| return () => { alive = false; }; | ||
| }, [parentId]); | ||
|
|
||
| useEffect(() => { | ||
| let alive = true; | ||
| fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`) | ||
| .then((r) => r.json()) | ||
| .then((d) => { if (alive) setMsgs(d.messages || []); }); | ||
| return () => { alive = false; }; | ||
| }, [channelId, parentId]); |
There was a problem hiding this comment.
Handle thread-loading failures explicitly (and cancel in-flight requests).
Both loaders can fail silently right now; network/JSON failures leave stale UI and can emit unhandled async errors.
Proposed resilient fetch pattern
useEffect(() => {
- let alive = true;
- fetch(`/api/chat/messages/${parentId}`)
- .then((r) => (r.ok ? r.json() : null))
- .then((d) => { if (alive) setParent(d); });
- return () => { alive = false; };
+ const ac = new AbortController();
+ (async () => {
+ try {
+ const r = await fetch(`/api/chat/messages/${parentId}`, { signal: ac.signal });
+ if (!r.ok) {
+ setParent(null);
+ return;
+ }
+ const d = await r.json();
+ setParent(d);
+ } catch {
+ if (!ac.signal.aborted) setParent(null);
+ }
+ })();
+ return () => ac.abort();
}, [parentId]);
useEffect(() => {
- let alive = true;
- fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`)
- .then((r) => r.json())
- .then((d) => { if (alive) setMsgs(d.messages || []); });
- return () => { alive = false; };
+ const ac = new AbortController();
+ (async () => {
+ try {
+ const r = await fetch(
+ `/api/chat/channels/${channelId}/threads/${parentId}/messages`,
+ { signal: ac.signal },
+ );
+ if (!r.ok) {
+ setMsgs([]);
+ return;
+ }
+ const d = await r.json();
+ setMsgs(d.messages || []);
+ } catch {
+ if (!ac.signal.aborted) setMsgs([]);
+ }
+ })();
+ return () => ac.abort();
}, [channelId, parentId]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| let alive = true; | |
| fetch(`/api/chat/messages/${parentId}`) | |
| .then((r) => (r.ok ? r.json() : null)) | |
| .then((d) => { if (alive) setParent(d); }); | |
| return () => { alive = false; }; | |
| }, [parentId]); | |
| useEffect(() => { | |
| let alive = true; | |
| fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`) | |
| .then((r) => r.json()) | |
| .then((d) => { if (alive) setMsgs(d.messages || []); }); | |
| return () => { alive = false; }; | |
| }, [channelId, parentId]); | |
| useEffect(() => { | |
| const ac = new AbortController(); | |
| (async () => { | |
| try { | |
| const r = await fetch(`/api/chat/messages/${parentId}`, { signal: ac.signal }); | |
| if (!r.ok) { | |
| setParent(null); | |
| return; | |
| } | |
| const d = await r.json(); | |
| setParent(d); | |
| } catch { | |
| if (!ac.signal.aborted) setParent(null); | |
| } | |
| })(); | |
| return () => ac.abort(); | |
| }, [parentId]); | |
| useEffect(() => { | |
| const ac = new AbortController(); | |
| (async () => { | |
| try { | |
| const r = await fetch( | |
| `/api/chat/channels/${channelId}/threads/${parentId}/messages`, | |
| { signal: ac.signal }, | |
| ); | |
| if (!r.ok) { | |
| setMsgs([]); | |
| return; | |
| } | |
| const d = await r.json(); | |
| setMsgs(d.messages || []); | |
| } catch { | |
| if (!ac.signal.aborted) setMsgs([]); | |
| } | |
| })(); | |
| return () => ac.abort(); | |
| }, [channelId, parentId]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@desktop/src/apps/chat/ThreadPanel.tsx` around lines 28 - 42, Both useEffect
blocks (the parent loader that calls fetch(`/api/chat/messages/${parentId}`) and
the thread messages loader that calls
fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`)) can fail
silently and leak in-flight requests; update each to use an AbortController to
cancel the fetch on cleanup, check response.ok before calling r.json(), add
.catch handlers to handle network/JSON errors, and in error cases explicitly
clear or set a safe state (e.g., setParent(null) or setMsgs([])) and optionally
set an error flag to avoid stale UI and unhandled promise rejections instead of
leaving the existing setParent/setMsgs calls as-is.
| async function submit() { | ||
| const content = input.trim(); | ||
| if (!content) return; | ||
| setInput(""); | ||
| await onSend(content, []); | ||
| } | ||
|
|
||
| function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) { | ||
| if (e.key === "Enter" && !e.shiftKey) { | ||
| e.preventDefault(); | ||
| submit(); | ||
| } |
There was a problem hiding this comment.
submit() currently loses drafts on failure and doesn’t refresh the thread view.
Input is cleared before onSend resolves, and Enter-triggered async failures can surface as unhandled promise rejections. Also, replies are not reflected locally after send.
Proposed submit flow fix
+ const [sending, setSending] = useState(false);
async function submit() {
const content = input.trim();
- if (!content) return;
- setInput("");
- await onSend(content, []);
+ if (!content || sending) return;
+ try {
+ setSending(true);
+ await onSend(content, []);
+ setInput("");
+ const r = await fetch(`/api/chat/channels/${channelId}/threads/${parentId}/messages`);
+ if (r.ok) {
+ const d = await r.json();
+ setMsgs(d.messages || []);
+ }
+ } catch {
+ // preserve draft on failure
+ setInput(content);
+ } finally {
+ setSending(false);
+ }
}
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
- submit();
+ void submit();
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@desktop/src/apps/chat/ThreadPanel.tsx` around lines 44 - 55, submit()
currently clears the input before onSend completes and may surface unhandled
rejections from Enter key handling; change submit() to await onSend(content, [])
first, only clear input (setInput("")) after a successful await, catch and
handle errors (e.g., show an error state and re-populate input) to avoid losing
drafts, and return the send result; update the local thread view by appending
the returned message from onSend (or calling a provided refresh/update callback)
so replies appear immediately; also make handleKeyDown await submit() (or call
submit().catch(...)) so Enter-triggered failures are not unhandled.
| const sendMessage = async () => { | ||
| const text = input.trim(); | ||
| if (!text || !selectedChannel || !wsRef.current || wsRef.current.readyState !== 1) return; | ||
| if (!text && pendingAttachments.length === 0) return; | ||
| if (!selectedChannel || !wsRef.current || wsRef.current.readyState !== 1) return; | ||
|
|
There was a problem hiding this comment.
Don't gate the HTTP send paths on WebSocket state.
This early return blocks attachment sends and other REST-backed flows whenever the socket drops, even though they do not need WS. Only the final text-only WS fallback should require readyState === 1.
Suggested fix
const sendMessage = async () => {
const text = input.trim();
if (!text && pendingAttachments.length === 0) return;
- if (!selectedChannel || !wsRef.current || wsRef.current.readyState !== 1) return;
+ if (!selectedChannel) return;
// Block send while uploads are in-flight
if (pendingAttachments.some((a) => a.uploading)) {
setSendError("waiting for uploads to finish…");
return;
@@
- if (!text) return;
+ if (!text) return;
+ if (!wsRef.current || wsRef.current.readyState !== 1) return;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@desktop/src/apps/MessagesApp.tsx` around lines 556 - 560, The sendMessage
function currently returns early when wsRef.current.readyState !== 1, which
incorrectly blocks REST-backed flows (e.g., pendingAttachments) — remove the
global readyState guard and instead only require wsRef.current.readyState === 1
for the text-only WebSocket fallback path. Concretely: in sendMessage, keep the
checks for input/pendingAttachments and selectedChannel, but do not return based
on wsRef.readyState; later, when choosing the send path, branch so that
attachment/HTTP flows call the REST functions regardless of wsRef state and only
the WS fallback path checks wsRef.current.readyState before sending.
| onSend={async (content, attachments) => { | ||
| await fetch("/api/chat/messages", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| channel_id: openThread.channelId, | ||
| author_id: "user", | ||
| author_type: "user", | ||
| content, | ||
| content_type: "text", | ||
| thread_id: openThread.parentId, | ||
| attachments, | ||
| }), | ||
| }); |
There was a problem hiding this comment.
Surface failed thread sends back to ThreadPanel.
ThreadPanel relies on onSend throwing when a send fails, but this callback never checks response.ok. Right now a 4xx/5xx reply is treated as success and the panel cannot preserve the draft or show the error.
Suggested fix
<ThreadPanel
channelId={openThread.channelId}
parentId={openThread.parentId}
onClose={closeThread}
onSend={async (content, attachments) => {
- await fetch("/api/chat/messages", {
+ const r = await fetch("/api/chat/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
channel_id: openThread.channelId,
author_id: "user",
@@
attachments,
}),
});
+ if (!r.ok) {
+ const body = await r.json().catch(() => ({}));
+ throw new Error((body as { error?: string }).error || "send failed");
+ }
}}
/>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@desktop/src/apps/MessagesApp.tsx` around lines 1497 - 1510, The onSend
callback in MessagesApp.tsx currently POSTs to "/api/chat/messages" but never
checks the fetch Response, so failed 4xx/5xx responses are treated as success
and ThreadPanel doesn't get an exception to preserve drafts or display errors;
update the onSend handler (the async function passed to onSend) to inspect the
fetch Response (response.ok) and, if not ok, read error details (e.g.,
response.text() or response.json()) and throw an Error containing that
information so ThreadPanel receives a thrown error and can handle the failure
appropriately.
| export async function uploadDiskFile(file: File, channelId?: string): Promise<AttachmentRecord> { | ||
| const form = new FormData(); | ||
| form.append("file", file); | ||
| if (channelId) form.append("channel_id", channelId); | ||
| const r = await fetch("/api/chat/upload", { method: "POST", body: form }); | ||
| await _ensureOk(r); | ||
| return r.json(); | ||
| } |
There was a problem hiding this comment.
Normalize /api/chat/upload response before returning AttachmentRecord.
uploadDiskFile currently returns raw server JSON, but the upload endpoint returns content_type and no source. This violates AttachmentRecord and can break image rendering logic that depends on mime_type.
Proposed response normalization
export async function uploadDiskFile(file: File, channelId?: string): Promise<AttachmentRecord> {
const form = new FormData();
form.append("file", file);
if (channelId) form.append("channel_id", channelId);
const r = await fetch("/api/chat/upload", { method: "POST", body: form });
await _ensureOk(r);
- return r.json();
+ const body = await r.json() as {
+ filename: string;
+ content_type?: string;
+ mime_type?: string;
+ size: number;
+ url: string;
+ source?: "disk" | "workspace" | "agent-workspace";
+ };
+ return {
+ filename: body.filename,
+ mime_type: body.mime_type ?? body.content_type ?? "application/octet-stream",
+ size: body.size,
+ url: body.url,
+ source: body.source ?? "disk",
+ };
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@desktop/src/lib/chat-attachments-api.ts` around lines 16 - 23, The
uploadDiskFile function returns raw server JSON which uses content_type and
omits source, breaking the AttachmentRecord shape; update uploadDiskFile to
parse the response JSON, map response.content_type to mime_type (or set
mime_type = response.content_type if present), ensure a source field exists
(e.g., source = "disk" or "upload") and any other required AttachmentRecord
properties are present/normalized, then return that normalized object instead of
raw r.json(); keep the function name uploadDiskFile and preserve the existing
fetch/_ensureOk flow.
| @@ -0,0 +1 @@ | |||
| import{r,j as e}from"./vendor-react-l6srOxy7.js";import{B as o,I as we,C as Y,c as Z}from"./toolbar-UW6q5pkx.js";import{$ as W,g as Q,ap as z,a9 as A,aN as ee,a5 as se,l as je,y as X}from"./vendor-icons-wm645Jsx.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";async function y(a,s,n){try{const c=await fetch(a,{...n,headers:{Accept:"application/json",...n==null?void 0:n.headers}});return!c.ok||!(c.headers.get("content-type")??"").includes("application/json")?s:await c.json()}catch{return s}}async function te(a,s,n){return y(a,n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}async function ae(a,s,n){return y(a,n,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}async function Ne(a){const n=await y("/api/agent-browsers/profiles",{profiles:[]});return Array.isArray(n.profiles)?n.profiles:[]}async function ye(a,s,n){try{const c=await fetch("/api/agent-browsers/profiles",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({profile_name:a,agent_name:s??null,node:n??"local"})});return!c.ok||!(c.headers.get("content-type")??"").includes("application/json")?null:await c.json()}catch{return null}}async function ve(a){try{return(await fetch(`/api/agent-browsers/profiles/${encodeURIComponent(a)}`,{method:"DELETE",headers:{Accept:"application/json"}})).ok}catch{return!1}}async function ke(a){try{return(await fetch(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/data`,{method:"DELETE",headers:{Accept:"application/json"}})).ok}catch{return!1}}async function Ce(a){return await te(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/start`,{},null)}async function Se(a){return await te(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/stop`,{},null)}async function ze(a){return(await y(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/screenshot`,{})).data??null}async function Ae(a){try{const s=await fetch(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/login-status`,{headers:{Accept:"application/json"}});return!s.ok||!(s.headers.get("content-type")??"").includes("application/json")?null:await s.json()}catch{return null}}async function De(a,s){return await ae(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/assign`,{agent_name:s},null)}async function Pe(a,s){return await ae(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/move`,{node:s},null)}const ne=[{key:"x",label:"X / Twitter"},{key:"github",label:"GitHub"},{key:"youtube",label:"YouTube"},{key:"reddit",label:"Reddit"}];function re({status:a}){const s=a==="running"?"bg-green-500/15 text-green-400 border border-green-500/30":a==="error"?"bg-red-500/15 text-red-400 border border-red-500/30":"bg-white/10 text-shell-text-tertiary border border-white/10";return e.jsx("span",{className:`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${s}`,children:a})}function le({node:a}){return e.jsx("span",{className:"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-accent/10 text-accent border border-accent/20",children:a})}function $e({status:a}){return e.jsx("div",{className:"flex gap-1","aria-label":"Login status indicators",children:ne.map(({key:s,label:n})=>e.jsx("span",{title:n,"aria-label":`${n}: ${a?a[s]?"logged in":"not logged in":"unknown"}`,className:`w-2 h-2 rounded-full ${a?a[s]?"bg-green-400":"bg-white/20":"bg-white/10"}`},s))})}function Ie({profile:a,loginStatus:s,selected:n,onSelect:c,onToggle:x,toggling:m}){return e.jsx(Y,{role:"button",tabIndex:0,"aria-selected":n,"aria-label":`Browser profile: ${a.profile_name}`,onClick:c,onKeyDown:h=>{(h.key==="Enter"||h.key===" ")&&(h.preventDefault(),c())},className:`cursor-pointer transition-colors select-none ${n?"border-accent/50 bg-accent/5":"border-white/5 hover:border-white/15 hover:bg-white/3"}`,children:e.jsxs(Z,{className:"p-3 space-y-2",children:[e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"text-sm font-semibold truncate",children:a.profile_name}),a.agent_name&&e.jsx("p",{className:"text-xs text-shell-text-tertiary truncate",children:a.agent_name})]}),e.jsx(re,{status:a.status})]}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(le,{node:a.node}),e.jsx($e,{status:s})]}),e.jsx(o,{variant:"ghost",size:"sm","aria-label":a.status==="running"?"Stop browser":"Start browser",disabled:m,onClick:x,className:"h-6 w-6 p-0 shrink-0",children:a.status==="running"?e.jsx(ee,{size:12,className:"text-red-400"}):e.jsx(se,{size:12,className:"text-green-400"})})]})]})})}function Te({onSelect:a,selected:s}){return e.jsx(Y,{role:"button",tabIndex:0,"aria-label":"Create new browser profile","aria-selected":s,onClick:a,onKeyDown:n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),a())},className:`cursor-pointer transition-colors border-dashed ${s?"border-accent/50 bg-accent/5":"border-white/10 hover:border-accent/30 hover:bg-white/3"}`,children:e.jsxs(Z,{className:"p-3 flex items-center gap-2 text-shell-text-tertiary",children:[e.jsx(Q,{size:14}),e.jsx("span",{className:"text-sm",children:"New Profile"})]})})}function Ue({windowId:a}){const[s,n]=r.useState(null),[c,x]=r.useState(null),[m,h]=r.useState([]),[D,ie]=r.useState({}),[P,ce]=r.useState({}),[$,oe]=r.useState([]),[de,I]=r.useState(!0),[v,T]=r.useState(null),[w,L]=r.useState(!1),[f,B]=r.useState(""),[k,E]=r.useState(""),[C,_]=r.useState(!1),[xe,j]=r.useState(!1),[g,R]=r.useState(""),[S,U]=r.useState("local"),d=typeof window<"u"&&window.innerWidth<640,[he,p]=r.useState(!1),N=r.useCallback(async()=>{I(!0);const t=await Ne();h(t),I(!1)},[]);r.useEffect(()=>{N()},[N]);const O=r.useCallback(async()=>{try{const t=await fetch("/api/agents",{headers:{Accept:"application/json"}});if(t.ok&&(t.headers.get("content-type")??"").includes("application/json")){const l=await t.json();Array.isArray(l)&&oe(l.map(u=>({name:String(u.name??"unknown"),color:String(u.color??"#3b82f6")})))}}catch{}},[]);r.useEffect(()=>{O()},[O]);const V=r.useCallback(async t=>{const i=await Ae(t);i&&ie(l=>({...l,[t]:i}))},[]);r.useEffect(()=>{for(const t of m)V(t.id)},[m,V]);const b=r.useCallback(async t=>{L(!0);const i=await ze(t);i&&ce(l=>({...l,[t]:i})),L(!1)},[]),ue=r.useCallback(t=>{n(t),x("detail"),j(!1),R(t.agent_name??""),U(t.node),t.status==="running"&&b(t.id),d&&p(!0)},[b,d]),me=r.useCallback(()=>{n(null),x("create"),B(""),E(""),d&&p(!0)},[d]),F=r.useCallback(()=>{p(!1),x(null),n(null)},[]),J=r.useCallback(async(t,i)=>{i==null||i.stopPropagation(),T(t.id);let l=null;t.status==="running"?l=await Se(t.id):l=await Ce(t.id),l&&(h(u=>u.map(H=>H.id===l.id?l:H)),(s==null?void 0:s.id)===l.id&&(n(l),l.status==="running"&&b(l.id))),T(null)},[s,b]),M=r.useCallback(async()=>{if(!f.trim())return;_(!0),await ye(f.trim(),k||void 0,"local")&&(await N(),x(null),n(null),d&&p(!1)),_(!1)},[f,k,N,d]),fe=r.useCallback(async()=>{if(!s)return;await ve(s.id)&&(h(i=>i.filter(l=>l.id!==s.id)),n(null),x(null),d&&p(!1))},[s,d]),ge=r.useCallback(async()=>{if(!s)return;await ke(s.id)&&j(!1)},[s]),pe=r.useCallback(async()=>{if(!s||!g)return;const t=await De(s.id,g);t&&(h(i=>i.map(l=>l.id===t.id?t:l)),n(t))},[s,g]),be=r.useCallback(async()=>{if(!s)return;const t=await Pe(s.id,S);t&&(h(i=>i.map(l=>l.id===t.id?t:l)),n(t))},[s,S]),q=e.jsxs("div",{className:"flex flex-col h-full","aria-label":"Create new browser profile",children:[e.jsxs("div",{className:"flex items-center gap-2 px-4 py-3 border-b border-white/5 shrink-0",children:[d&&e.jsx(o,{variant:"ghost",size:"sm","aria-label":"Back",onClick:F,className:"h-7 w-7 p-0 mr-1",children:e.jsx(W,{size:14})}),e.jsx(Q,{size:14,className:"text-accent"}),e.jsx("h2",{className:"text-sm font-semibold",children:"New Profile"})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-4",children:[e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("label",{htmlFor:"new-profile-name",className:"text-xs text-shell-text-tertiary",children:"Profile name"}),e.jsx(we,{id:"new-profile-name",placeholder:"e.g. research-main",value:f,onChange:t=>B(t.target.value),onKeyDown:t=>{t.key==="Enter"&&M()},"aria-required":"true"})]}),e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("label",{htmlFor:"new-profile-agent",className:"text-xs text-shell-text-tertiary",children:"Assign agent (optional)"}),e.jsxs("select",{id:"new-profile-agent",value:k,onChange:t=>E(t.target.value),className:"w-full h-9 rounded-md border border-white/10 bg-shell-surface/50 px-3 text-sm text-shell-text focus:outline-none focus:ring-1 focus:ring-accent",children:[e.jsx("option",{value:"",children:"Unassigned"}),$.map(t=>e.jsx("option",{value:t.name,children:t.name},t.name))]})]}),e.jsx(o,{onClick:M,disabled:!f.trim()||C,className:"w-full","aria-busy":C,children:C?"Creating…":"Create Profile"})]})]}),G=s?e.jsxs("div",{className:"flex flex-col h-full","aria-label":`Browser profile details: ${s.profile_name}`,children:[e.jsxs("div",{className:"flex items-center gap-2 px-4 py-3 border-b border-white/5 shrink-0",children:[d&&e.jsx(o,{variant:"ghost",size:"sm","aria-label":"Back",onClick:F,className:"h-7 w-7 p-0 mr-1",children:e.jsx(W,{size:14})}),e.jsx(z,{size:14,className:"text-accent shrink-0"}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("h2",{className:"text-sm font-semibold truncate",children:s.profile_name}),s.agent_name&&e.jsx("p",{className:"text-xs text-shell-text-tertiary truncate",children:s.agent_name})]}),e.jsxs("div",{className:"flex items-center gap-1.5 shrink-0",children:[e.jsx(le,{node:s.node}),e.jsx(re,{status:s.status})]})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-4",children:[e.jsxs("section",{"aria-labelledby":"screenshot-heading",children:[e.jsx("h3",{id:"screenshot-heading",className:"text-xs font-medium text-shell-text-tertiary uppercase tracking-wider mb-2",children:"Preview"}),e.jsx("div",{className:"relative w-full aspect-video bg-shell-surface/50 border border-white/5 rounded-md overflow-hidden flex items-center justify-center",children:w?e.jsxs("div",{className:"flex items-center gap-2 text-shell-text-tertiary text-xs",children:[e.jsx(A,{size:12,className:"animate-spin"}),e.jsx("span",{children:"Loading preview…"})]}):P[s.id]?e.jsx("img",{src:P[s.id],alt:`Screenshot of ${s.profile_name}`,className:"w-full h-full object-contain"}):e.jsx("p",{className:"text-xs text-shell-text-tertiary text-center px-4",children:s.status==="running"?"No screenshot available":"Start browser to see preview"})})]}),e.jsxs("section",{"aria-labelledby":"login-status-heading",children:[e.jsx("h3",{id:"login-status-heading",className:"text-xs font-medium text-shell-text-tertiary uppercase tracking-wider mb-2",children:"Login Status"}),e.jsx("div",{className:"space-y-1",children:ne.map(({key:t,label:i})=>{const l=D[s.id],u=l?l[t]:null;return e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx("span",{className:`w-2 h-2 rounded-full shrink-0 ${u===!0?"bg-green-400":u===!1?"bg-red-400/60":"bg-white/20"}`,"aria-hidden":"true"}),e.jsx("span",{className:"text-shell-text-secondary",children:i}),e.jsx("span",{className:"ml-auto text-xs text-shell-text-tertiary",children:u===!0?"Logged in":u===!1?"Not logged in":"Unknown"})]},t)})})]}),e.jsxs("section",{"aria-labelledby":"actions-heading",children:[e.jsx("h3",{id:"actions-heading",className:"text-xs font-medium text-shell-text-tertiary uppercase tracking-wider mb-2",children:"Actions"}),e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex gap-2",children:[e.jsx(o,{variant:s.status==="running"?"secondary":"default",size:"sm",disabled:v===s.id,onClick:()=>J(s),"aria-busy":v===s.id,className:"flex-1 flex items-center gap-1.5",children:s.status==="running"?e.jsxs(e.Fragment,{children:[e.jsx(ee,{size:12}),"Stop"]}):e.jsxs(e.Fragment,{children:[e.jsx(se,{size:12}),"Start"]})}),e.jsxs(o,{variant:"secondary",size:"sm",disabled:s.status!=="running",title:"Opens browser in a taOS window","aria-label":"Connect to browser via noVNC — opens browser in a taOS window",className:"flex-1 flex items-center gap-1.5",onClick:()=>{},children:[e.jsx(je,{size:12}),"Connect"]})]}),s.status==="running"&&e.jsxs(o,{variant:"ghost",size:"sm",onClick:()=>b(s.id),disabled:w,"aria-busy":w,className:"w-full flex items-center gap-1.5 text-xs",children:[e.jsx(A,{size:11,className:w?"animate-spin":""}),"Refresh screenshot"]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{htmlFor:"assign-agent-select",className:"text-xs text-shell-text-tertiary",children:"Assign agent"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsxs("select",{id:"assign-agent-select",value:g,onChange:t=>R(t.target.value),className:"flex-1 h-8 rounded-md border border-white/10 bg-shell-surface/50 px-2 text-xs text-shell-text focus:outline-none focus:ring-1 focus:ring-accent",children:[e.jsx("option",{value:"",children:"Unassigned"}),$.map(t=>e.jsx("option",{value:t.name,children:t.name},t.name))]}),e.jsx(o,{variant:"secondary",size:"sm",onClick:pe,disabled:!g,className:"shrink-0",children:"Assign"})]})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{htmlFor:"move-node-select",className:"text-xs text-shell-text-tertiary",children:"Node"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("select",{id:"move-node-select",value:S,onChange:t=>U(t.target.value),className:"flex-1 h-8 rounded-md border border-white/10 bg-shell-surface/50 px-2 text-xs text-shell-text focus:outline-none focus:ring-1 focus:ring-accent",children:e.jsx("option",{value:"local",children:"local"})}),e.jsx(o,{variant:"secondary",size:"sm",onClick:be,className:"shrink-0",children:"Move"})]})]})]})]}),e.jsxs("section",{"aria-labelledby":"danger-heading",children:[e.jsx("h3",{id:"danger-heading",className:"text-xs font-medium text-red-400/70 uppercase tracking-wider mb-2",children:"Danger Zone"}),e.jsxs("div",{className:"space-y-2",children:[e.jsxs(o,{variant:"ghost",size:"sm",onClick:fe,className:"w-full flex items-center gap-1.5 text-red-400 hover:text-red-300 hover:bg-red-500/10 border border-red-500/20","aria-label":"Delete container",children:[e.jsx(X,{size:12}),"Delete container"]}),xe?e.jsxs("div",{className:"rounded-md border border-red-500/30 bg-red-500/5 p-3 space-y-2",children:[e.jsx("p",{className:"text-xs text-red-300",children:"This permanently removes all passwords, bookmarks, cookies, and browsing history."}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(o,{variant:"ghost",size:"sm",onClick:()=>j(!1),className:"flex-1 text-xs",children:"Cancel"}),e.jsx(o,{size:"sm",onClick:ge,className:"flex-1 text-xs bg-red-600 hover:bg-red-700 text-white border-0","aria-label":"Confirm delete all browser data",children:"Delete all data"})]})]}):e.jsxs(o,{variant:"ghost",size:"sm",onClick:()=>j(!0),className:"w-full flex items-center gap-1.5 text-red-400 hover:text-red-300 hover:bg-red-500/10 border border-red-500/20","aria-label":"Delete browser data",children:[e.jsx(X,{size:12}),"Delete data"]})]})]})]})]}):null,K=e.jsxs("div",{className:"flex flex-col h-full",role:"region","aria-label":"Browser profiles",children:[e.jsxs("div",{className:"flex items-center gap-2 px-4 py-3 border-b border-white/5 shrink-0",children:[e.jsx(z,{size:15,className:"text-accent"}),e.jsx("h1",{className:"text-sm font-semibold",children:"Agent Browsers"})]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-3",children:de?e.jsxs("div",{className:"flex items-center justify-center h-24 text-shell-text-tertiary text-sm",children:[e.jsx(A,{size:14,className:"animate-spin mr-2"}),"Loading profiles…"]}):e.jsxs("div",{role:"list","aria-label":"Browser profile cards",className:"grid grid-cols-1 gap-2",children:[m.map(t=>e.jsx("div",{role:"listitem",children:e.jsx(Ie,{profile:t,loginStatus:D[t.id]??null,selected:(s==null?void 0:s.id)===t.id,onSelect:()=>ue(t),onToggle:i=>J(t,i),toggling:v===t.id})},t.id)),e.jsx("div",{role:"listitem",children:e.jsx(Te,{onSelect:me,selected:c==="create"})})]})})]});return d?e.jsx("div",{className:"w-full h-full bg-shell-bg text-shell-text overflow-hidden",children:he?c==="create"?q:G:K}):e.jsxs("div",{className:"w-full h-full bg-shell-bg text-shell-text flex overflow-hidden",children:[e.jsx("div",{className:"w-72 shrink-0 border-r border-white/5 flex flex-col overflow-hidden",children:K}),e.jsx("div",{className:"flex-1 min-w-0 overflow-hidden",children:c==="create"?q:c==="detail"&&s?G:e.jsx("div",{className:"flex items-center justify-center h-full text-shell-text-tertiary",children:e.jsxs("div",{className:"text-center space-y-2",children:[e.jsx(z,{size:32,className:"mx-auto opacity-20"}),e.jsx("p",{className:"text-sm",children:"Select a profile to view details"}),e.jsx("p",{className:"text-xs opacity-60",children:"or create a new one"})]})})})]})}export{Ue as AgentBrowsersApp}; | |||
There was a problem hiding this comment.
Screenshot loading is wired to JSON, but the endpoint returns PNG bytes.
On Line 1, ze() calls the screenshot endpoint via y(), which only accepts JSON. /api/agent-browsers/profiles/{profile_id}/screenshot returns image/png, so this path always falls back and P[s.id] never gets a usable image.
Suggested fix
- async function ze(a){return(await y(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/screenshot`,{})).data??null}
+ async function ze(a){
+ try{
+ const res = await fetch(`/api/agent-browsers/profiles/${encodeURIComponent(a)}/screenshot`);
+ if(!res.ok) return null;
+ const blob = await res.blob();
+ return URL.createObjectURL(blob);
+ }catch{
+ return null;
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/AgentBrowsersApp-CFn8GY-5.js` at line 1, ze() currently
uses y() which expects JSON, so screenshot fetch always falls back; change ze to
perform a raw fetch that reads response.blob() (or arrayBuffer -> base64) and
returns an image URL/data URL instead of JSON so P[s.id] becomes a usable src;
update any callers (b and state setter ce that stores P) to accept the returned
URL/string and consider revoking object URLs when a screenshot is replaced or
the component unmounts.
Start/stop updates overwrite profile shape and break status logic.
On Line 1, J() replaces a full profile with start/stop API responses ({status:"started"|"stopped", id}), but UI expects full profile fields (profile_name, node, agent_name) and checks for status === "running". This causes state corruption and incorrect button behavior after toggling.
Suggested fix
- let l=null;t.status==="running"?l=await Se(t.id):l=await Ce(t.id),
- l&&(h(u=>u.map(H=>H.id===l.id?l:H)),(s==null?void 0:s.id)===l.id&&(n(l),l.status==="running"&&b(l.id)))
+ const actionRes = t.status==="running" ? await Se(t.id) : await Ce(t.id);
+ if(actionRes?.id){
+ const refreshed = await fetch(`/api/agent-browsers/profiles/${encodeURIComponent(actionRes.id)}`, { headers:{Accept:"application/json"} })
+ .then(r => r.ok ? r.json() : null)
+ .catch(() => null);
+ if(refreshed){
+ h(u=>u.map(H=>H.id===refreshed.id ? refreshed : H));
+ if((s==null?void 0:s.id)===refreshed.id){
+ n(refreshed);
+ if(refreshed.status==="running") b(refreshed.id);
+ }
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/AgentBrowsersApp-CFn8GY-5.js` at line 1, The J callback
replaces full profile objects with the small start/stop API response (only id
and status), corrupting profile shape and breaking status checks; update J so
when it receives l from Ce/Se it merges l onto the existing profile instead of
replacing it (e.g. in the h state update use
h(prev=>prev.map(H=>H.id===l.id?{...H,...l}:H))) and when updating the selected
profile (n) also merge l with the current selected profile rather than assigning
l directly so fields like profile_name, node and agent_name are preserved.
| @@ -0,0 +1 @@ | |||
| import{r as i,j as e}from"./vendor-react-l6srOxy7.js";import{B as x,I as Be,C as H,a as O,c as E,S as Te,d as Ae,e as Ge,f as M,g as U}from"./toolbar-UW6q5pkx.js";import{M as He}from"./MobileSplitView-CtNEF6zb.js";import{u as Oe}from"./use-is-mobile-v5lglusa.js";import{aX as y,aY as w,B as ie,aR as oe,aZ as v,a_ as N,am as ce,r as Ee,S as de,D as q,aL as W,a1 as Me,$ as P,ay as K,a$ as F,aF as xe,ac as Ue}from"./vendor-icons-wm645Jsx.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";async function k(l,c,s){try{const d=await fetch(l,{...s,headers:{Accept:"application/json",...s==null?void 0:s.headers}});return!d.ok||!(d.headers.get("content-type")??"").includes("application/json")?c:await d.json()}catch{return c}}async function qe(l,c,s){return k(l,s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})}async function We(l){const s=new URLSearchParams().toString(),d=`/api/github/starred${s?`?${s}`:""}`,n=await k(d,{repos:[],total:0});return{repos:Array.isArray(n.repos)?n.repos:[],total:n.total??0}}async function Pe(){const l=await k("/api/github/notifications",{notifications:[],unread_count:0});return{notifications:Array.isArray(l.notifications)?l.notifications:[],unread_count:l.unread_count??0}}async function Ke(l,c){try{const s=await fetch(`/api/github/repo/${encodeURIComponent(l)}/${encodeURIComponent(c)}`,{headers:{Accept:"application/json"}});return!s.ok||!(s.headers.get("content-type")??"").includes("application/json")?null:await s.json()}catch{return null}}async function Fe(l,c,s){try{const d=await fetch(`/api/github/repo/${encodeURIComponent(l)}/${encodeURIComponent(c)}/issues/${s}`,{headers:{Accept:"application/json"}});return!d.ok||!(d.headers.get("content-type")??"").includes("application/json")?null:await d.json()}catch{return null}}async function Ve(l,c){const s=await k(`/api/github/repo/${encodeURIComponent(l)}/${encodeURIComponent(c)}/releases`,{releases:[]});return Array.isArray(s.releases)?s.releases:[]}async function Je(){return k("/api/github/auth/status",{authenticated:!1})}async function Ye(l){return qe("/api/knowledge/ingest",{url:l,title:"",text:"",categories:[],source:"github-browser"},null)}const j=l=>{if(!l)return"";const c=new Date(l),s=(Date.now()-c.getTime())/1e3;return s<60?"just now":s<3600?`${Math.floor(s/60)}m ago`:s<86400?`${Math.floor(s/3600)}h ago`:s<604800?`${Math.floor(s/86400)}d ago`:c.toLocaleDateString()},Xe=l=>l<1024?`${l} B`:l<1048576?`${(l/1024).toFixed(1)} KB`:`${(l/1048576).toFixed(1)} MB`,he=l=>l==="open"?"bg-green-500/15 text-green-400 border-green-500/30":l==="closed"?"bg-red-500/15 text-red-400 border-red-500/30":l==="merged"?"bg-slate-500/15 text-slate-400 border-slate-500/30":"bg-white/10 text-shell-text-tertiary border-white/10";function Ze({comment:l,depth:c=0}){const[s,d]=i.useState(c>=3);return e.jsxs("div",{className:`border-l-2 ${c===0?"border-white/10":"border-white/5"} pl-3 py-1`,style:{marginLeft:c>0?`${c*12}px`:0},children:[e.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[e.jsx("span",{className:"text-xs font-medium text-shell-text-secondary",children:l.author}),e.jsx("span",{className:"text-[10px] text-shell-text-tertiary",children:j(l.created_at)}),c>=3&&e.jsx("button",{className:"text-[10px] text-accent hover:underline ml-1",onClick:()=>d(n=>!n),"aria-expanded":!s,"aria-label":s?"Expand comment":"Collapse comment",children:s?"expand":"collapse"})]}),!s&&e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"text-xs text-shell-text-secondary whitespace-pre-wrap leading-relaxed mb-1",children:l.body}),Object.keys(l.reactions??{}).length>0&&e.jsx("div",{className:"flex gap-1.5 flex-wrap mb-1",children:Object.entries(l.reactions).map(([n,u])=>u>0?e.jsxs("span",{className:"px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] text-shell-text-secondary","aria-label":`${n}: ${u}`,children:[n," ",u]},n):null)})]})]})}function nt({windowId:l}){const[,c]=i.useState("list"),[s,d]=i.useState(null),[n,u]=i.useState("starred"),[S,V]=i.useState("repos"),[pe,ue]=i.useState(null),[J,be]=i.useState([]),[Y,me]=i.useState([]),[C,fe]=i.useState(0),[X]=i.useState([]),[Z,$]=i.useState(!0),[p,D]=i.useState(""),[Q,z]=i.useState(!1),[ge,ee]=i.useState([]),[je,te]=i.useState(!1),[m,se]=i.useState(!1),[h,L]=i.useState(!1),[R,ye]=i.useState({authenticated:!1}),f=Oe(),ae=i.useCallback(async()=>{const t=await Je();ye(t)},[]),I=i.useCallback(async()=>{$(!0);const t=await We();be(t.repos),$(!1)},[]),_=i.useCallback(async()=>{$(!0);const t=await Pe();me(t.notifications),fe(t.unread_count),$(!1)},[]);i.useEffect(()=>{ae(),I(),_()},[ae,I,_]),i.useEffect(()=>{c("list"),d(null),D(""),n==="starred"||n==="watched"?I():n==="notifications"&&_()},[n,I,_]);const B=i.useCallback(async t=>{c("detail"),d({type:"repo",repo:t}),L(!1),te(!1),z(!0);const[a,r]=await Promise.all([Ve(t.owner,t.name),Ke(t.owner,t.name)]);ee(a),r&&d({type:"repo",repo:r}),z(!1)},[]),T=i.useCallback(async t=>{c("detail"),d({type:"issue",issue:t}),L(!1),z(!0);const[a,r]=t.repo.split("/");if(a&&r){const o=await Fe(a,r,t.number);o&&d({type:"issue",issue:o})}z(!1)},[]),re=i.useCallback((t,a)=>{c("detail"),d({type:"release",release:{...t,repo:a}}),L(!1)},[]),b=i.useCallback(()=>{c("list"),d(null),ee([])},[]),le=i.useMemo(()=>s?s.type==="repo"&&s.repo?`repo:${s.repo.owner}/${s.repo.name}`:s.type==="issue"&&s.issue?`issue:${s.issue.repo}#${s.issue.number}`:s.type==="release"&&s.release?`release:${s.release.tag}`:null:null,[s]),A=i.useCallback(async t=>{se(!0);const a=await Ye(t);se(!1),a&&L(!0)},[]),g=i.useMemo(()=>n==="starred"||n==="watched"?(n==="watched"?X:J).filter(a=>{var o;if(!p)return!0;const r=p.toLowerCase();return a.name.toLowerCase().includes(r)||a.owner.toLowerCase().includes(r)||((o=a.description)==null?void 0:o.toLowerCase().includes(r))}):n==="notifications"?Y.filter(t=>{if(!p)return!0;const a=p.toLowerCase();return t.title.toLowerCase().includes(a)||t.repo.toLowerCase().includes(a)}):[],[n,J,X,Y,p]),we=e.jsxs("nav",{className:"w-52 shrink-0 border-r border-white/5 bg-shell-surface/30 flex flex-col overflow-hidden","aria-label":"GitHub Browser navigation",children:[e.jsxs("div",{className:"flex items-center gap-2 px-3 py-3 border-b border-white/5 shrink-0",children:[e.jsx(y,{size:15,className:"text-accent","aria-hidden":"true"}),e.jsx("h1",{className:"text-sm font-semibold",children:"GitHub"})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-2 space-y-4",children:[e.jsx("section",{"aria-label":"Sections",children:e.jsxs("div",{className:"space-y-0.5",children:[e.jsxs(x,{variant:n==="starred"?"secondary":"ghost",size:"sm","aria-pressed":n==="starred",onClick:()=>u("starred"),className:"w-full justify-start text-xs h-7 px-2 gap-1.5",children:[e.jsx(w,{size:11,"aria-hidden":"true"}),"Starred Repos"]}),e.jsxs(x,{variant:n==="notifications"?"secondary":"ghost",size:"sm","aria-pressed":n==="notifications",onClick:()=>u("notifications"),className:"w-full justify-between text-xs h-7 px-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5",children:[e.jsx(ie,{size:11,"aria-hidden":"true"}),"Notifications"]}),C>0&&e.jsx("span",{className:"px-1.5 py-0.5 rounded-full bg-accent text-white text-[10px] tabular-nums","aria-label":`${C} unread`,children:C})]}),e.jsxs(x,{variant:n==="watched"?"secondary":"ghost",size:"sm","aria-pressed":n==="watched",onClick:()=>u("watched"),className:"w-full justify-start text-xs h-7 px-2 gap-1.5",children:[e.jsx(oe,{size:11,"aria-hidden":"true"}),"Watched"]})]})}),e.jsxs("section",{"aria-label":"Content type",children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary px-2 mb-1.5",children:"Content"}),e.jsx("div",{className:"space-y-0.5",children:[{id:"repos",label:"Repos",icon:y},{id:"issues",label:"Issues",icon:v},{id:"prs",label:"Pull Requests",icon:N},{id:"releases",label:"Releases",icon:ce}].map(({id:t,label:a,icon:r})=>e.jsxs(x,{variant:S===t?"secondary":"ghost",size:"sm","aria-pressed":S===t,onClick:()=>V(t),className:"w-full justify-start text-xs h-7 px-2 gap-1.5",children:[e.jsx(r,{size:11,"aria-hidden":"true"}),a]},t))})]}),e.jsxs("section",{"aria-label":"Status filter",children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary px-2 mb-1.5",children:"Status"}),e.jsx("div",{className:"space-y-0.5",children:["open","closed","merged"].map(t=>{const a=pe===t;return e.jsx(x,{variant:a?"secondary":"ghost",size:"sm","aria-pressed":a,onClick:()=>ue(r=>r===t?null:t),className:"w-full justify-start text-xs h-7 px-2 capitalize",children:t},t)})})]})]}),e.jsx("div",{className:"shrink-0 border-t border-white/5 px-3 py-2",children:R.authenticated?e.jsxs("div",{className:"space-y-0.5",children:[e.jsx("p",{className:"text-[10px] text-shell-text-tertiary capitalize",children:R.method??"connected"}),e.jsxs("p",{className:"text-xs text-shell-text-secondary truncate",children:["@",R.username]})]}):e.jsx("button",{className:"text-xs text-accent hover:underline",onClick:()=>{},"aria-label":"Connect GitHub account",children:"Connect GitHub"})})]}),ne=R.authenticated?null:e.jsxs("div",{className:"flex items-center gap-3 px-4 py-2 bg-amber-500/10 border-b border-amber-500/20 text-xs text-amber-300 shrink-0",role:"banner","aria-label":"GitHub authentication notice",children:[e.jsx(Ee,{size:13,"aria-hidden":"true"}),e.jsx("span",{children:"Connect GitHub for starred repos and notifications."}),e.jsx("button",{className:"ml-auto underline hover:text-amber-200","aria-label":"Open Secrets app to connect GitHub",children:"Connect"})]}),ve=t=>e.jsxs(H,{className:"cursor-pointer hover:border-white/15 transition-colors",onClick:()=>B(t),onKeyDown:a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),B(t))},tabIndex:0,role:"button","aria-label":`Open ${t.owner}/${t.name}`,children:[e.jsxs(O,{className:"pb-1 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("h3",{className:"text-sm font-medium leading-snug",children:[e.jsxs("span",{className:"text-shell-text-tertiary",children:[t.owner,"/"]}),t.name]}),t.language&&e.jsx("span",{className:"shrink-0 text-[10px] px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20",children:t.language})]}),t.description&&e.jsx("p",{className:"text-[11px] text-shell-text-secondary line-clamp-1 leading-relaxed mt-0.5",children:t.description})]}),e.jsx(E,{className:"pt-0 px-3 pb-3",children:e.jsxs("div",{className:"flex items-center gap-3 text-[10px] text-shell-text-tertiary",children:[e.jsxs("span",{className:"flex items-center gap-1","aria-label":`${t.stars} stars`,children:[e.jsx(w,{size:10,"aria-hidden":"true"}),t.stars.toLocaleString()]}),e.jsxs("span",{className:"flex items-center gap-1","aria-label":`${t.forks} forks`,children:[e.jsx(W,{size:10,"aria-hidden":"true"}),t.forks.toLocaleString()]}),e.jsx("span",{className:"ml-auto",children:j(t.updated_at)})]})})]},`${t.owner}/${t.name}`),Ne=t=>e.jsxs(H,{className:"cursor-pointer hover:border-white/15 transition-colors",onClick:()=>T(t),onKeyDown:a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),T(t))},tabIndex:0,role:"button","aria-label":`Open ${t.is_pull_request?"PR":"issue"}: ${t.title}`,children:[e.jsx(O,{className:"pb-1 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[t.is_pull_request?e.jsx(N,{size:13,className:"mt-0.5 shrink-0 text-accent","aria-hidden":"true"}):e.jsx(v,{size:13,className:"mt-0.5 shrink-0 text-green-400","aria-hidden":"true"}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("h3",{className:"text-sm font-medium leading-snug line-clamp-1",children:t.title}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:t.repo})]}),e.jsx("span",{className:`shrink-0 text-[10px] px-1.5 py-0.5 rounded border ${he(t.state)}`,"aria-label":`Status: ${t.state}`,children:t.state})]})}),e.jsxs(E,{className:"pt-0 px-3 pb-3 space-y-1.5",children:[t.labels.length>0&&e.jsx("div",{className:"flex flex-wrap gap-1","aria-label":"Labels",children:t.labels.map(a=>e.jsx("span",{className:"px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] text-shell-text-secondary",children:a},a))}),e.jsxs("div",{className:"flex items-center gap-3 text-[10px] text-shell-text-tertiary",children:[e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx(Me,{size:10,"aria-hidden":"true"}),t.comments.length]}),e.jsx("span",{children:t.author}),e.jsx("span",{className:"ml-auto",children:j(t.created_at)})]})]})]},`${t.repo}#${t.number}`),ke=(t,a="")=>e.jsxs(H,{className:"cursor-pointer hover:border-white/15 transition-colors",onClick:()=>re(t,a),onKeyDown:r=>{(r.key==="Enter"||r.key===" ")&&(r.preventDefault(),re(t,a))},tabIndex:0,role:"button","aria-label":`Open release ${t.tag}`,children:[e.jsx(O,{className:"pb-1 p-3",children:e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("div",{children:[e.jsxs("h3",{className:"text-sm font-medium leading-snug flex items-center gap-1.5",children:[e.jsx(xe,{size:11,"aria-hidden":"true",className:"text-accent"}),t.tag]}),a&&e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:a})]}),t.prerelease&&e.jsx("span",{className:"shrink-0 text-[10px] px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-400 border border-amber-500/30",children:"pre-release"})]})}),e.jsx(E,{className:"pt-0 px-3 pb-3",children:e.jsx("p",{className:"text-[10px] text-shell-text-tertiary",children:j(t.published_at)})})]},t.tag),Se=e.jsxs("main",{className:"flex-1 flex flex-col overflow-hidden","aria-label":"GitHub content list",children:[e.jsx("div",{className:"flex items-center gap-2 px-4 py-3 border-b border-white/5 shrink-0",children:e.jsxs("div",{className:"relative flex-1",children:[e.jsx(de,{size:14,className:"absolute left-3 top-1/2 -translate-y-1/2 text-shell-text-tertiary pointer-events-none z-10","aria-hidden":"true"}),e.jsx(Be,{type:"search",value:p,onChange:t=>D(t.target.value),placeholder:"Search…",className:"pl-8 h-8","aria-label":"Search GitHub content"})]})}),e.jsx("div",{className:"flex-1 overflow-y-auto p-3 space-y-2",role:"list","aria-label":"GitHub items",children:Z?e.jsx("div",{className:"flex items-center justify-center h-full text-shell-text-tertiary text-sm",role:"status","aria-live":"polite",children:"Loading…"}):g.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center h-full gap-3 text-shell-text-tertiary",children:[e.jsx(y,{size:36,className:"opacity-20","aria-hidden":"true"}),e.jsx("p",{className:"text-sm",children:p?"No results for your search":"Nothing here yet"})]}):n==="notifications"?g.map(t=>e.jsx("div",{role:"listitem",children:Ne(t)},`${t.repo}#${t.number}`)):g.map(t=>e.jsx("div",{role:"listitem",children:ve(t)},`${t.owner}/${t.name}`))})]}),Ce=t=>{const a=`https://github.com/${t.owner}/${t.name}`,r=ge[0]??null;return e.jsx("main",{className:"flex-1 flex flex-col overflow-hidden","aria-label":`${t.owner}/${t.name} detail`,children:e.jsxs("div",{className:"flex-1 overflow-y-auto",children:[e.jsxs("div",{className:"px-5 pt-4 pb-3 border-b border-white/5",children:[!f&&e.jsxs(x,{variant:"ghost",size:"sm",onClick:b,className:"text-xs mb-3 -ml-1 text-shell-text-secondary","aria-label":"Back to list",onKeyDown:o=>o.key==="Escape"&&b(),children:[e.jsx(P,{size:14,"aria-hidden":"true"}),"Back"]}),e.jsxs("h2",{className:"text-lg font-semibold leading-snug mb-1",children:[e.jsxs("span",{className:"text-shell-text-tertiary",children:[t.owner,"/"]}),t.name]}),t.description&&e.jsx("p",{className:"text-sm text-shell-text-secondary mb-3",children:t.description}),e.jsxs("div",{className:"flex flex-wrap gap-2 mb-3",children:[e.jsxs("span",{className:"flex items-center gap-1 text-[11px] px-2 py-0.5 rounded bg-white/5 border border-white/10 text-shell-text-secondary","aria-label":`${t.stars} stars`,children:[e.jsx(w,{size:10,"aria-hidden":"true"}),t.stars.toLocaleString()," stars"]}),e.jsxs("span",{className:"flex items-center gap-1 text-[11px] px-2 py-0.5 rounded bg-white/5 border border-white/10 text-shell-text-secondary","aria-label":`${t.forks} forks`,children:[e.jsx(W,{size:10,"aria-hidden":"true"}),t.forks.toLocaleString()," forks"]}),t.language&&e.jsx("span",{className:"text-[11px] px-2 py-0.5 rounded bg-accent/10 text-accent border border-accent/20",children:t.language}),t.license&&e.jsx("span",{className:"text-[11px] px-2 py-0.5 rounded bg-white/5 border border-white/10 text-shell-text-secondary",children:t.license})]}),t.topics.length>0&&e.jsx("div",{className:"flex flex-wrap gap-1 mb-2","aria-label":"Topics",children:t.topics.map(o=>e.jsx("span",{className:"px-1.5 py-0.5 rounded-full bg-blue-500/10 text-blue-400 text-[10px] border border-blue-500/20",children:o},o))})]}),t.readme_content&&e.jsxs("div",{className:"px-5 py-4 border-b border-white/5",children:[e.jsx("h3",{className:"text-xs font-semibold text-shell-text-tertiary uppercase tracking-wider mb-2",children:"README"}),e.jsx("div",{className:"rounded-lg bg-white/[0.02] border border-white/5 p-3 max-h-64 overflow-y-auto",children:e.jsx("pre",{className:"text-xs text-shell-text-secondary whitespace-pre-wrap leading-relaxed font-sans",children:Q?"Loading…":t.readme_content})})]}),r&&e.jsxs("div",{className:"px-5 py-4 border-b border-white/5",children:[e.jsx("h3",{className:"text-xs font-semibold text-shell-text-tertiary uppercase tracking-wider mb-2",children:"Latest Release"}),ke(r,`${t.owner}/${t.name}`)]}),e.jsxs("div",{className:"px-5 py-3 border-b border-white/5 flex items-center justify-between",children:[e.jsx("label",{htmlFor:`monitor-${t.name}`,className:"text-xs text-shell-text-secondary cursor-pointer",children:"Monitor releases"}),e.jsx(Te,{id:`monitor-${t.name}`,checked:je,onCheckedChange:te,"aria-label":"Monitor releases for this repository"})]}),e.jsxs("div",{className:"px-5 py-3 flex flex-wrap gap-2",children:[e.jsxs(x,{size:"sm",variant:"ghost",className:"text-xs gap-1.5",onClick:()=>window.open(a,"_blank","noopener,noreferrer"),"aria-label":"Open on GitHub",children:[e.jsx(K,{size:13,"aria-hidden":"true"}),"Open on GitHub"]}),e.jsxs(x,{size:"sm",variant:h?"secondary":"outline",className:"text-xs gap-1.5",onClick:()=>A(a),disabled:m||h,"aria-label":h?"Saved to library":"Save to Library",children:[e.jsx(F,{size:13,"aria-hidden":"true"}),h?"Saved":m?"Saving…":"Save to Library"]})]})]})})},$e=t=>{const a=`https://github.com/${t.repo}/${t.is_pull_request?"pull":"issues"}/${t.number}`;return e.jsx("main",{className:"flex-1 flex flex-col overflow-hidden","aria-label":`Issue ${t.number} detail`,children:e.jsxs("div",{className:"flex-1 overflow-y-auto",children:[e.jsxs("div",{className:"px-5 pt-4 pb-3 border-b border-white/5",children:[!f&&e.jsxs(x,{variant:"ghost",size:"sm",onClick:b,className:"text-xs mb-3 -ml-1 text-shell-text-secondary","aria-label":"Back to list",onKeyDown:r=>r.key==="Escape"&&b(),children:[e.jsx(P,{size:14,"aria-hidden":"true"}),"Back"]}),e.jsxs("div",{className:"flex items-start gap-2 mb-2",children:[t.is_pull_request?e.jsx(N,{size:16,className:"mt-0.5 shrink-0 text-accent","aria-hidden":"true"}):e.jsx(v,{size:16,className:"mt-0.5 shrink-0 text-green-400","aria-hidden":"true"}),e.jsx("h2",{className:"text-base font-semibold leading-snug flex-1",children:t.title}),e.jsx("span",{className:`shrink-0 text-[10px] px-1.5 py-0.5 rounded border ${he(t.state)}`,"aria-label":`Status: ${t.state}`,children:t.state})]}),e.jsxs("p",{className:"text-xs text-shell-text-tertiary mb-2",children:[t.repo," · ",t.author," · ",j(t.created_at)]}),t.labels.length>0&&e.jsx("div",{className:"flex flex-wrap gap-1 mb-2","aria-label":"Labels",children:t.labels.map(r=>e.jsx("span",{className:"px-1.5 py-0.5 rounded bg-white/5 border border-white/10 text-[10px] text-shell-text-secondary",children:r},r))})]}),e.jsx("div",{className:"px-5 py-3 flex-1",children:e.jsxs(Ae,{defaultValue:"discussion",children:[e.jsxs(Ge,{children:[e.jsx(M,{value:"discussion",children:"Discussion"}),e.jsx(M,{value:"history",children:"History"}),e.jsx(M,{value:"metadata",children:"Metadata"})]}),e.jsxs(U,{value:"discussion",children:[t.body&&e.jsx("div",{className:"rounded-lg bg-white/[0.02] border border-white/5 p-3 mb-3 mt-3",children:e.jsx("p",{className:"text-xs text-shell-text-secondary whitespace-pre-wrap leading-relaxed",children:Q?"Loading…":t.body})}),t.comments.length>0&&e.jsxs("div",{className:"space-y-2 mt-2","aria-label":"Comments",children:[e.jsxs("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary mb-1",children:[t.comments.length," comment",t.comments.length!==1?"s":""]}),t.comments.map((r,o)=>e.jsx(Ze,{comment:r,depth:0},o))]})]}),e.jsx(U,{value:"history",children:e.jsx("div",{className:"mt-3 text-xs text-shell-text-tertiary italic",children:"Issue history not available in this view."})}),e.jsx(U,{value:"metadata",children:e.jsx("div",{className:"mt-3 space-y-2",children:[{label:"Number",value:`#${t.number}`},{label:"State",value:t.state},{label:"Author",value:t.author},{label:"Repo",value:t.repo},{label:"Type",value:t.is_pull_request?"Pull Request":"Issue"},{label:"Created",value:t.created_at}].map(({label:r,value:o})=>e.jsxs("div",{className:"flex justify-between text-xs",children:[e.jsx("span",{className:"text-shell-text-tertiary",children:r}),e.jsx("span",{className:"text-shell-text-secondary",children:o})]},r))})})]})}),e.jsxs("div",{className:"px-5 py-3 flex flex-wrap gap-2 border-t border-white/5",children:[e.jsxs(x,{size:"sm",variant:"ghost",className:"text-xs gap-1.5",onClick:()=>window.open(a,"_blank","noopener,noreferrer"),"aria-label":"Open on GitHub",children:[e.jsx(K,{size:13,"aria-hidden":"true"}),"Open on GitHub"]}),e.jsxs(x,{size:"sm",variant:h?"secondary":"outline",className:"text-xs gap-1.5",onClick:()=>A(a),disabled:m||h,"aria-label":h?"Saved to library":"Save to Library",children:[e.jsx(F,{size:13,"aria-hidden":"true"}),h?"Saved":m?"Saving…":"Save to Library"]})]})]})})},ze=t=>{const a=t.repo??"",r=a?`https://github.com/${a}/releases/tag/${encodeURIComponent(t.tag)}`:"#";return e.jsx("main",{className:"flex-1 flex flex-col overflow-hidden","aria-label":`Release ${t.tag} detail`,children:e.jsxs("div",{className:"flex-1 overflow-y-auto",children:[e.jsxs("div",{className:"px-5 pt-4 pb-3 border-b border-white/5",children:[!f&&e.jsxs(x,{variant:"ghost",size:"sm",onClick:b,className:"text-xs mb-3 -ml-1 text-shell-text-secondary","aria-label":"Back to list",onKeyDown:o=>o.key==="Escape"&&b(),children:[e.jsx(P,{size:14,"aria-hidden":"true"}),"Back"]}),e.jsxs("div",{className:"flex items-start gap-2 mb-1",children:[e.jsx(xe,{size:16,className:"mt-0.5 shrink-0 text-accent","aria-hidden":"true"}),e.jsx("h2",{className:"text-lg font-semibold leading-snug",children:t.tag}),t.prerelease&&e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-400 border border-amber-500/30",children:"pre-release"})]}),a&&e.jsx("p",{className:"text-xs text-shell-text-tertiary mb-1",children:a}),e.jsxs("p",{className:"text-xs text-shell-text-tertiary",children:[t.author," · ",j(t.published_at)]})]}),t.body&&e.jsxs("div",{className:"px-5 py-4 border-b border-white/5",children:[e.jsx("h3",{className:"text-xs font-semibold text-shell-text-tertiary uppercase tracking-wider mb-2",children:"Release Notes"}),e.jsx("pre",{className:"text-xs text-shell-text-secondary whitespace-pre-wrap leading-relaxed font-sans",children:t.body})]}),t.assets.length>0&&e.jsxs("div",{className:"px-5 py-4 border-b border-white/5",children:[e.jsxs("h3",{className:"text-xs font-semibold text-shell-text-tertiary uppercase tracking-wider mb-2",children:["Assets (",t.assets.length,")"]}),e.jsx("div",{className:"space-y-1.5",role:"list","aria-label":"Release assets",children:t.assets.map(o=>e.jsxs("div",{className:"flex items-center gap-3 px-3 py-2 rounded-lg bg-white/[0.02] border border-white/5 text-xs",role:"listitem",children:[e.jsx(Ue,{size:11,"aria-hidden":"true",className:"text-shell-text-tertiary shrink-0"}),e.jsx("span",{className:"flex-1 truncate text-shell-text-secondary font-mono",children:o.name}),e.jsx("span",{className:"text-shell-text-tertiary shrink-0",children:Xe(o.size)}),e.jsxs("span",{className:"text-shell-text-tertiary shrink-0","aria-label":`${o.download_count} downloads`,children:[o.download_count.toLocaleString()," dl"]})]},o.name))})]}),e.jsxs("div",{className:"px-5 py-3 flex flex-wrap gap-2",children:[e.jsxs(x,{size:"sm",variant:"ghost",className:"text-xs gap-1.5",onClick:()=>window.open(r,"_blank","noopener,noreferrer"),"aria-label":"Open on GitHub",children:[e.jsx(K,{size:13,"aria-hidden":"true"}),"Open on GitHub"]}),e.jsxs(x,{size:"sm",variant:h?"secondary":"outline",className:"text-xs gap-1.5",onClick:()=>A(r),disabled:m||h||r==="#","aria-label":h?"Saved to library":"Save to Library",children:[e.jsx(F,{size:13,"aria-hidden":"true"}),h?"Saved":m?"Saving…":"Save to Library"]})]})]})})},Le=s?s.type==="repo"&&s.repo?Ce(s.repo):s.type==="issue"&&s.issue?$e(s.issue):s.type==="release"&&s.release?ze(s.release):null:null,Re=i.useMemo(()=>s?s.type==="repo"&&s.repo?`${s.repo.owner}/${s.repo.name}`:s.type==="issue"&&s.issue?s.issue.title:s.type==="release"&&s.release?s.release.tag:"":"",[s]),Ie=!f||le===null,_e=e.jsxs("div",{style:{display:"flex",flexDirection:"column",height:"100%"},children:[ne,e.jsx("div",{style:{padding:"8px 0 4px",borderBottom:"1px solid rgba(255,255,255,0.05)",flexShrink:0},children:e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},children:[{id:"starred",label:"Starred Repos",icon:w,badge:null},{id:"notifications",label:"Notifications",icon:ie,badge:C},{id:"watched",label:"Watched",icon:oe,badge:null}].map(({id:t,label:a,icon:r,badge:o},G,De)=>e.jsxs("button",{type:"button",onClick:()=>u(t),"aria-pressed":n===t,"aria-label":a,style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:n===t?"rgba(255,255,255,0.08)":"none",border:"none",borderBottom:G===De.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsx(r,{size:15,style:{color:"rgba(255,255,255,0.6)",flexShrink:0},"aria-hidden":"true"}),e.jsx("span",{style:{flex:1,fontSize:15,fontWeight:500,color:"rgba(255,255,255,0.9)"},children:a}),o!=null&&o>0&&e.jsx("span",{style:{fontSize:11,padding:"1px 7px",borderRadius:20,background:"var(--accent, #7c6be8)",color:"#fff",fontWeight:600},"aria-label":`${o} unread`,children:o}),e.jsx(q,{size:14,style:{color:"rgba(255,255,255,0.3)",flexShrink:0},"aria-hidden":"true"})]},t))})}),e.jsxs("div",{style:{padding:"8px 0 4px",borderBottom:"1px solid rgba(255,255,255,0.05)",flexShrink:0},children:[e.jsx("div",{style:{fontSize:12,textTransform:"uppercase",letterSpacing:.5,color:"rgba(255,255,255,0.45)",padding:"0 20px 6px",fontWeight:600},children:"Content"}),e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},children:[{id:"repos",label:"Repos",icon:y},{id:"issues",label:"Issues",icon:v},{id:"prs",label:"Pull Requests",icon:N},{id:"releases",label:"Releases",icon:ce}].map(({id:t,label:a,icon:r},o,G)=>e.jsxs("button",{type:"button",onClick:()=>V(t),"aria-pressed":S===t,"aria-label":a,style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"12px 16px",background:S===t?"rgba(255,255,255,0.08)":"none",border:"none",borderBottom:o===G.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsx(r,{size:14,style:{color:"rgba(255,255,255,0.6)",flexShrink:0},"aria-hidden":"true"}),e.jsx("span",{style:{flex:1,fontSize:14,color:"rgba(255,255,255,0.85)"},children:a})]},t))})]}),e.jsxs("div",{style:{flex:1,overflowY:"auto",padding:"8px 0 16px"},children:[e.jsx("div",{style:{fontSize:12,textTransform:"uppercase",letterSpacing:.5,color:"rgba(255,255,255,0.45)",padding:"4px 20px 8px",fontWeight:600},children:n==="notifications"?"Notifications":n==="watched"?"Watched":"Starred"}),e.jsx("div",{style:{padding:"0 12px 8px"},children:e.jsxs("div",{style:{position:"relative"},children:[e.jsx(de,{size:13,style:{position:"absolute",left:10,top:"50%",transform:"translateY(-50%)",color:"rgba(255,255,255,0.4)",pointerEvents:"none"},"aria-hidden":"true"}),e.jsx("input",{type:"search",value:p,onChange:t=>D(t.target.value),placeholder:"Search…","aria-label":"Search GitHub content",style:{width:"100%",padding:"8px 12px 8px 30px",borderRadius:10,background:"rgba(255,255,255,0.06)",border:"1px solid rgba(255,255,255,0.1)",color:"inherit",fontSize:13,outline:"none",boxSizing:"border-box"}})]})}),Z?e.jsx("div",{style:{padding:"24px 20px",textAlign:"center",fontSize:13,color:"rgba(255,255,255,0.4)"},role:"status","aria-live":"polite",children:"Loading…"}):g.length===0?e.jsx("div",{style:{padding:"32px 20px",textAlign:"center",fontSize:13,color:"rgba(255,255,255,0.4)"},children:p?"No results for your search":"Nothing here yet"}):e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},role:"list","aria-label":"GitHub items",children:n==="notifications"?g.map((t,a,r)=>e.jsxs("button",{type:"button",role:"listitem",onClick:()=>T(t),"aria-label":`Open ${t.is_pull_request?"PR":"issue"}: ${t.title}`,style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:"none",border:"none",borderBottom:a===r.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[t.is_pull_request?e.jsx(N,{size:13,style:{flexShrink:0,color:"rgba(130,140,255,0.9)"},"aria-hidden":"true"}):e.jsx(v,{size:13,style:{flexShrink:0,color:"rgba(80,200,120,0.9)"},"aria-hidden":"true"}),e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:14,fontWeight:500,color:"rgba(255,255,255,0.9)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",marginBottom:2},children:t.title}),e.jsx("div",{style:{fontSize:12,color:"rgba(255,255,255,0.45)"},children:t.repo})]}),e.jsx(q,{size:14,style:{color:"rgba(255,255,255,0.3)",flexShrink:0},"aria-hidden":"true"})]},`${t.repo}#${t.number}`)):g.map((t,a,r)=>e.jsxs("button",{type:"button",role:"listitem",onClick:()=>B(t),"aria-label":`Open ${t.owner}/${t.name}`,style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:"none",border:"none",borderBottom:a===r.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsxs("div",{style:{fontSize:14,fontWeight:600,color:"rgba(255,255,255,0.95)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",marginBottom:2},children:[e.jsxs("span",{style:{color:"rgba(255,255,255,0.5)"},children:[t.owner,"/"]}),t.name]}),t.description&&e.jsx("div",{style:{fontSize:12,color:"rgba(255,255,255,0.45)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:t.description}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,marginTop:4,fontSize:11,color:"rgba(255,255,255,0.35)"},children:[e.jsxs("span",{style:{display:"flex",alignItems:"center",gap:3},"aria-label":`${t.stars} stars`,children:[e.jsx(w,{size:9,"aria-hidden":"true"})," ",t.stars.toLocaleString()]}),e.jsxs("span",{style:{display:"flex",alignItems:"center",gap:3},"aria-label":`${t.forks} forks`,children:[e.jsx(W,{size:9,"aria-hidden":"true"})," ",t.forks.toLocaleString()]}),t.language&&e.jsx("span",{children:t.language})]})]}),e.jsx(q,{size:14,style:{color:"rgba(255,255,255,0.3)",flexShrink:0},"aria-hidden":"true"})]},`${t.owner}/${t.name}`))})]})]});return e.jsxs("div",{className:"flex flex-col h-full min-h-0 overflow-hidden bg-shell-surface text-shell-text select-none relative",children:[Ie&&e.jsx("div",{className:"flex items-center justify-between px-4 py-3 border-b border-white/5 shrink-0",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(y,{size:15,className:"text-accent shrink-0","aria-hidden":"true"}),e.jsx("h1",{className:"text-sm font-semibold",children:"GitHub"})]})}),e.jsx(He,{selectedId:le,onBack:b,listTitle:"GitHub",detailTitle:Re,listWidth:208,list:f?_e:e.jsxs("div",{className:"flex h-full overflow-hidden",children:[we,e.jsxs("div",{className:"flex-1 flex flex-col overflow-hidden",children:[ne,Se]})]}),detail:Le??(f?null:e.jsx("div",{className:"flex items-center justify-center h-full text-shell-text-tertiary text-sm",children:"Select an item to view details"}))})]})}export{nt as GitHubApp}; | |||
There was a problem hiding this comment.
The unauthenticated “Connect GitHub” actions are no-ops.
Both CTAs render, but neither has a real navigation or handler, so an unauthenticated user cannot actually start the connection flow from this screen. Either wire them to the intended auth path or remove the affordance until it exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/GitHubApp-IYMAlDty.js` at line 1, The unauthenticated
"Connect GitHub" buttons render with empty onClick handlers (no-op) so users
can't start auth; wire the two places that render the CTA— the small connect
button in the nav (the button rendered where R.authenticated is false, currently
onClick:()=>{}) and the banner button stored in ne (button with onClick:()=>{})
— to actually start the GitHub auth flow (for example by opening the app's
GitHub auth endpoint or calling the existing auth start endpoint) or remove the
buttons; update those handlers to call the auth start URL (window.open or
navigate) and ensure aria-labels remain correct.
Several sidebar filters are wired to state but never affect the data.
Watched reads from X, which is never populated, and the Repos / Issues / Pull Requests / Releases plus status filters never feed into g or any fetch path. Users can toggle these controls, but the list still shows the same starred repos or an always-empty watched view.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/GitHubApp-IYMAlDty.js` at line 1, The sidebar controls
(Watched, Content type S, and status filter pe) are wired to state (X, S, pe)
but never applied to the displayed list g and X is never populated; fix by (1)
populating X when the view is "watched" (call the appropriate fetch in I or a
new fetchWatched function and set X via set state X) so Watched shows data, (2)
update the useMemo that computes g (the variable g declared with i.useMemo) to
branch on S (repos/issues/prs/releases) and pe (status) and filter the
appropriate source array (J for repos, Y for notifications/issues, X for
watched, etc.) rather than always returning J/X/J, and (3) ensure the effect
that reacts to n (the view selector set via u) triggers the correct fetch (I for
starred, fetchWatched for watched, _ for notifications) so toggling those
sidebar buttons actually updates g; refer to symbols X, J, Y, n, S, pe, I, _,
and the useMemo that defines g.
| @@ -0,0 +1 @@ | |||
| import{r as l,j as t}from"./vendor-react-l6srOxy7.js";import{L as U,C as k,c as C,B as p}from"./toolbar-UW6q5pkx.js";import{ab as f,ak as B,y as M,an as O}from"./vendor-icons-wm645Jsx.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";const g=[".txt",".md",".pdf",".html",".json",".csv"],L=["text/plain","text/markdown","application/pdf","text/html","application/json","text/csv"];function R(c){return c<1024?`${c} B`:c<1024*1024?`${(c/1024).toFixed(1)} KB`:`${(c/(1024*1024)).toFixed(1)} MB`}function Y({windowId:c}){const[S,D]=l.useState([]),[r,E]=l.useState(""),[i,b]=l.useState([]),[A,x]=l.useState(!1),[h,j]=l.useState(!1),[u,v]=l.useState(0),[y,w]=l.useState(!1),[d,o]=l.useState(null),m=l.useRef(null);l.useEffect(()=>{(async()=>{try{const e=await fetch("/api/agents",{headers:{Accept:"application/json"}});if(e.ok&&(e.headers.get("content-type")??"").includes("application/json")){const a=await e.json();Array.isArray(a)&&a.length>0&&D(a.map(n=>String(n.name??"unknown")))}}catch{}})()},[]);const $=l.useCallback(e=>{var a;const s="."+((a=e.name.split(".").pop())==null?void 0:a.toLowerCase());return g.includes(s)||L.includes(e.type)},[]);function N(e){const a=e.filter($).map(n=>({id:`${n.name}-${Date.now()}-${Math.random().toString(36).slice(2,6)}`,file:n,name:n.name,size:n.size}));b(n=>[...n,...a]),o(null)}function z(e){e.preventDefault(),x(!1);const s=Array.from(e.dataTransfer.files);N(s)}function F(e){e.target.files&&N(Array.from(e.target.files)),e.target.value=""}function I(e){b(s=>s.filter(a=>a.id!==e))}async function T(){if(!r||i.length===0)return;j(!0),v(0),o(null);const e=i.length;let s=0;for(const a of i){const n=new FormData;n.append("file",a.file),n.append("agent",r);try{await fetch("/api/import/upload",{method:"POST",body:n})}catch{}s++,v(Math.round(s/e*100))}j(!1),o(`Uploaded ${e} file${e!==1?"s":""} for ${r}`)}async function P(){if(r){w(!0),o(null);try{(await fetch("/api/import/embed",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:r})})).ok?o("Embedding complete. Memory updated."):o("Embedding request sent. Check agent memory.")}catch{o("Could not reach embed endpoint. API may not be available.")}w(!1)}}return t.jsxs("div",{className:"flex flex-col h-full bg-shell-bg text-shell-text select-none",children:[t.jsxs("div",{className:"flex items-center gap-2 px-4 py-3 border-b border-white/5",children:[t.jsx(f,{size:18,className:"text-accent"}),t.jsx("h1",{className:"text-sm font-semibold",children:"Import"})]}),t.jsxs("div",{className:"flex-1 overflow-auto p-4 space-y-4",children:[t.jsxs("div",{className:"space-y-1.5",children:[t.jsx(U,{htmlFor:"import-agent",children:"Target Agent"}),t.jsxs("select",{id:"import-agent",value:r,onChange:e=>E(e.target.value),className:"flex h-9 w-full max-w-sm rounded-lg border border-white/10 bg-shell-bg-deep px-3 py-1 text-sm text-shell-text focus-visible:outline-none focus-visible:border-accent/40 focus-visible:ring-2 focus-visible:ring-accent/20",children:[t.jsx("option",{value:"",children:"Select an agent..."}),S.map(e=>t.jsx("option",{value:e,children:e},e))]})]}),t.jsx(k,{onDragOver:e=>{e.preventDefault(),x(!0)},onDragLeave:()=>x(!1),onDrop:z,className:`border-2 border-dashed transition-colors cursor-pointer ${A?"border-accent bg-accent/5":"border-white/10 hover:border-white/20"}`,onClick:()=>{var e;return(e=m.current)==null?void 0:e.click()},role:"button","aria-label":"Drop files here or click to browse",tabIndex:0,onKeyDown:e=>{var s;(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),(s=m.current)==null||s.click())},children:t.jsxs(C,{className:"flex flex-col items-center justify-center gap-3 p-8",children:[t.jsx(f,{size:32,className:"text-shell-text-tertiary"}),t.jsxs("div",{className:"text-center",children:[t.jsx("p",{className:"text-sm text-shell-text-secondary",children:"Drag and drop files here"}),t.jsx("p",{className:"text-xs text-shell-text-tertiary mt-1",children:g.join(", ")})]}),t.jsx(p,{variant:"secondary",size:"sm",onClick:e=>{var s;e.stopPropagation(),(s=m.current)==null||s.click()},children:"Browse"}),t.jsx("input",{ref:m,type:"file",multiple:!0,accept:g.join(","),onChange:F,className:"hidden","aria-label":"Select files to import"})]})}),i.length>0&&t.jsxs("div",{className:"space-y-1.5",children:[t.jsxs("h2",{className:"text-xs text-shell-text-secondary font-medium",children:["Queued Files (",i.length,")"]}),i.map(e=>t.jsx(k,{children:t.jsxs(C,{className:"flex items-center gap-3 px-3.5 py-2.5",children:[t.jsx(B,{size:14,className:"text-shell-text-tertiary shrink-0"}),t.jsx("span",{className:"text-sm flex-1 truncate",children:e.name}),t.jsx("span",{className:"text-xs text-shell-text-tertiary tabular-nums shrink-0",children:R(e.size)}),t.jsx(p,{variant:"ghost",size:"icon",onClick:()=>I(e.id),className:"h-7 w-7 hover:text-red-400 hover:bg-red-500/15","aria-label":`Remove ${e.name}`,children:t.jsx(M,{size:14})})]})},e.id))]}),h&&t.jsxs("div",{className:"space-y-1.5",children:[t.jsxs("div",{className:"flex items-center justify-between text-xs text-shell-text-secondary",children:[t.jsx("span",{children:"Uploading..."}),t.jsxs("span",{className:"tabular-nums",children:[u,"%"]})]}),t.jsx("div",{className:"h-2 w-full rounded-full bg-white/5",role:"progressbar","aria-valuenow":u,"aria-valuemin":0,"aria-valuemax":100,children:t.jsx("div",{className:"h-full rounded-full bg-accent transition-all",style:{width:`${u}%`}})})]}),d&&t.jsx("p",{className:`text-xs ${d.includes("complete")||d.includes("Uploaded")?"text-emerald-400":"text-amber-400"}`,children:d}),t.jsxs("div",{className:"flex gap-2",children:[t.jsxs(p,{onClick:T,disabled:!r||i.length===0||h,children:[t.jsx(f,{size:14}),h?"Uploading...":"Upload"]}),t.jsxs(p,{variant:"secondary",onClick:P,disabled:!r||y,className:"bg-violet-600 text-white hover:bg-violet-500",children:[t.jsx(O,{size:14}),y?"Embedding...":"Embed"]})]})]})]})}export{Y as ImportApp}; | |||
There was a problem hiding this comment.
Embed requests cannot succeed with the current payload.
/api/import/embed requires agent_name and files, but this client sends only { agent }. On top of that, /api/import/upload returns the filenames that need to be passed into the embed call, and those are discarded here. As written, the Embed action will 400 every time against tinyagentos/routes/import_data.py:45-102.
Proposed fix
- await fetch("/api/import/embed", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ agent: selectedAgent }),
- })
+ await fetch("/api/import/embed", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ agent_name: selectedAgent,
+ files: uploadedFilenames,
+ }),
+ })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/ImportApp-DBAV17Xb.js` at line 1, The embed call fails
because ImportApp's upload flow (function T) discards the filenames returned by
/api/import/upload and the embed action (function P) sends only {agent} instead
of the required {agent_name, files}; fix by capturing each upload response JSON
in T (await response.json()), collect the returned filenames into a new state
(e.g., [uploadedFiles, setUploadedFiles]), and append those filenames when all
uploads finish; then change P to POST JSON.stringify({agent_name: r, files:
uploadedFiles}) and guard P to require uploadedFiles.length>0; update references
to functions T and P and state names i (queued files) to wire the new
uploadedFiles state.
Upload status is reported as success even when the server rejects a file.
The loop swallows errors, never checks response.ok, and still advances progress plus the final “Uploaded N files” message. A partial or total failure will look successful to the user, and you still won’t have the authoritative filenames needed for embedding.
Proposed fix
- try {
- await fetch("/api/import/upload", { method: "POST", body: formData })
- } catch {}
- uploadedCount += 1
+ const res = await fetch("/api/import/upload", { method: "POST", body: formData })
+ if (!res.ok) {
+ const err = await res.json().catch(() => ({}))
+ throw new Error(err.error ?? `Upload failed (${res.status})`)
+ }
+ const body = await res.json()
+ uploadedFiles.push(body.filename)
+ uploadedCount += 1🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/ImportApp-DBAV17Xb.js` at line 1, The upload loop in T
currently swallows errors and always increments the progress and final success
message; update the T function to check each fetch response.ok (for the POST to
"/api/import/upload"), treat non-ok and caught exceptions as failures (do not
increment the successful-count), collect names/ids of successfully uploaded
files (use the existing i array entries), and compute progress based on total
files but report and set state o(...) and u accordingly to reflect successes vs
failures; ensure h (uploading) is still toggled off on completion and that the
final message uses the actual successful count (or lists failed filenames)
rather than assuming all files succeeded.
| @@ -0,0 +1 @@ | |||
| import{r as a,j as e}from"./vendor-react-l6srOxy7.js";import{I as O,B as o}from"./toolbar-UW6q5pkx.js";import{l as Te,i as _e,f as Ae}from"./knowledge-ES9kK4zW.js";import{M as $e}from"./MobileSplitView-CtNEF6zb.js";import{u as Me}from"./use-is-mobile-v5lglusa.js";import{a3 as W,g as re,b as le,aR as ie,f as A,S as Ee,a9 as q,$ as Pe,ay as ne,y as De,k as de,D as Fe,aG as Be}from"./vendor-icons-wm645Jsx.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";const K={posts:[],after:null};async function M(s,r,l){try{const i=await fetch(s,{...l,headers:{Accept:"application/json",...l==null?void 0:l.headers}});return!i.ok||!(i.headers.get("content-type")??"").includes("application/json")?r:await i.json()}catch{return r}}async function Ue(s){const r=new URLSearchParams({url:s});try{const l=await fetch(`/api/reddit/thread?${r}`,{headers:{Accept:"application/json"}});return!l.ok||!(l.headers.get("content-type")??"").includes("application/json")?null:await l.json()}catch{return null}}async function Oe(s,r="hot",l){const i=new URLSearchParams({name:s,sort:r}),x=await M(`/api/reddit/subreddit?${i}`,{...K});return{posts:Array.isArray(x.posts)?x.posts:[],after:x.after??null}}async function We(s,r){const l=new URLSearchParams({q:s});r&&l.set("subreddit",r);const i=await M(`/api/reddit/search?${l}`,{...K});return{posts:Array.isArray(i.posts)?i.posts:[],after:i.after??null}}async function qe(s){const l=new URLSearchParams().toString(),i=await M(`/api/reddit/saved${l?`?${l}`:""}`,{...K});return{posts:Array.isArray(i.posts)?i.posts:[],after:i.after??null}}async function Ke(){return M("/api/reddit/auth/status",{authenticated:!1})}async function ce(s,r){try{const l=await fetch("/api/knowledge/ingest",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({url:s,title:r??"",text:"",categories:[],source:"reddit-client"})});return!l.ok||!(l.headers.get("content-type")??"").includes("application/json")?null:await l.json()}catch{return null}}const Ve=["LocalLLaMA","selfhosted","homelab","linux"];function V(s){const r=Date.now()/1e3-s;return r<60?"just now":r<3600?`${Math.floor(r/60)}m ago`:r<86400?`${Math.floor(r/3600)}h ago`:r<604800?`${Math.floor(r/86400)}d ago`:new Date(s*1e3).toLocaleDateString()}function $(s){return s>=1e3?`${(s/1e3).toFixed(1)}k`:String(s)}function Ge(s){return s.replace(/^https?:\/\/(www\.)?reddit\.com/,"")}function oe({comment:s,maxDepth:r=4}){const[l,i]=a.useState(!1),[x,u]=a.useState(s.depth<r),j=s.author==="[deleted]"||s.body==="[deleted]",d=()=>i(m=>!m);return e.jsxs("li",{role:"listitem",className:"text-sm",style:{marginLeft:s.depth>0?"2rem":0},children:[e.jsxs("div",{className:"flex items-center gap-2 py-0.5",children:[e.jsx("button",{"aria-label":l?"Expand comment":"Collapse comment","aria-expanded":!l,onClick:d,className:"text-shell-text-tertiary hover:text-shell-text shrink-0",children:l?e.jsx(Fe,{size:13}):e.jsx(Be,{size:13})}),j?e.jsx("span",{className:"text-shell-text-tertiary italic text-xs",children:"[deleted]"}):e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"font-semibold text-xs text-shell-text",children:["u/",s.author]}),s.distinguished==="moderator"&&e.jsx("span",{className:"text-[10px] text-green-400 font-semibold",children:"MOD"}),e.jsxs("span",{className:"text-shell-text-tertiary text-xs",children:[$(s.score)," pts"]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:V(s.created_utc)}),s.edited&&e.jsx("span",{className:"text-shell-text-tertiary text-xs italic",children:"(edited)"})]})]}),!l&&!j&&e.jsx("p",{className:"text-shell-text-secondary text-xs whitespace-pre-wrap mt-0.5 ml-5 pb-1 leading-relaxed",children:s.body}),!l&&s.replies.length>0&&e.jsx("div",{className:"border-l border-white/5 ml-5 mt-0.5",children:x||s.depth<r?e.jsx("ul",{role:"list",className:"space-y-1",children:s.replies.map(m=>e.jsx(oe,{comment:m,maxDepth:r},m.id))}):e.jsxs("button",{className:"text-xs text-accent hover:underline ml-3 py-0.5",onClick:()=>u(!0),"aria-label":`Show ${s.replies.length} more replies`,children:["Show ",s.replies.length," more ",s.replies.length===1?"reply":"replies"]})})]})}function Je({post:s,savedItem:r,onOpen:l,onSave:i,saving:x}){const u=!!r,j=d=>{(d.key==="Enter"||d.key===" ")&&(d.preventDefault(),l(s))};return e.jsxs("div",{className:"border border-white/5 rounded-lg p-3 bg-shell-surface/30 hover:bg-shell-surface/50 transition-colors",role:"article",children:[e.jsx("div",{className:"flex items-start gap-2 mb-1",children:e.jsx("div",{className:"flex-1 min-w-0",children:e.jsx("button",{className:"text-left text-sm font-medium text-shell-text hover:text-accent transition-colors leading-snug cursor-pointer",onClick:()=>l(s),onKeyDown:j,tabIndex:0,"aria-label":`Open thread: ${s.title}`,children:s.title})})}),e.jsxs("div",{className:"flex items-center gap-2 flex-wrap mb-1.5",children:[e.jsxs("span",{className:"text-[11px] font-semibold px-1.5 py-0.5 rounded-full bg-orange-500/20 text-orange-400 border border-orange-500/30",children:["r/",s.subreddit]}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:["u/",s.author]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:[$(s.score)," pts"]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:[s.num_comments," comments"]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsx("span",{className:"text-xs text-shell-text-tertiary",children:V(s.created_utc)}),s.flair&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsx("span",{className:"text-[11px] px-1.5 py-0.5 rounded bg-white/5 text-shell-text-tertiary border border-white/10",children:s.flair})]})]}),s.is_self&&s.selftext&&e.jsx("p",{className:"text-xs text-shell-text-secondary line-clamp-2 mb-2 leading-relaxed",children:s.selftext}),u&&r.categories.length>0&&e.jsx("div",{className:"flex flex-wrap gap-1 mb-2",children:r.categories.map(d=>e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded-full bg-accent/10 text-accent border border-accent/20",children:d},d))}),e.jsx("div",{className:"flex items-center gap-2 mt-1",children:e.jsx(o,{variant:u?"secondary":"ghost",size:"sm",className:"h-6 text-xs gap-1 px-2",onClick:()=>i(s),disabled:x||u,"aria-label":u?"Saved to Library":"Save to Library",children:u?e.jsxs(e.Fragment,{children:[e.jsx(A,{size:11}),"Saved"]}):e.jsxs(e.Fragment,{children:[e.jsx(de,{size:11}),x?"Saving…":"Save to Library"]})})})]})}function rt({windowId:s}){const[r,l]=a.useState("feed"),[i,x]=a.useState(null),[u,j]=a.useState(!1),[d,m]=a.useState(null),[C,xe]=a.useState(Ve),[G,N]=a.useState(!1),[z,E]=a.useState(""),[f,y]=a.useState("subreddits"),[P,J]=a.useState({posts:[],after:null}),[L,Q]=a.useState(!1),[R,he]=a.useState("hot"),[I,D]=a.useState(""),[H,F]=a.useState(""),[g,pe]=a.useState({authenticated:!1}),[v,ue]=a.useState([]),[me,Y]=a.useState(null),[S,X]=a.useState("comments"),[h,B]=a.useState(null),[Z,ee]=a.useState(!1),[fe,w]=a.useState(!1),[Qe,ge]=a.useState(!1),k=Me(),be=a.useRef(null);a.useEffect(()=>{Ke().then(pe),b()},[]);const b=a.useCallback(async()=>{const{items:t}=await Te({source_type:"reddit",limit:200});ue(t)},[]),te=a.useCallback(async(t,n,c)=>{Q(!0);try{let p;c.trim()?p=await We(c.trim(),t??void 0):f==="saved"&&g.authenticated?p=await qe():t?p=await Oe(t,n):p={posts:[],after:null},J(p)}catch{J({posts:[],after:null})}Q(!1)},[f,g.authenticated]);a.useEffect(()=>{r==="feed"&&te(d,R,I)},[d,R,I,r,te]);const je=a.useCallback(async t=>{var p;l("thread"),X("comments"),x(null),w(!1),j(!0);const n=await Ue(t.url);x(n),j(!1);const c=v.find(ae=>ae.source_url===t.url||ae.source_url===`https://www.reddit.com${t.permalink}`);B(c??null),ge(c?(((p=c.monitor)==null?void 0:p.current_interval)??0)>0:!1)},[v]),U=a.useCallback(()=>{l("feed"),x(null),w(!1)},[]);a.useEffect(()=>{const t=n=>{n.key==="Escape"&&r==="thread"&&U()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[r,U]);const ye=a.useCallback(async t=>{Y(t.id),await ce(t.url,t.title),await b(),Y(null)},[b]),ve=a.useCallback(async()=>{if(!i)return;if(ee(!0),await ce(i.post.url,i.post.title)){await b();const n=v.find(c=>c.source_url===i.post.url);B(n??null)}ee(!1)},[i,v,b]),Ne=a.useCallback(async()=>{h&&(await _e(h.source_url,{title:h.title,categories:h.categories}),await b())},[h,b]),we=a.useCallback(async()=>{h&&(await Ae(h.id),B(null),w(!1),await b())},[h,b]),T=a.useCallback(()=>{const t=z.trim().replace(/^r\//,"");t&&!C.includes(t)&&(xe(n=>[...n,t]),m(t),y("subreddits")),E(""),N(!1)},[z,C]),Se=a.useCallback(t=>v.find(n=>n.source_url===t.url||n.source_url===`https://www.reddit.com${t.permalink}`),[v]),_=v.filter(t=>{var n;return t.source_type==="reddit"&&(((n=t.monitor)==null?void 0:n.current_interval)??0)>0}),ke=e.jsxs("nav",{className:"flex flex-col overflow-hidden h-full","aria-label":"Reddit navigation",children:[!k&&e.jsxs("div",{className:"flex items-center gap-2 px-3 py-3 border-b border-white/5 shrink-0",children:[e.jsx(W,{size:15,className:"text-orange-400"}),e.jsx("h1",{className:"text-sm font-semibold",children:"Reddit"})]}),e.jsx("div",{className:"flex-1 overflow-y-auto space-y-4",style:k?{padding:"8px 0 16px"}:{padding:"0.5rem"},children:k?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsxs("div",{style:{fontSize:12,textTransform:"uppercase",letterSpacing:.5,color:"rgba(255,255,255,0.45)",padding:"8px 20px 6px",fontWeight:600,display:"flex",alignItems:"center",justifyContent:"space-between"},children:[e.jsx("span",{children:"Subreddits"}),e.jsx("button",{"aria-label":"Add subreddit",onClick:()=>N(t=>!t),style:{color:"rgba(255,255,255,0.45)",background:"none",border:"none",cursor:"pointer",padding:"0 4px"},children:e.jsx(re,{size:14})})]}),e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},children:C.map((t,n,c)=>{const p=f==="subreddits"&&d===t;return e.jsxs("button",{type:"button","aria-pressed":p,onClick:()=>{m(t),y("subreddits"),D(""),F("")},style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:p?"rgba(255,255,255,0.08)":"none",border:"none",borderBottom:n===c.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsx("span",{style:{color:"#fb923c",fontSize:12,fontWeight:700,flexShrink:0},children:"r/"}),e.jsx("span",{style:{flex:1,fontSize:15,fontWeight:500,color:"rgba(255,255,255,0.9)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:t}),e.jsx("svg",{width:"8",height:"14",viewBox:"0 0 8 14",fill:"none",style:{color:"rgba(255,255,255,0.3)",flexShrink:0},children:e.jsx("path",{d:"M1 1L7 7L1 13",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})]},t)})})]}),e.jsxs("div",{children:[e.jsxs("div",{style:{fontSize:12,textTransform:"uppercase",letterSpacing:.5,color:"rgba(255,255,255,0.45)",padding:"0 20px 6px",fontWeight:600,display:"flex",alignItems:"center",gap:6},children:[e.jsx("span",{children:"Saved Posts"}),!g.authenticated&&e.jsx(le,{size:10})]}),e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},children:g.authenticated?e.jsxs("button",{type:"button","aria-pressed":f==="saved",onClick:()=>{y("saved"),m(null)},style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:f==="saved"?"rgba(255,255,255,0.08)":"none",border:"none",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsx("span",{style:{flex:1,fontSize:15,fontWeight:500,color:"rgba(255,255,255,0.9)"},children:"Reddit Saved"}),e.jsx("svg",{width:"8",height:"14",viewBox:"0 0 8 14",fill:"none",style:{color:"rgba(255,255,255,0.3)",flexShrink:0},children:e.jsx("path",{d:"M1 1L7 7L1 13",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})]}):e.jsx("div",{style:{padding:"14px 16px",fontSize:14,color:"rgba(255,255,255,0.4)",fontStyle:"italic"},children:"Not connected"})})]}),_.length>0&&e.jsxs("div",{children:[e.jsx("div",{style:{fontSize:12,textTransform:"uppercase",letterSpacing:.5,color:"rgba(255,255,255,0.45)",padding:"0 20px 6px",fontWeight:600},children:"Monitored"}),e.jsx("div",{style:{margin:"0 12px",borderRadius:16,background:"rgba(255,255,255,0.05)",border:"1px solid rgba(255,255,255,0.08)",overflow:"hidden"},children:_.map((t,n,c)=>e.jsxs("button",{type:"button",onClick:()=>y("monitored"),"aria-label":`Monitored: ${t.title}`,style:{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"14px 16px",background:"none",border:"none",borderBottom:n===c.length-1?"none":"1px solid rgba(255,255,255,0.06)",cursor:"pointer",color:"inherit",textAlign:"left"},children:[e.jsx(ie,{size:13,style:{flexShrink:0,color:"rgba(255,255,255,0.5)"}}),e.jsx("span",{style:{flex:1,fontSize:14,color:"rgba(255,255,255,0.85)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:t.title}),e.jsx("svg",{width:"8",height:"14",viewBox:"0 0 8 14",fill:"none",style:{color:"rgba(255,255,255,0.3)",flexShrink:0},children:e.jsx("path",{d:"M1 1L7 7L1 13",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})]},t.id))})]}),e.jsx("div",{style:{padding:"4px 20px 8px"},children:g.authenticated?e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[e.jsx("span",{style:{width:8,height:8,borderRadius:"50%",background:"#22c55e",flexShrink:0}}),e.jsxs("span",{style:{fontSize:12,color:"rgba(255,255,255,0.5)"},children:["u/",g.username]})]}):e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[e.jsx("span",{style:{width:8,height:8,borderRadius:"50%",background:"rgba(255,255,255,0.3)",flexShrink:0}}),e.jsx("a",{href:"/api/reddit/auth/login",style:{fontSize:12,color:"rgb(100,180,255)"},"aria-label":"Connect Reddit account",children:"Not connected"})]})})]}):e.jsxs(e.Fragment,{children:[e.jsxs("section",{children:[e.jsxs("div",{className:"flex items-center justify-between px-2 mb-1.5",children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary",children:"Subreddits"}),e.jsx("button",{"aria-label":"Add subreddit",onClick:()=>N(t=>!t),className:"text-shell-text-tertiary hover:text-accent transition-colors",children:e.jsx(re,{size:12})})]}),G&&e.jsxs("div",{className:"flex gap-1 mb-1 px-1",children:[e.jsx(O,{value:z,onChange:t=>E(t.target.value),placeholder:"r/subreddit",className:"h-6 text-xs flex-1","aria-label":"New subreddit name",onKeyDown:t=>{t.key==="Enter"&&T(),t.key==="Escape"&&N(!1)},autoFocus:!0}),e.jsx(o,{size:"sm",variant:"ghost",className:"h-6 px-1.5 text-xs",onClick:T,"aria-label":"Confirm add subreddit",children:e.jsx(A,{size:11})})]}),e.jsx("div",{className:"space-y-0.5",children:C.map(t=>{const n=f==="subreddits"&&d===t;return e.jsxs(o,{variant:n?"secondary":"ghost",size:"sm","aria-pressed":n,onClick:()=>{m(t),y("subreddits"),D(""),F("")},className:"w-full justify-start text-xs h-7 px-2 gap-1.5",children:[e.jsx("span",{className:"text-orange-400 text-[10px] font-bold",children:"r/"}),t]},t)})})]}),e.jsxs("section",{children:[e.jsxs("div",{className:"flex items-center gap-1.5 px-2 mb-1.5",children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary",children:"Saved Posts"}),!g.authenticated&&e.jsx(le,{size:10,className:"text-shell-text-tertiary"})]}),g.authenticated?e.jsx(o,{variant:f==="saved"?"secondary":"ghost",size:"sm","aria-pressed":f==="saved",onClick:()=>{y("saved"),m(null)},className:"w-full justify-start text-xs h-7 px-2",children:"Reddit Saved"}):e.jsx("p",{className:"text-[11px] text-shell-text-tertiary px-2 italic",children:"Not connected"})]}),e.jsxs("section",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary px-2 mb-1.5",children:"Monitored"}),e.jsx("div",{className:"space-y-0.5",children:_.length===0?e.jsx("p",{className:"text-[11px] text-shell-text-tertiary px-2 italic",children:"None yet"}):_.map(t=>e.jsxs(o,{variant:f==="monitored"&&d===t.id?"secondary":"ghost",size:"sm",onClick:()=>{y("monitored")},className:"w-full justify-start text-xs h-7 px-2 truncate","aria-label":`Monitored: ${t.title}`,children:[e.jsx(ie,{size:11,className:"shrink-0 mr-1"}),e.jsx("span",{className:"truncate",children:t.title})]},t.id))})]}),e.jsxs("section",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-shell-text-tertiary px-2 mb-1.5",children:"History"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary px-2 italic",children:"Coming soon"})]}),e.jsx("div",{className:"border-t border-white/5 px-3 py-2 shrink-0 mt-auto",children:g.authenticated?e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"w-2 h-2 rounded-full bg-green-500 shrink-0"}),e.jsxs("span",{className:"text-xs text-shell-text-secondary truncate",children:["u/",g.username]})]}):e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"w-2 h-2 rounded-full bg-shell-text-tertiary shrink-0"}),e.jsx("a",{href:"/api/reddit/auth/login",className:"text-xs text-accent hover:underline","aria-label":"Connect Reddit account",children:"Not connected"})]})})]})})]}),Ce=t=>{t.preventDefault(),D(H)},ze=e.jsxs("main",{className:"flex-1 flex flex-col overflow-hidden",children:[e.jsxs("div",{className:"px-4 py-3 border-b border-white/5 shrink-0 space-y-2",children:[e.jsxs("form",{onSubmit:Ce,className:"flex gap-2",role:"search",children:[e.jsx(O,{ref:be,value:H,onChange:t=>F(t.target.value),placeholder:d?`Search r/${d}…`:"Search Reddit…",className:"flex-1 h-8 text-sm","aria-label":"Search Reddit"}),e.jsx(o,{type:"submit",variant:"ghost",size:"sm",className:"h-8 px-2","aria-label":"Run search",children:e.jsx(Ee,{size:14})})]}),e.jsx("div",{className:"flex items-center gap-1",role:"group","aria-label":"Sort posts",children:["hot","new","top"].map(t=>e.jsx(o,{variant:R===t?"secondary":"ghost",size:"sm",className:"h-6 text-xs px-2 capitalize","aria-pressed":R===t,onClick:()=>he(t),children:t},t))})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-3 space-y-2",children:[L&&e.jsx("div",{className:"flex items-center justify-center py-12",children:e.jsx(q,{size:18,className:"animate-spin text-shell-text-tertiary"})}),!L&&!d&&f==="subreddits"&&!I&&e.jsxs("div",{className:"flex flex-col items-center justify-center py-16 text-shell-text-tertiary",children:[e.jsx(W,{size:36,className:"mb-3 opacity-30"}),e.jsx("p",{className:"text-sm",children:"Select a subreddit to browse"})]}),!L&&P.posts.length>0&&e.jsx("ul",{role:"list",className:"space-y-2",children:P.posts.map(t=>e.jsx("li",{role:"listitem",children:e.jsx(Je,{post:t,savedItem:Se(t),onOpen:je,onSave:ye,saving:me===t.id})},t.id))}),!L&&P.posts.length===0&&(d||I||f==="saved")&&e.jsx("div",{className:"flex flex-col items-center justify-center py-16 text-shell-text-tertiary",children:e.jsx("p",{className:"text-sm",children:"No posts found"})})]})]}),Le=(()=>{const t=(i==null?void 0:i.post)??null,n=(i==null?void 0:i.comments)??[];return e.jsxs("main",{className:"flex-1 flex flex-col overflow-hidden",children:[e.jsxs("div",{className:"px-3 py-2 border-b border-white/5 shrink-0 flex items-center justify-between gap-2 flex-wrap",children:[e.jsxs(o,{variant:"ghost",size:"sm",className:"h-7 text-xs gap-1.5",onClick:U,"aria-label":"Back to feed",children:[e.jsx(Pe,{size:13}),"Back to feed"]}),t&&e.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap",children:[e.jsxs("a",{href:`https://www.reddit.com${t.permalink}`,target:"_blank",rel:"noopener noreferrer",className:"flex items-center gap-1 text-xs text-shell-text-tertiary hover:text-accent transition-colors","aria-label":"Open on Reddit",children:[e.jsx(ne,{size:12}),"Reddit"]}),h?e.jsxs(e.Fragment,{children:[e.jsxs(o,{variant:"secondary",size:"sm",className:"h-6 text-xs gap-1 px-2",disabled:!0,"aria-label":"Already saved to Library",children:[e.jsx(A,{size:11}),"Saved"]}),e.jsxs(o,{variant:"ghost",size:"sm",className:"h-6 text-xs gap-1 px-2",onClick:Ne,"aria-label":"Re-ingest this thread",children:[e.jsx(q,{size:11}),"Re-ingest"]}),fe?e.jsxs(e.Fragment,{children:[e.jsx(o,{variant:"ghost",size:"sm",className:"h-6 text-xs px-2 text-red-400 hover:text-red-300",onClick:we,"aria-label":"Confirm delete from Library",children:"Confirm Delete"}),e.jsx(o,{variant:"ghost",size:"sm",className:"h-6 text-xs px-2",onClick:()=>w(!1),"aria-label":"Cancel delete",children:"Cancel"})]}):e.jsx(o,{variant:"ghost",size:"sm",className:"h-6 text-xs gap-1 px-2 text-red-400 hover:text-red-300",onClick:()=>w(!0),"aria-label":"Delete from Library",children:e.jsx(De,{size:11})})]}):e.jsxs(o,{variant:"ghost",size:"sm",className:"h-6 text-xs gap-1 px-2",onClick:ve,disabled:Z,"aria-label":"Save to Library",children:[e.jsx(de,{size:11}),Z?"Saving…":"Save to Library"]})]})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-4",children:[u&&e.jsx("div",{className:"flex items-center justify-center py-16",children:e.jsx(q,{size:20,className:"animate-spin text-shell-text-tertiary"})}),!u&&t&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx("h2",{className:"text-base font-semibold text-shell-text leading-snug",children:t.title}),e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs("span",{className:"text-[11px] font-semibold px-1.5 py-0.5 rounded-full bg-orange-500/20 text-orange-400 border border-orange-500/30",children:["r/",t.subreddit]}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:["by u/",t.author]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:[$(t.score)," pts"]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsxs("span",{className:"text-xs text-shell-text-tertiary",children:[Math.round(t.upvote_ratio*100),"% upvoted"]}),e.jsx("span",{className:"text-shell-text-tertiary text-xs",children:"·"}),e.jsx("span",{className:"text-xs text-shell-text-tertiary",children:V(t.created_utc)})]}),(h==null?void 0:h.summary)&&e.jsxs("div",{className:"bg-accent/5 border border-accent/20 rounded-lg px-3 py-2",children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wider text-accent mb-1 font-semibold",children:"Summary"}),e.jsx("p",{className:"text-xs text-shell-text-secondary leading-relaxed",children:h.summary})]}),t.is_self&&t.selftext&&e.jsx("div",{className:"bg-white/3 rounded-lg px-3 py-2 border border-white/5",children:e.jsx("p",{className:"text-sm text-shell-text-secondary whitespace-pre-wrap leading-relaxed",children:t.selftext})}),!t.is_self&&e.jsxs("a",{href:t.url,target:"_blank",rel:"noopener noreferrer",className:"text-xs text-accent hover:underline flex items-center gap-1","aria-label":`External link: ${t.url}`,children:[e.jsx(ne,{size:11}),Ge(t.url)||t.url]})]}),e.jsxs("div",{children:[e.jsx("div",{role:"tablist","aria-label":"Thread sections",className:"flex gap-1 border-b border-white/5 pb-0 mb-3",children:["comments","history","metadata"].map(c=>e.jsxs("button",{role:"tab","aria-selected":S===c,onClick:()=>X(c),className:["px-3 py-1.5 text-xs capitalize border-b-2 transition-colors",S===c?"border-accent text-accent":"border-transparent text-shell-text-tertiary hover:text-shell-text"].join(" "),children:[c,c==="comments"&&e.jsxs("span",{className:"ml-1 text-[10px] opacity-60",children:["(",t.num_comments,")"]})]},c))}),S==="comments"&&e.jsx("ul",{role:"list",className:"space-y-2",children:n.length===0?e.jsx("li",{className:"text-sm text-shell-text-tertiary py-4 text-center italic",children:"No comments yet"}):n.map(c=>e.jsx(oe,{comment:c},c.id))}),S==="history"&&e.jsx("div",{className:"text-sm text-shell-text-tertiary py-4",children:h?e.jsx("p",{className:"italic",children:"Monitoring snapshots will appear here when available."}):e.jsx("p",{className:"italic",children:"Save this thread to the Library to enable monitoring."})}),S==="metadata"&&t&&e.jsx("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-2 text-xs",children:[["Subreddit",`r/${t.subreddit}`],["Author",`u/${t.author}`],["Score",$(t.score)],["Upvote ratio",`${Math.round(t.upvote_ratio*100)}%`],["Comments",String(t.num_comments)],["Flair",t.flair||"—"],["Type",t.is_self?"Text post":"Link post"],["Posted",new Date(t.created_utc*1e3).toLocaleString()],["Permalink",t.permalink]].map(([c,p])=>e.jsxs("div",{className:"contents",children:[e.jsx("dt",{className:"text-shell-text-tertiary font-medium truncate",children:c}),e.jsx("dd",{className:"text-shell-text truncate",title:p,children:p})]},c))})]})]}),!u&&!i&&e.jsx("div",{className:"flex flex-col items-center justify-center py-16 text-shell-text-tertiary",children:e.jsx("p",{className:"text-sm",children:"Failed to load thread"})})]})]})})(),se=d,Re=a.useCallback(()=>{m(null),l("feed"),x(null),w(!1)},[]),Ie=r==="thread"?Le:ze;return e.jsxs("div",{className:"flex flex-col h-full min-h-0 overflow-hidden bg-shell-base text-shell-text relative",children:[e.jsx($e,{selectedId:se,onBack:Re,listTitle:"Reddit",detailTitle:d?`r/${d}`:void 0,listWidth:208,list:ke,detail:se!==null?Ie:k?null:e.jsxs("div",{className:"flex flex-col items-center justify-center h-full text-shell-text-tertiary",children:[e.jsx(W,{size:36,className:"mb-3 opacity-20"}),e.jsx("p",{className:"text-sm",children:"Select a subreddit"})]})}),k&&G&&e.jsx("div",{className:"absolute inset-0 z-50 flex items-end bg-black/50 backdrop-blur-sm",onClick:()=>N(!1),role:"dialog","aria-modal":"true","aria-label":"Add subreddit",children:e.jsxs("div",{style:{borderRadius:"20px 20px 0 0",width:"100%",background:"var(--shell-surface, #1a1a2e)",padding:"20px 16px 32px"},onClick:t=>t.stopPropagation(),children:[e.jsx("p",{className:"text-sm font-semibold mb-3",children:"Add Subreddit"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(O,{value:z,onChange:t=>E(t.target.value),placeholder:"r/subreddit",className:"flex-1","aria-label":"New subreddit name",onKeyDown:t=>{t.key==="Enter"&&T(),t.key==="Escape"&&N(!1)},autoFocus:!0}),e.jsxs(o,{onClick:T,"aria-label":"Confirm add subreddit",children:[e.jsx(A,{size:14}),"Add"]})]})]})})]})}export{rt as RedditApp}; | |||
There was a problem hiding this comment.
The monitored view never loads any posts.
Clicking a monitored entry only switches f to "monitored", but te() has no monitored branch, so P.posts falls back to empty and the main pane shows “No posts found.” This makes the monitored section a dead end right now.
| @@ -1,4 +1,4 @@ | |||
| import{r as s,j as e}from"./vendor-react-l6srOxy7.js";import{B as b,C as w,L as N,I as E,S as T,T as Z}from"./toolbar-UW6q5pkx.js";import{u as ee}from"./main-BXOeBesV.js";import{t as te,a1 as se,ao as ae,ad as K,aa as P,at as V,au as le,av as ne,l as re,U as ce,a0 as ie,r as F,f as D,aw as Y,g as oe,y as de,c as xe,ac as me,ax as ue,X as he}from"./vendor-icons-DcMSPw1y.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";import"./tokens-DIiUixLu.js";import"./vendor-codemirror-Byxbuxf1.js";function pe(r,l,t=a=>a,x=a=>({value:a})){const a=`taos-pref:${r}`,[d,i]=s.useState(()=>{try{const p=localStorage.getItem(a);if(p!==null)return JSON.parse(p)}catch{}return l}),[n,c]=s.useState(!1),m=s.useRef(null);s.useEffect(()=>{let p=!1;return(async()=>{try{const o=await fetch(`/api/preferences/${encodeURIComponent(r)}`);if(!o.ok){c(!0);return}const j=await o.json();if(p)return;if(j&&typeof j=="object"&&Object.keys(j).length>0){const v=t(j);i(v);try{localStorage.setItem(a,JSON.stringify(v))}catch{}}c(!0)}catch{c(!0)}})(),()=>{p=!0}},[r]);const h=s.useCallback(p=>{i(o=>{const j=typeof p=="function"?p(o):p;try{localStorage.setItem(a,JSON.stringify(j))}catch{}return m.current!==null&&clearTimeout(m.current),m.current=setTimeout(()=>{m.current=null,fetch(`/api/preferences/${encodeURIComponent(r)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(x(j))}).catch(()=>{})},500),j})},[a,x,r]);return[d,h,{loaded:n}]}const fe=[{id:"system",label:"System Info",icon:te},{id:"storage",label:"Storage",icon:se},{id:"memory",label:"Memory",icon:ae},{id:"backup",label:"Backup & Restore",icon:K},{id:"updates",label:"Updates",icon:P},{id:"advanced",label:"Advanced",icon:V},{id:"shortcuts",label:"Keyboard Shortcuts",icon:le},{id:"accessibility",label:"Accessibility",icon:ne},{id:"desktop",label:"Desktop & Dock",icon:re},{id:"users",label:"Users",icon:ce}],je={cpu:"Detecting...",ram:"Detecting...",npu:"Detecting...",gpu:"Detecting...",disk:"Detecting...",os:"Detecting..."},ye=[{label:"Models",size:"--",bytes:0,maxBytes:1},{label:"Data",size:"--",bytes:0,maxBytes:1},{label:"App Catalog",size:"--",bytes:0,maxBytes:1}];async function G(r,l){try{const t=await fetch(r,{headers:{Accept:"application/json"}});return!t.ok||!(t.headers.get("content-type")??"").includes("application/json")?l:await t.json()}catch{return l}}function be({value:r,max:l}){const t=l>0?Math.min(100,r/l*100):0;return e.jsx("div",{className:"h-2 w-full rounded-full bg-white/5",role:"progressbar","aria-valuenow":t,"aria-valuemin":0,"aria-valuemax":100,children:e.jsx("div",{className:"h-full rounded-full bg-sky-500 transition-all",style:{width:`${t}%`}})})}function ge(){const[r,l]=s.useState(je),[t,x]=s.useState(!1),[a,d]=s.useState(!1),i=s.useCallback(async()=>{var m,h,p,o,j,v,k,S,g,R,_,I,y;x(!0);const c=await G("/api/system",null);if(c!=null&&c.hardware||c!=null&&c.resources){const u=c.hardware??{},U=c.resources??{},C=U.ram_total_mb??u.ram_mb??0,$=U.disk_total_gb??((m=u.disk)==null?void 0:m.total_gb)??0,A=((h=u.cpu)==null?void 0:h.model)??((p=u.cpu)==null?void 0:p.soc)??"Unknown",B=(o=u.cpu)!=null&&o.cores?` × ${u.cpu.cores}`:"",L=(j=u.cpu)!=null&&j.arch?` (${u.cpu.arch})`:"",f=((v=u.gpu)==null?void 0:v.model)||((k=u.gpu)==null?void 0:k.type)||"None",z=(S=u.gpu)!=null&&S.vram_mb&&u.gpu.vram_mb>0?` (${(u.gpu.vram_mb/1024).toFixed(1)} GB)`:"",M=(g=u.npu)!=null&&g.type&&u.npu.type!=="none"?u.npu.type:"None",q=(R=u.npu)!=null&&R.tops&&u.npu.tops>0?` · ${u.npu.tops} TOPS`:"",X=(_=u.disk)!=null&&_.type?` ${u.disk.type}`:"",J=[(I=u.os)==null?void 0:I.distro,(y=u.os)==null?void 0:y.version].filter(Boolean),Q=J.length>0?J.join(" "):"—";l({cpu:`${A}${B}${L}`,ram:C>=1024?`${(C/1024).toFixed(1)} GB`:C>0?`${C} MB`:"—",npu:`${M}${q}`,gpu:`${f}${z}`,disk:$>0?`${$} GB${X}`:"—",os:Q})}else l({cpu:"Unavailable",ram:"Unavailable",npu:"Unavailable",gpu:"Unavailable",disk:"Unavailable",os:"Unavailable"});x(!1)},[]);s.useEffect(()=>{i()},[i]);const n=[["CPU",r.cpu],["RAM",r.ram],["NPU",r.npu],["GPU",r.gpu],["Disk",r.disk],["OS",r.os]];return e.jsxs("section",{"aria-label":"System information",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"System Information"}),e.jsx("div",{className:"rounded-2xl bg-white/[0.04] border border-white/[0.06] overflow-x-auto backdrop-blur-sm",children:e.jsx("table",{className:"w-full text-sm min-w-[360px]",children:e.jsx("tbody",{children:n.map(([c,m])=>e.jsxs("tr",{className:"border-b border-white/5 last:border-0",children:[e.jsx("td",{className:"px-5 py-3 text-shell-text-secondary font-medium w-32",children:c}),e.jsx("td",{className:"px-5 py-3",children:m})]},c))})})}),e.jsxs("div",{className:"mt-3 flex items-center gap-2 flex-wrap",children:[e.jsxs(b,{variant:"outline",size:"sm",onClick:i,disabled:t,children:[e.jsx(P,{size:14,className:t?"animate-spin":""}),"Re-detect Hardware"]}),e.jsxs(b,{variant:"outline",size:"sm",onClick:async()=>{d(!0);try{await fetch("/api/system/restart/prepare",{method:"POST"})}catch{}},"aria-label":"Restart taOS server",children:[e.jsx(P,{size:14}),"Restart Server"]})]}),e.jsx("p",{className:"mt-2 text-xs text-shell-text-tertiary",children:"Restart the server to apply settings changes that require a reload."}),a&&e.jsx(W,{onClose:()=>d(!1)})]})}function Ne(){const[r,l]=s.useState(ye);return s.useEffect(()=>{G("/api/settings/storage",null).then(t=>{t&&Array.isArray(t)?l(t):l([{label:"Models",size:"4.2 GB",bytes:4200,maxBytes:32e3},{label:"Data",size:"1.8 GB",bytes:1800,maxBytes:32e3},{label:"App Catalog",size:"320 MB",bytes:320,maxBytes:32e3}])})},[]),e.jsxs("section",{"aria-label":"Storage usage",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Storage Usage"}),e.jsx("div",{className:"space-y-3",children:r.map(t=>e.jsxs(w,{className:"p-4",children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("span",{className:"text-sm font-medium",children:t.label}),e.jsx("span",{className:"text-sm text-shell-text-secondary tabular-nums",children:t.size})]}),e.jsx(be,{value:t.bytes,max:t.maxBytes})]},t.label))})]})}const ve=[{key:"capture_conversations",label:"Conversations",desc:"Messages you send to agents in the Message Hub"},{key:"capture_notes",label:"Notes",desc:"Notes from the Text Editor app"},{key:"capture_files",label:"File activity",desc:"Files you upload or open"},{key:"capture_searches",label:"Search queries",desc:"What you search for in global search"}];function we(){const[r,l]=s.useState(null),[t,x]=s.useState(null),[a,d]=s.useState(null);s.useEffect(()=>{fetch("/api/user-memory/settings").then(n=>n.ok?n.json():null).then(n=>{l(n||{})}).catch(()=>{l({}),d("Could not load memory settings.")}),fetch("/api/user-memory/stats").then(n=>n.ok?n.json():null).then(n=>{n&&x(n)}).catch(()=>{})},[]);const i=(n,c)=>{const m={...r||{},[n]:c};l(m),fetch("/api/user-memory/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({[n]:c})}).then(h=>{h.ok?d(null):d(`Failed to save setting (${h.status})`)}).catch(()=>d("Could not reach backend."))};return r?e.jsxs("section",{"aria-label":"Memory capture settings",children:[e.jsx("h2",{className:"text-lg font-semibold mb-2",children:"Memory Capture"}),e.jsx("p",{className:"text-sm text-shell-text-tertiary mb-5",children:"Choose what activity gets saved to your personal memory index. All data stays on this device."}),a&&e.jsxs("p",{className:"mb-3 text-xs text-amber-400 flex items-center gap-1.5",children:[e.jsx(F,{size:12})," ",a]}),e.jsx("div",{className:"space-y-2",children:ve.map(n=>{const c=!!r[n.key],m=`capture-${String(n.key)}`;return e.jsxs(w,{className:"p-4 flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{htmlFor:m,className:"text-sm font-medium text-shell-text",children:n.label}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mt-0.5",children:n.desc})]}),e.jsx(T,{id:m,checked:c,onCheckedChange:h=>i(n.key,h),"aria-label":`Capture ${n.label}`})]},String(n.key))})}),t&&e.jsxs(w,{className:"mt-6 p-4",children:[e.jsx("h3",{className:"text-sm font-medium mb-3",children:"Stored chunks"}),e.jsxs("div",{className:"text-xs text-shell-text-secondary mb-2 tabular-nums",children:["Total: ",t.total]}),Object.keys(t.collections||{}).length>0?e.jsx("ul",{className:"space-y-1 text-xs text-shell-text-tertiary",children:Object.entries(t.collections).map(([n,c])=>e.jsxs("li",{className:"flex justify-between tabular-nums",children:[e.jsx("span",{children:n}),e.jsx("span",{children:c})]},n))}):e.jsx("p",{className:"text-xs text-shell-text-tertiary",children:"No memories captured yet."})]})]}):e.jsxs("section",{"aria-label":"Memory capture settings",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Memory Capture"}),e.jsx("p",{className:"text-sm text-shell-text-tertiary",children:"Loading..."})]})}function ke(){const[r,l]=s.useState(null),[t,x]=s.useState(!1),a=async()=>{x(!0),l(null);try{const d=await fetch("/api/backup",{method:"POST"});d.ok?l("Backup created successfully."):l(`Backup failed (${d.status}). API may not be available yet.`)}catch{l("Could not reach backup endpoint. API not available yet.")}x(!1)};return e.jsxs("section",{"aria-label":"Backup and restore",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Backup & Restore"}),e.jsxs(w,{className:"p-4 space-y-4",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium mb-2",children:"Create Backup"}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mb-3",children:"Export all agents, memory, and configuration as a backup archive."}),e.jsxs(b,{size:"sm",onClick:a,disabled:t,children:[e.jsx(K,{size:14,className:t?"animate-bounce":""}),t?"Creating...":"Create Backup"]}),r&&e.jsx("p",{className:`mt-2 text-xs ${r.includes("success")?"text-emerald-400":"text-amber-400"}`,children:r})]}),e.jsx("hr",{className:"border-white/5"}),e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium mb-2",children:"Restore from Backup"}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mb-3",children:"Upload a previously created backup archive to restore."}),e.jsxs("label",{className:"flex flex-col items-center gap-2 p-6 rounded-lg border-2 border-dashed border-white/10 hover:border-white/20 transition-colors cursor-pointer",children:[e.jsx(me,{size:24,className:"text-shell-text-tertiary"}),e.jsx("span",{className:"text-xs text-shell-text-tertiary",children:"Click to select a backup file"}),e.jsx("input",{type:"file",accept:".tar.gz,.zip,.bak",className:"hidden","aria-label":"Upload backup file"})]})]})]})]})}function W({onClose:r}){const[l,t]=s.useState(null),[x,a]=s.useState(!1);s.useEffect(()=>{let n=!1,c=null,m=null,h=!1;const p=()=>{h||n||(h=!0,c&&clearInterval(c),m=setInterval(async()=>{if(!n)try{(await fetch("/api/settings/update-status")).ok&&(a(!0),m&&clearInterval(m),setTimeout(()=>{n||window.location.reload()},500))}catch{}},2e3))};return c=setInterval(async()=>{if(!n)try{const o=await fetch("/api/system/restart/status");if(o.ok){const j=await o.json();t(j),j.phase==="restarting"&&p()}}catch{p()}},1e3),()=>{n=!0,c&&clearInterval(c),m&&clearInterval(m)}},[]);const d=l?Object.entries(l.agents):[];function i(n){return n==="ready"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300",children:"ready"}):n==="timeout"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-300",children:"timeout"}):n==="error"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-red-500/20 text-red-300",children:"error"}):e.jsxs("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-sky-500/20 text-sky-300 flex items-center gap-1",children:[e.jsx(P,{size:10,className:"animate-spin"}),n]})}return e.jsx("div",{role:"dialog","aria-modal":"true","aria-label":"Restart progress",className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60",children:e.jsxs("div",{className:"bg-shell-surface border border-white/10 rounded-xl p-6 w-full max-w-md shadow-xl space-y-4",children:[e.jsx("h3",{className:"text-base font-semibold",children:x?"Restarted — reloading…":d.length>0?"Preparing agents for restart":"Restarting server…"}),d.length>0&&e.jsx("ul",{className:"space-y-1","aria-label":"Agent preparation status",children:d.map(([n,c])=>e.jsxs("li",{className:"flex items-center justify-between text-sm",children:[e.jsx("span",{className:"text-shell-text-secondary",children:n}),i(c.status)]},n))}),(l==null?void 0:l.phase)==="restarting"&&!x&&e.jsx("p",{className:"text-xs text-shell-text-tertiary",children:"Waiting for server to come back…"}),!l&&e.jsxs("p",{className:"text-xs text-shell-text-tertiary flex items-center gap-1",children:[e.jsx(P,{size:12,className:"animate-spin"})," Connecting…"]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(b,{variant:"outline",size:"sm",onClick:r,"aria-label":"Cancel restart progress dialog",children:"Cancel"})})]})})}function Se(){const[r,l]=s.useState(!1),[t,x]=s.useState(!1),[a,d]=s.useState(null),[i,n]=s.useState(null),[c,m]=s.useState({check_enabled:!0,auto_apply:!1,auto_restart:!1}),[h,p]=s.useState(null),[o,j]=s.useState(!1),[v,k]=s.useState(!1);s.useEffect(()=>{(async()=>{try{const y=await fetch("/api/preferences/auto-update");if(y.ok){const u=await y.json();u&&typeof u=="object"&&m({check_enabled:u.check_enabled??!0,auto_apply:u.auto_apply??!1,auto_restart:u.auto_restart??!1})}}catch{}try{const y=await fetch("/api/settings/update-check");y.ok&&d(await y.json())}catch{}try{const y=await fetch("/api/settings/update-status");y.ok&&p(await y.json())}catch{}})()},[]);const S=s.useCallback(async y=>{m(y);try{await fetch("/api/preferences/auto-update",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(y)})}catch{}},[]),g=async()=>{l(!0),n(null);try{const y=await fetch("/api/settings/update-check");if(y.ok){const u=await y.json();d(u),n(u.has_updates?"A new version is available.":"You are up to date.")}else n("Update check not available.")}catch{n("Could not reach update server.")}l(!1)},R=async()=>{x(!0),n(null);try{const y=await fetch("/api/settings/update",{method:"POST"});if(y.ok){const u=await y.json().catch(()=>({}));if(u.status==="restarting")j(!0);else{n(u.message??"Update applied. Restart the server to finish."),k(!0);const U=await fetch("/api/settings/update-check");U.ok&&d(await U.json());const C=await fetch("/api/settings/update-status");C.ok&&p(await C.json())}}else{const u=await y.json().catch(()=>({}));n(u.error??"Update failed.")}}catch{n("Could not apply update.")}x(!1)},_=async()=>{j(!0);try{await fetch("/api/system/restart/prepare",{method:"POST"})}catch{}},I=!!(h!=null&&h.pending_restart_sha);return e.jsxs("section",{"aria-label":"System updates",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Updates"}),I&&e.jsxs("div",{className:"mb-4 flex items-center justify-between gap-3 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm text-amber-200",children:[e.jsx(F,{size:16,className:"shrink-0"}),e.jsxs("span",{children:["Update pulled — restart to finish applying (",h.pending_restart_sha.slice(0,7),")"]})]}),e.jsx(b,{size:"sm",onClick:_,"aria-label":"Restart server to apply update",children:"Restart now"})]}),e.jsxs(w,{className:"p-4 space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 rounded-lg bg-white/5 text-sky-400",children:e.jsx(xe,{size:20})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"text-sm font-medium",children:"taOS"}),a!=null&&a.has_updates&&a.new_commit?e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("p",{className:"text-xs text-shell-text-tertiary tabular-nums",children:[e.jsx("span",{className:"text-white/40",children:"installed "}),a.current_commit]}),e.jsxs("p",{className:"text-xs text-amber-300/90 tabular-nums",children:[e.jsx("span",{className:"text-amber-300/50",children:"available "}),a.new_commit]})]}):e.jsx("p",{className:"text-xs text-shell-text-tertiary tabular-nums",children:(a==null?void 0:a.current_commit)??"v0.1.0-dev"})]}),(a==null?void 0:a.has_updates)&&e.jsx("span",{className:"text-[10px] px-2 py-1 rounded-full font-semibold bg-amber-500/20 text-amber-300",children:"Update available"})]}),e.jsxs("div",{className:"flex gap-2 flex-wrap",children:[e.jsxs(b,{variant:"outline",size:"sm",onClick:g,disabled:r,children:[e.jsx(P,{size:14,className:r?"animate-spin":""}),r?"Checking...":"Check Now"]}),v?e.jsx(b,{size:"sm",onClick:_,"aria-label":"Restart server to apply update",children:"Restart Now"}):a!=null&&a.has_updates?e.jsx(b,{size:"sm",onClick:R,disabled:t,children:t?"Installing...":"Install Update"}):null]}),i&&e.jsxs("div",{className:"flex items-start gap-2 text-xs",children:[i.includes("up to date")||i.includes("applied")?e.jsx(D,{size:14,className:"text-emerald-400 shrink-0 mt-0.5"}):e.jsx(F,{size:14,className:"text-amber-400 shrink-0 mt-0.5"}),e.jsx("span",{className:"text-shell-text-secondary",children:i})]}),e.jsxs("div",{className:"border-t border-white/5 pt-4 space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Check for updates automatically"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:"Polls GitHub hourly and notifies when a new version is available."})]}),e.jsx(T,{checked:c.check_enabled??!0,onCheckedChange:y=>S({...c,check_enabled:y})})]}),e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Install updates automatically"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:"Pulls + installs new versions as soon as they're detected. You'll still need to restart the server manually."})]}),e.jsx(T,{checked:c.auto_apply??!1,onCheckedChange:y=>S({...c,auto_apply:y}),disabled:!(c.check_enabled??!0)})]}),e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Automatically restart after update"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:c.auto_restart?"Server will restart automatically once an update is pulled.":"We'll remind you every 6 hours when a restart is pending."})]}),e.jsx(T,{checked:c.auto_restart??!1,onCheckedChange:y=>S({...c,auto_restart:y}),"aria-label":"Automatically restart after update"})]})]})]}),o&&e.jsx(W,{onClose:()=>j(!1)})]})}function Ce(){const[r,l]=s.useState(`# taOS Configuration | |||
| import{r as s,j as e}from"./vendor-react-l6srOxy7.js";import{B as b,C as w,L as N,I as E,S as T,T as Z}from"./toolbar-UW6q5pkx.js";import{u as ee}from"./main-DgK4yEp2.js";import{t as te,a0 as se,an as ae,ac as K,a9 as P,as as V,at as le,au as ne,l as re,U as ce,$ as ie,r as F,f as D,av as Y,g as oe,y as de,c as xe,ab as me,aw as ue,X as he}from"./vendor-icons-wm645Jsx.js";import"./vendor-radix-BhM7AEEG.js";import"./vendor-layout-B-pp9n1f.js";import"./tokens-BWEexfPB.js";import"./vendor-codemirror-CL2HhW7v.js";function pe(r,l,t=a=>a,x=a=>({value:a})){const a=`taos-pref:${r}`,[d,i]=s.useState(()=>{try{const p=localStorage.getItem(a);if(p!==null)return JSON.parse(p)}catch{}return l}),[n,c]=s.useState(!1),m=s.useRef(null);s.useEffect(()=>{let p=!1;return(async()=>{try{const o=await fetch(`/api/preferences/${encodeURIComponent(r)}`);if(!o.ok){c(!0);return}const j=await o.json();if(p)return;if(j&&typeof j=="object"&&Object.keys(j).length>0){const v=t(j);i(v);try{localStorage.setItem(a,JSON.stringify(v))}catch{}}c(!0)}catch{c(!0)}})(),()=>{p=!0}},[r]);const h=s.useCallback(p=>{i(o=>{const j=typeof p=="function"?p(o):p;try{localStorage.setItem(a,JSON.stringify(j))}catch{}return m.current!==null&&clearTimeout(m.current),m.current=setTimeout(()=>{m.current=null,fetch(`/api/preferences/${encodeURIComponent(r)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(x(j))}).catch(()=>{})},500),j})},[a,x,r]);return[d,h,{loaded:n}]}const fe=[{id:"system",label:"System Info",icon:te},{id:"storage",label:"Storage",icon:se},{id:"memory",label:"Memory",icon:ae},{id:"backup",label:"Backup & Restore",icon:K},{id:"updates",label:"Updates",icon:P},{id:"advanced",label:"Advanced",icon:V},{id:"shortcuts",label:"Keyboard Shortcuts",icon:le},{id:"accessibility",label:"Accessibility",icon:ne},{id:"desktop",label:"Desktop & Dock",icon:re},{id:"users",label:"Users",icon:ce}],je={cpu:"Detecting...",ram:"Detecting...",npu:"Detecting...",gpu:"Detecting...",disk:"Detecting...",os:"Detecting..."},ye=[{label:"Models",size:"--",bytes:0,maxBytes:1},{label:"Data",size:"--",bytes:0,maxBytes:1},{label:"App Catalog",size:"--",bytes:0,maxBytes:1}];async function G(r,l){try{const t=await fetch(r,{headers:{Accept:"application/json"}});return!t.ok||!(t.headers.get("content-type")??"").includes("application/json")?l:await t.json()}catch{return l}}function be({value:r,max:l}){const t=l>0?Math.min(100,r/l*100):0;return e.jsx("div",{className:"h-2 w-full rounded-full bg-white/5",role:"progressbar","aria-valuenow":t,"aria-valuemin":0,"aria-valuemax":100,children:e.jsx("div",{className:"h-full rounded-full bg-sky-500 transition-all",style:{width:`${t}%`}})})}function ge(){const[r,l]=s.useState(je),[t,x]=s.useState(!1),[a,d]=s.useState(!1),i=s.useCallback(async()=>{var m,h,p,o,j,v,k,S,g,R,_,I,y;x(!0);const c=await G("/api/system",null);if(c!=null&&c.hardware||c!=null&&c.resources){const u=c.hardware??{},U=c.resources??{},C=U.ram_total_mb??u.ram_mb??0,$=U.disk_total_gb??((m=u.disk)==null?void 0:m.total_gb)??0,A=((h=u.cpu)==null?void 0:h.model)??((p=u.cpu)==null?void 0:p.soc)??"Unknown",B=(o=u.cpu)!=null&&o.cores?` × ${u.cpu.cores}`:"",L=(j=u.cpu)!=null&&j.arch?` (${u.cpu.arch})`:"",f=((v=u.gpu)==null?void 0:v.model)||((k=u.gpu)==null?void 0:k.type)||"None",z=(S=u.gpu)!=null&&S.vram_mb&&u.gpu.vram_mb>0?` (${(u.gpu.vram_mb/1024).toFixed(1)} GB)`:"",M=(g=u.npu)!=null&&g.type&&u.npu.type!=="none"?u.npu.type:"None",q=(R=u.npu)!=null&&R.tops&&u.npu.tops>0?` · ${u.npu.tops} TOPS`:"",X=(_=u.disk)!=null&&_.type?` ${u.disk.type}`:"",J=[(I=u.os)==null?void 0:I.distro,(y=u.os)==null?void 0:y.version].filter(Boolean),Q=J.length>0?J.join(" "):"—";l({cpu:`${A}${B}${L}`,ram:C>=1024?`${(C/1024).toFixed(1)} GB`:C>0?`${C} MB`:"—",npu:`${M}${q}`,gpu:`${f}${z}`,disk:$>0?`${$} GB${X}`:"—",os:Q})}else l({cpu:"Unavailable",ram:"Unavailable",npu:"Unavailable",gpu:"Unavailable",disk:"Unavailable",os:"Unavailable"});x(!1)},[]);s.useEffect(()=>{i()},[i]);const n=[["CPU",r.cpu],["RAM",r.ram],["NPU",r.npu],["GPU",r.gpu],["Disk",r.disk],["OS",r.os]];return e.jsxs("section",{"aria-label":"System information",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"System Information"}),e.jsx("div",{className:"rounded-2xl bg-white/[0.04] border border-white/[0.06] overflow-x-auto backdrop-blur-sm",children:e.jsx("table",{className:"w-full text-sm min-w-[360px]",children:e.jsx("tbody",{children:n.map(([c,m])=>e.jsxs("tr",{className:"border-b border-white/5 last:border-0",children:[e.jsx("td",{className:"px-5 py-3 text-shell-text-secondary font-medium w-32",children:c}),e.jsx("td",{className:"px-5 py-3",children:m})]},c))})})}),e.jsxs("div",{className:"mt-3 flex items-center gap-2 flex-wrap",children:[e.jsxs(b,{variant:"outline",size:"sm",onClick:i,disabled:t,children:[e.jsx(P,{size:14,className:t?"animate-spin":""}),"Re-detect Hardware"]}),e.jsxs(b,{variant:"outline",size:"sm",onClick:async()=>{d(!0);try{await fetch("/api/system/restart/prepare",{method:"POST"})}catch{}},"aria-label":"Restart taOS server",children:[e.jsx(P,{size:14}),"Restart Server"]})]}),e.jsx("p",{className:"mt-2 text-xs text-shell-text-tertiary",children:"Restart the server to apply settings changes that require a reload."}),a&&e.jsx(W,{onClose:()=>d(!1)})]})}function Ne(){const[r,l]=s.useState(ye);return s.useEffect(()=>{G("/api/settings/storage",null).then(t=>{t&&Array.isArray(t)?l(t):l([{label:"Models",size:"4.2 GB",bytes:4200,maxBytes:32e3},{label:"Data",size:"1.8 GB",bytes:1800,maxBytes:32e3},{label:"App Catalog",size:"320 MB",bytes:320,maxBytes:32e3}])})},[]),e.jsxs("section",{"aria-label":"Storage usage",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Storage Usage"}),e.jsx("div",{className:"space-y-3",children:r.map(t=>e.jsxs(w,{className:"p-4",children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("span",{className:"text-sm font-medium",children:t.label}),e.jsx("span",{className:"text-sm text-shell-text-secondary tabular-nums",children:t.size})]}),e.jsx(be,{value:t.bytes,max:t.maxBytes})]},t.label))})]})}const ve=[{key:"capture_conversations",label:"Conversations",desc:"Messages you send to agents in the Message Hub"},{key:"capture_notes",label:"Notes",desc:"Notes from the Text Editor app"},{key:"capture_files",label:"File activity",desc:"Files you upload or open"},{key:"capture_searches",label:"Search queries",desc:"What you search for in global search"}];function we(){const[r,l]=s.useState(null),[t,x]=s.useState(null),[a,d]=s.useState(null);s.useEffect(()=>{fetch("/api/user-memory/settings").then(n=>n.ok?n.json():null).then(n=>{l(n||{})}).catch(()=>{l({}),d("Could not load memory settings.")}),fetch("/api/user-memory/stats").then(n=>n.ok?n.json():null).then(n=>{n&&x(n)}).catch(()=>{})},[]);const i=(n,c)=>{const m={...r||{},[n]:c};l(m),fetch("/api/user-memory/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({[n]:c})}).then(h=>{h.ok?d(null):d(`Failed to save setting (${h.status})`)}).catch(()=>d("Could not reach backend."))};return r?e.jsxs("section",{"aria-label":"Memory capture settings",children:[e.jsx("h2",{className:"text-lg font-semibold mb-2",children:"Memory Capture"}),e.jsx("p",{className:"text-sm text-shell-text-tertiary mb-5",children:"Choose what activity gets saved to your personal memory index. All data stays on this device."}),a&&e.jsxs("p",{className:"mb-3 text-xs text-amber-400 flex items-center gap-1.5",children:[e.jsx(F,{size:12})," ",a]}),e.jsx("div",{className:"space-y-2",children:ve.map(n=>{const c=!!r[n.key],m=`capture-${String(n.key)}`;return e.jsxs(w,{className:"p-4 flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{htmlFor:m,className:"text-sm font-medium text-shell-text",children:n.label}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mt-0.5",children:n.desc})]}),e.jsx(T,{id:m,checked:c,onCheckedChange:h=>i(n.key,h),"aria-label":`Capture ${n.label}`})]},String(n.key))})}),t&&e.jsxs(w,{className:"mt-6 p-4",children:[e.jsx("h3",{className:"text-sm font-medium mb-3",children:"Stored chunks"}),e.jsxs("div",{className:"text-xs text-shell-text-secondary mb-2 tabular-nums",children:["Total: ",t.total]}),Object.keys(t.collections||{}).length>0?e.jsx("ul",{className:"space-y-1 text-xs text-shell-text-tertiary",children:Object.entries(t.collections).map(([n,c])=>e.jsxs("li",{className:"flex justify-between tabular-nums",children:[e.jsx("span",{children:n}),e.jsx("span",{children:c})]},n))}):e.jsx("p",{className:"text-xs text-shell-text-tertiary",children:"No memories captured yet."})]})]}):e.jsxs("section",{"aria-label":"Memory capture settings",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Memory Capture"}),e.jsx("p",{className:"text-sm text-shell-text-tertiary",children:"Loading..."})]})}function ke(){const[r,l]=s.useState(null),[t,x]=s.useState(!1),a=async()=>{x(!0),l(null);try{const d=await fetch("/api/backup",{method:"POST"});d.ok?l("Backup created successfully."):l(`Backup failed (${d.status}). API may not be available yet.`)}catch{l("Could not reach backup endpoint. API not available yet.")}x(!1)};return e.jsxs("section",{"aria-label":"Backup and restore",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Backup & Restore"}),e.jsxs(w,{className:"p-4 space-y-4",children:[e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium mb-2",children:"Create Backup"}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mb-3",children:"Export all agents, memory, and configuration as a backup archive."}),e.jsxs(b,{size:"sm",onClick:a,disabled:t,children:[e.jsx(K,{size:14,className:t?"animate-bounce":""}),t?"Creating...":"Create Backup"]}),r&&e.jsx("p",{className:`mt-2 text-xs ${r.includes("success")?"text-emerald-400":"text-amber-400"}`,children:r})]}),e.jsx("hr",{className:"border-white/5"}),e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium mb-2",children:"Restore from Backup"}),e.jsx("p",{className:"text-xs text-shell-text-tertiary mb-3",children:"Upload a previously created backup archive to restore."}),e.jsxs("label",{className:"flex flex-col items-center gap-2 p-6 rounded-lg border-2 border-dashed border-white/10 hover:border-white/20 transition-colors cursor-pointer",children:[e.jsx(me,{size:24,className:"text-shell-text-tertiary"}),e.jsx("span",{className:"text-xs text-shell-text-tertiary",children:"Click to select a backup file"}),e.jsx("input",{type:"file",accept:".tar.gz,.zip,.bak",className:"hidden","aria-label":"Upload backup file"})]})]})]})]})}function W({onClose:r}){const[l,t]=s.useState(null),[x,a]=s.useState(!1);s.useEffect(()=>{let n=!1,c=null,m=null,h=!1;const p=()=>{h||n||(h=!0,c&&clearInterval(c),m=setInterval(async()=>{if(!n)try{(await fetch("/api/settings/update-status")).ok&&(a(!0),m&&clearInterval(m),setTimeout(()=>{n||window.location.reload()},500))}catch{}},2e3))};return c=setInterval(async()=>{if(!n)try{const o=await fetch("/api/system/restart/status");if(o.ok){const j=await o.json();t(j),j.phase==="restarting"&&p()}}catch{p()}},1e3),()=>{n=!0,c&&clearInterval(c),m&&clearInterval(m)}},[]);const d=l?Object.entries(l.agents):[];function i(n){return n==="ready"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300",children:"ready"}):n==="timeout"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-300",children:"timeout"}):n==="error"?e.jsx("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-red-500/20 text-red-300",children:"error"}):e.jsxs("span",{className:"text-[10px] px-1.5 py-0.5 rounded bg-sky-500/20 text-sky-300 flex items-center gap-1",children:[e.jsx(P,{size:10,className:"animate-spin"}),n]})}return e.jsx("div",{role:"dialog","aria-modal":"true","aria-label":"Restart progress",className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60",children:e.jsxs("div",{className:"bg-shell-surface border border-white/10 rounded-xl p-6 w-full max-w-md shadow-xl space-y-4",children:[e.jsx("h3",{className:"text-base font-semibold",children:x?"Restarted — reloading…":d.length>0?"Preparing agents for restart":"Restarting server…"}),d.length>0&&e.jsx("ul",{className:"space-y-1","aria-label":"Agent preparation status",children:d.map(([n,c])=>e.jsxs("li",{className:"flex items-center justify-between text-sm",children:[e.jsx("span",{className:"text-shell-text-secondary",children:n}),i(c.status)]},n))}),(l==null?void 0:l.phase)==="restarting"&&!x&&e.jsx("p",{className:"text-xs text-shell-text-tertiary",children:"Waiting for server to come back…"}),!l&&e.jsxs("p",{className:"text-xs text-shell-text-tertiary flex items-center gap-1",children:[e.jsx(P,{size:12,className:"animate-spin"})," Connecting…"]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(b,{variant:"outline",size:"sm",onClick:r,"aria-label":"Cancel restart progress dialog",children:"Cancel"})})]})})}function Se(){const[r,l]=s.useState(!1),[t,x]=s.useState(!1),[a,d]=s.useState(null),[i,n]=s.useState(null),[c,m]=s.useState({check_enabled:!0,auto_apply:!1,auto_restart:!1}),[h,p]=s.useState(null),[o,j]=s.useState(!1),[v,k]=s.useState(!1);s.useEffect(()=>{(async()=>{try{const y=await fetch("/api/preferences/auto-update");if(y.ok){const u=await y.json();u&&typeof u=="object"&&m({check_enabled:u.check_enabled??!0,auto_apply:u.auto_apply??!1,auto_restart:u.auto_restart??!1})}}catch{}try{const y=await fetch("/api/settings/update-check");y.ok&&d(await y.json())}catch{}try{const y=await fetch("/api/settings/update-status");y.ok&&p(await y.json())}catch{}})()},[]);const S=s.useCallback(async y=>{m(y);try{await fetch("/api/preferences/auto-update",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(y)})}catch{}},[]),g=async()=>{l(!0),n(null);try{const y=await fetch("/api/settings/update-check");if(y.ok){const u=await y.json();d(u),n(u.has_updates?"A new version is available.":"You are up to date.")}else n("Update check not available.")}catch{n("Could not reach update server.")}l(!1)},R=async()=>{x(!0),n(null);try{const y=await fetch("/api/settings/update",{method:"POST"});if(y.ok){const u=await y.json().catch(()=>({}));if(u.status==="restarting")j(!0);else{n(u.message??"Update applied. Restart the server to finish."),k(!0);const U=await fetch("/api/settings/update-check");U.ok&&d(await U.json());const C=await fetch("/api/settings/update-status");C.ok&&p(await C.json())}}else{const u=await y.json().catch(()=>({}));n(u.error??"Update failed.")}}catch{n("Could not apply update.")}x(!1)},_=async()=>{j(!0);try{await fetch("/api/system/restart/prepare",{method:"POST"})}catch{}},I=!!(h!=null&&h.pending_restart_sha);return e.jsxs("section",{"aria-label":"System updates",children:[e.jsx("h2",{className:"text-lg font-semibold mb-5",children:"Updates"}),I&&e.jsxs("div",{className:"mb-4 flex items-center justify-between gap-3 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm text-amber-200",children:[e.jsx(F,{size:16,className:"shrink-0"}),e.jsxs("span",{children:["Update pulled — restart to finish applying (",h.pending_restart_sha.slice(0,7),")"]})]}),e.jsx(b,{size:"sm",onClick:_,"aria-label":"Restart server to apply update",children:"Restart now"})]}),e.jsxs(w,{className:"p-4 space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 rounded-lg bg-white/5 text-sky-400",children:e.jsx(xe,{size:20})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"text-sm font-medium",children:"taOS"}),a!=null&&a.has_updates&&a.new_commit?e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("p",{className:"text-xs text-shell-text-tertiary tabular-nums",children:[e.jsx("span",{className:"text-white/40",children:"installed "}),a.current_commit]}),e.jsxs("p",{className:"text-xs text-amber-300/90 tabular-nums",children:[e.jsx("span",{className:"text-amber-300/50",children:"available "}),a.new_commit]})]}):e.jsx("p",{className:"text-xs text-shell-text-tertiary tabular-nums",children:(a==null?void 0:a.current_commit)??"v0.1.0-dev"})]}),(a==null?void 0:a.has_updates)&&e.jsx("span",{className:"text-[10px] px-2 py-1 rounded-full font-semibold bg-amber-500/20 text-amber-300",children:"Update available"})]}),e.jsxs("div",{className:"flex gap-2 flex-wrap",children:[e.jsxs(b,{variant:"outline",size:"sm",onClick:g,disabled:r,children:[e.jsx(P,{size:14,className:r?"animate-spin":""}),r?"Checking...":"Check Now"]}),v?e.jsx(b,{size:"sm",onClick:_,"aria-label":"Restart server to apply update",children:"Restart Now"}):a!=null&&a.has_updates?e.jsx(b,{size:"sm",onClick:R,disabled:t,children:t?"Installing...":"Install Update"}):null]}),i&&e.jsxs("div",{className:"flex items-start gap-2 text-xs",children:[i.includes("up to date")||i.includes("applied")?e.jsx(D,{size:14,className:"text-emerald-400 shrink-0 mt-0.5"}):e.jsx(F,{size:14,className:"text-amber-400 shrink-0 mt-0.5"}),e.jsx("span",{className:"text-shell-text-secondary",children:i})]}),e.jsxs("div",{className:"border-t border-white/5 pt-4 space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Check for updates automatically"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:"Polls GitHub hourly and notifies when a new version is available."})]}),e.jsx(T,{checked:c.check_enabled??!0,onCheckedChange:y=>S({...c,check_enabled:y})})]}),e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Install updates automatically"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:"Pulls + installs new versions as soon as they're detected. You'll still need to restart the server manually."})]}),e.jsx(T,{checked:c.auto_apply??!1,onCheckedChange:y=>S({...c,auto_apply:y}),disabled:!(c.check_enabled??!0)})]}),e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx(N,{className:"text-sm",children:"Automatically restart after update"}),e.jsx("p",{className:"text-[11px] text-shell-text-tertiary mt-0.5",children:c.auto_restart?"Server will restart automatically once an update is pulled.":"We'll remind you every 6 hours when a restart is pending."})]}),e.jsx(T,{checked:c.auto_restart??!1,onCheckedChange:y=>S({...c,auto_restart:y}),"aria-label":"Automatically restart after update"})]})]})]}),o&&e.jsx(W,{onClose:()=>j(!1)})]})}function Ce(){const[r,l]=s.useState(`# taOS Configuration | |||
There was a problem hiding this comment.
Guard Object.entries against missing agents in restart status.
const d = l ? Object.entries(l.agents) : [] can throw when l exists but l.agents is undefined (valid during partial status responses), crashing the restart dialog.
💡 Proposed fix
-const d=l?Object.entries(l.agents):[];
+const d=l&&l.agents&&typeof l.agents==="object"?Object.entries(l.agents):[];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@static/desktop/assets/SettingsApp-Bjcx0zeF.js` at line 1, The restart dialog
crashes because W's local state variable l can exist while l.agents is
undefined; change the computation of d (currently `const d = l ?
Object.entries(l.agents) : []`) to safely handle missing agents by using a guard
like checking l?.agents or falling back to an empty object before calling
Object.entries (i.e., compute d from Object.entries(l?.agents || {})), update
references in function W where d is used so the dialog no longer throws when
agents is absent.
Summary
Phase 2b-1 of taOS chat: Slack-style threads, message attachments, a reusable shared file-picker shell primitive, and a canonical chat guide with
/helpcommand.Threads
@allescalates.Attachments
/help + guide
New backend endpoints
Test plan
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests