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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions apps/codex-claw/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Route as ApiPathsRouteImport } from './routes/api/paths'
import { Route as ApiMcpHealthRouteImport } from './routes/api/mcp-health'
import { Route as ApiHistoryRouteImport } from './routes/api/history'
import { Route as ApiGitReviewRouteImport } from './routes/api/git-review'
import { Route as ApiArtifactsRouteImport } from './routes/api/artifacts'

const NewRoute = NewRouteImport.update({
id: '/new',
Expand Down Expand Up @@ -100,11 +101,17 @@ const ApiGitReviewRoute = ApiGitReviewRouteImport.update({
path: '/api/git-review',
getParentRoute: () => rootRouteImport,
} as any)
const ApiArtifactsRoute = ApiArtifactsRouteImport.update({
id: '/api/artifacts',
path: '/api/artifacts',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/artifacts': typeof ApiArtifactsRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/mcp-health': typeof ApiMcpHealthRoute
Expand All @@ -122,6 +129,7 @@ export interface FileRoutesByTo {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/artifacts': typeof ApiArtifactsRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/mcp-health': typeof ApiMcpHealthRoute
Expand All @@ -140,6 +148,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/artifacts': typeof ApiArtifactsRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/mcp-health': typeof ApiMcpHealthRoute
Expand All @@ -159,6 +168,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/artifacts'
| '/api/git-review'
| '/api/history'
| '/api/mcp-health'
Expand All @@ -176,6 +186,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/artifacts'
| '/api/git-review'
| '/api/history'
| '/api/mcp-health'
Expand All @@ -193,6 +204,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/artifacts'
| '/api/git-review'
| '/api/history'
| '/api/mcp-health'
Expand All @@ -211,6 +223,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ConnectRoute: typeof ConnectRoute
NewRoute: typeof NewRoute
ApiArtifactsRoute: typeof ApiArtifactsRoute
ApiGitReviewRoute: typeof ApiGitReviewRoute
ApiHistoryRoute: typeof ApiHistoryRoute
ApiMcpHealthRoute: typeof ApiMcpHealthRoute
Expand Down Expand Up @@ -332,13 +345,21 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiGitReviewRouteImport
parentRoute: typeof rootRouteImport
}
'/api/artifacts': {
id: '/api/artifacts'
path: '/api/artifacts'
fullPath: '/api/artifacts'
preLoaderRoute: typeof ApiArtifactsRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ConnectRoute: ConnectRoute,
NewRoute: NewRoute,
ApiArtifactsRoute: ApiArtifactsRoute,
ApiGitReviewRoute: ApiGitReviewRoute,
ApiHistoryRoute: ApiHistoryRoute,
ApiMcpHealthRoute: ApiMcpHealthRoute,
Expand Down
71 changes: 71 additions & 0 deletions apps/codex-claw/src/routes/api/artifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import path from 'node:path'
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import {
getCodexArtifactFile,
listCodexArtifacts,
} from '../../server/codex-cli'

function contentTypeForPath(filePath: string) {
const ext = path.extname(filePath).toLowerCase()
if (ext === '.json') return 'application/json'
if (ext === '.md') return 'text/markdown'
if (ext === '.patch' || ext === '.diff') return 'text/x-diff'
if (ext === '.png') return 'image/png'
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg'
if (ext === '.gif') return 'image/gif'
if (ext === '.webp') return 'image/webp'
return 'text/plain'
}

function downloadName(filePath: string) {
return path.basename(filePath).replace(/[^a-zA-Z0-9._-]/g, '-')
}

export const Route = createFileRoute('/api/artifacts')({
server: {
handlers: {
GET: ({ request }) => {
try {
const url = new URL(request.url)
const sessionKey = url.searchParams.get('sessionKey') ?? undefined
const friendlyId = url.searchParams.get('friendlyId') ?? undefined
const id = url.searchParams.get('id') ?? ''

if (url.searchParams.get('manifest') === '1') {
const payload = listCodexArtifacts({ sessionKey, friendlyId })
return new Response(JSON.stringify(payload.manifest, null, 2), {
headers: {
'content-type': 'application/json; charset=utf-8',
'content-disposition':
'attachment; filename="codexclaw-artifacts-manifest.json"',
},
})
}

if (id && url.searchParams.get('download') === '1') {
const payload = getCodexArtifactFile({ id, sessionKey, friendlyId })
return new Response(payload.content, {
headers: {
'content-type': contentTypeForPath(payload.artifact.path),
'content-disposition':
'attachment; filename="' +
downloadName(payload.artifact.path) +
'"',
},
})
}

return json(listCodexArtifacts({ sessionKey, friendlyId }))
} catch (err) {
return json(
{
error: err instanceof Error ? err.message : String(err),
},
{ status: 500 },
)
}
},
},
},
})
16 changes: 16 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-queries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getMessageTimestamp, normalizeSessions, readError } from './utils'
import type { QueryClient } from '@tanstack/react-query'
import type {
ArtifactListResponse,
GatewayMessage,
GitReviewPayload,
HistoryResponse,
Expand Down Expand Up @@ -33,6 +34,9 @@ export const chatQueryKeys = {
},
workspaces: ['chat', 'workspaces'] as const,
tasks: ['chat', 'tasks'] as const,
artifacts: function artifacts(sessionKey: string, friendlyId: string) {
return ['chat', 'artifacts', sessionKey, friendlyId] as const
},
history: function history(friendlyId: string, sessionKey: string) {
return ['chat', 'history', friendlyId, sessionKey] as const
},
Expand Down Expand Up @@ -110,6 +114,18 @@ export async function fetchHistory(payload: {
return (await res.json()) as HistoryResponse
}

export async function fetchArtifacts(payload: {
sessionKey: string
friendlyId: string
}): Promise<ArtifactListResponse> {
const query = new URLSearchParams()
if (payload.sessionKey) query.set('sessionKey', payload.sessionKey)
if (payload.friendlyId) query.set('friendlyId', payload.friendlyId)
const res = await fetch('/api/artifacts?' + query.toString())
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as ArtifactListResponse
}

export async function fetchGatewayStatus(): Promise<GatewayStatusResponse> {
const controller = new AbortController()
const timeout = window.setTimeout(() => controller.abort(), 2500)
Expand Down
10 changes: 10 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ChatComposer } from './components/chat-composer'
import { GitReviewPanel } from './components/git-review-panel'
import { McpHealthPanel } from './components/mcp-health-panel'
import { TaskQueuePanel } from './components/task-queue-panel'
import { ArtifactsPanel } from './components/artifacts-panel'
import { GatewayStatusMessage } from './components/gateway-status-message'
import {
hasPendingGeneration,
Expand Down Expand Up @@ -75,6 +76,7 @@ export function ChatScreen({
const [gitReviewOpen, setGitReviewOpen] = useState(false)
const [mcpHealthOpen, setMcpHealthOpen] = useState(false)
const [taskQueueOpen, setTaskQueueOpen] = useState(false)
const [artifactsOpen, setArtifactsOpen] = useState(false)
const { headerRef, composerRef, mainRef, pinGroupMinHeight, headerHeight } =
useChatMeasurements()
const [waitingForResponse, setWaitingForResponse] = useState(
Expand Down Expand Up @@ -637,11 +639,19 @@ export function ChatScreen({
onToggleMcpHealth={() => setMcpHealthOpen((current) => !current)}
taskQueueOpen={taskQueueOpen}
onToggleTaskQueue={() => setTaskQueueOpen((current) => !current)}
artifactsOpen={artifactsOpen}
onToggleArtifacts={() => setArtifactsOpen((current) => !current)}
/>
<TaskQueuePanel
open={taskQueueOpen}
onClose={() => setTaskQueueOpen(false)}
/>
<ArtifactsPanel
open={artifactsOpen}
sessionKey={activeSessionKey}
friendlyId={activeFriendlyId}
onClose={() => setArtifactsOpen(false)}
/>
<McpHealthPanel
open={mcpHealthOpen}
onClose={() => setMcpHealthOpen(false)}
Expand Down
Loading
Loading