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 @@ -21,6 +21,7 @@ import { Route as ApiRepoContextRouteImport } from './routes/api/repo-context'
import { Route as ApiPingRouteImport } from './routes/api/ping'
import { Route as ApiPathsRouteImport } from './routes/api/paths'
import { Route as ApiHistoryRouteImport } from './routes/api/history'
import { Route as ApiGitReviewRouteImport } from './routes/api/git-review'

const NewRoute = NewRouteImport.update({
id: '/new',
Expand Down Expand Up @@ -82,11 +83,17 @@ const ApiHistoryRoute = ApiHistoryRouteImport.update({
path: '/api/history',
getParentRoute: () => rootRouteImport,
} as any)
const ApiGitReviewRoute = ApiGitReviewRouteImport.update({
id: '/api/git-review',
path: '/api/git-review',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
Expand All @@ -101,6 +108,7 @@ export interface FileRoutesByTo {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
Expand All @@ -116,6 +124,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/connect': typeof ConnectRoute
'/new': typeof NewRoute
'/api/git-review': typeof ApiGitReviewRoute
'/api/history': typeof ApiHistoryRoute
'/api/paths': typeof ApiPathsRoute
'/api/ping': typeof ApiPingRoute
Expand All @@ -132,6 +141,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/git-review'
| '/api/history'
| '/api/paths'
| '/api/ping'
Expand All @@ -146,6 +156,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/git-review'
| '/api/history'
| '/api/paths'
| '/api/ping'
Expand All @@ -160,6 +171,7 @@ export interface FileRouteTypes {
| '/'
| '/connect'
| '/new'
| '/api/git-review'
| '/api/history'
| '/api/paths'
| '/api/ping'
Expand All @@ -175,6 +187,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ConnectRoute: typeof ConnectRoute
NewRoute: typeof NewRoute
ApiGitReviewRoute: typeof ApiGitReviewRoute
ApiHistoryRoute: typeof ApiHistoryRoute
ApiPathsRoute: typeof ApiPathsRoute
ApiPingRoute: typeof ApiPingRoute
Expand Down Expand Up @@ -272,13 +285,21 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiHistoryRouteImport
parentRoute: typeof rootRouteImport
}
'/api/git-review': {
id: '/api/git-review'
path: '/api/git-review'
fullPath: '/api/git-review'
preLoaderRoute: typeof ApiGitReviewRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ConnectRoute: ConnectRoute,
NewRoute: NewRoute,
ApiGitReviewRoute: ApiGitReviewRoute,
ApiHistoryRoute: ApiHistoryRoute,
ApiPathsRoute: ApiPathsRoute,
ApiPingRoute: ApiPingRoute,
Expand Down
58 changes: 58 additions & 0 deletions apps/codex-claw/src/routes/api/git-review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import {
getGitReviewPayload,
stageGitReviewFiles,
} from '../../server/git-review'

export const Route = createFileRoute('/api/git-review')({
server: {
handlers: {
GET: () => {
try {
return json(getGitReviewPayload())
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 500 },
)
}
},
POST: async ({ request }) => {
try {
const body = (await request.json().catch(() => ({}))) as Record<
string,
unknown
>
const action = typeof body.action === 'string' ? body.action : ''
const paths = Array.isArray(body.paths)
? body.paths.filter(
(item): item is string => typeof item === 'string',
)
: []
if (action === 'stage') {
return json(stageGitReviewFiles(paths))
}
if (action === 'draft') {
return json(getGitReviewPayload())
}
return json(
{ ok: false, error: 'unsupported action' },
{ status: 400 },
)
} catch (err) {
return json(
{
ok: false,
error: err instanceof Error ? err.message : String(err),
},
{ status: 500 },
)
}
},
},
},
})
19 changes: 19 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getMessageTimestamp, normalizeSessions, readError } from './utils'
import type { QueryClient } from '@tanstack/react-query'
import type {
GatewayMessage,
GitReviewPayload,
HistoryResponse,
RepoContextPayload,
RepoContextSelection,
Expand Down Expand Up @@ -127,6 +128,24 @@ export async function fetchRepoContext(
return (await res.json()) as RepoContextPayload
}

export async function fetchGitReview(): Promise<GitReviewPayload> {
const res = await fetch('/api/git-review')
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as GitReviewPayload
}

export async function stageGitReviewFiles(
paths: Array<string>,
): Promise<GitReviewPayload> {
const res = await fetch('/api/git-review', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ action: 'stage', paths }),
})
if (!res.ok) throw new Error(await readError(res))
return (await res.json()) as GitReviewPayload
}

export function updateHistoryMessages(
queryClient: QueryClient,
friendlyId: string,
Expand Down
8 changes: 8 additions & 0 deletions apps/codex-claw/src/screens/chat/chat-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ChatSidebar } from './components/chat-sidebar'
import { ChatHeader } from './components/chat-header'
import { ChatMessageList } from './components/chat-message-list'
import { ChatComposer } from './components/chat-composer'
import { GitReviewPanel } from './components/git-review-panel'
import { GatewayStatusMessage } from './components/gateway-status-message'
import {
hasPendingGeneration,
Expand Down Expand Up @@ -69,6 +70,7 @@ export function ChatScreen({
const [sending, setSending] = useState(false)
const [creatingSession, setCreatingSession] = useState(false)
const [isRedirecting, setIsRedirecting] = useState(false)
const [gitReviewOpen, setGitReviewOpen] = useState(false)
const { headerRef, composerRef, mainRef, pinGroupMinHeight, headerHeight } =
useChatMeasurements()
const [waitingForResponse, setWaitingForResponse] = useState(
Expand Down Expand Up @@ -615,6 +617,12 @@ export function ChatScreen({
showExport={!isNewChat}
usedTokens={activeSession?.totalTokens}
maxTokens={activeSession?.contextTokens}
gitReviewOpen={gitReviewOpen}
onToggleGitReview={() => setGitReviewOpen((current) => !current)}
/>
<GitReviewPanel
open={gitReviewOpen}
onClose={() => setGitReviewOpen(false)}
/>

{hideUi ? null : (
Expand Down
17 changes: 16 additions & 1 deletion apps/codex-claw/src/screens/chat/components/chat-header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { memo } from 'react'
import { HugeiconsIcon } from '@hugeicons/react'
import { Menu01Icon } from '@hugeicons/core-free-icons'
import { GitBranchIcon, Menu01Icon } from '@hugeicons/core-free-icons'
import { ContextMeter } from './context-meter'
import { Button } from '@/components/ui/button'
import { ExportMenu } from '@/components/export-menu'
Expand All @@ -17,6 +17,8 @@ type ChatHeaderProps = {
onExport: (format: ExportFormat) => void
exportDisabled?: boolean
showExport?: boolean
gitReviewOpen?: boolean
onToggleGitReview?: () => void
}

function ChatHeaderComponent({
Expand All @@ -29,6 +31,8 @@ function ChatHeaderComponent({
onExport,
exportDisabled = false,
showExport = true,
gitReviewOpen = false,
onToggleGitReview,
}: ChatHeaderProps) {
return (
<div
Expand All @@ -50,6 +54,17 @@ function ChatHeaderComponent({
{activeTitle}
</div>
<div className="flex items-center gap-2 shrink-0">
{onToggleGitReview ? (
<Button
size="icon-sm"
variant="ghost"
onClick={onToggleGitReview}
className={gitReviewOpen ? 'bg-primary-100' : undefined}
aria-label="Review local changes"
>
<HugeiconsIcon icon={GitBranchIcon} size={18} strokeWidth={1.5} />
</Button>
) : null}
{showExport ? (
<ExportMenu onExport={onExport} disabled={exportDisabled} />
) : null}
Expand Down
Loading
Loading