Update expo application
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { useState } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Button } from '~/components/ui/button';
|
||||
|
||||
export const DiffPreview = ({
|
||||
content,
|
||||
initialLines = 120,
|
||||
}: {
|
||||
content: string;
|
||||
initialLines?: number;
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const lines = content.split('\n');
|
||||
const visibleLines = expanded ? lines : lines.slice(0, initialLines);
|
||||
const hiddenCount = Math.max(lines.length - visibleLines.length, 0);
|
||||
|
||||
return (
|
||||
<View className='gap-3 rounded-lg bg-zinc-950 p-3'>
|
||||
<View>
|
||||
{visibleLines.map((line, index) => {
|
||||
const color = line.startsWith('+')
|
||||
? 'text-emerald-300'
|
||||
: line.startsWith('-')
|
||||
? 'text-red-300'
|
||||
: line.startsWith('@@')
|
||||
? 'text-sky-300'
|
||||
: 'text-zinc-100';
|
||||
return (
|
||||
<Text
|
||||
key={`${index}-${line.slice(0, 12)}`}
|
||||
className={`font-mono text-xs leading-5 ${color}`}
|
||||
>
|
||||
{line || ' '}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
{hiddenCount > 0 ? (
|
||||
<Button variant='outline' onPress={() => setExpanded(true)}>
|
||||
Show {hiddenCount} more lines
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Text, View } from 'react-native';
|
||||
import * as Clipboard from 'expo-clipboard';
|
||||
|
||||
import { Button } from '~/components/ui/button';
|
||||
import { Card } from '~/components/ui/card';
|
||||
import { DiffPreview } from './diff-preview';
|
||||
|
||||
type Artifact = {
|
||||
_id: string;
|
||||
content: string;
|
||||
contentType: string;
|
||||
kind: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export const WorkspaceArtifacts = ({
|
||||
artifacts,
|
||||
mode,
|
||||
}: {
|
||||
artifacts: Artifact[];
|
||||
mode: 'diffs' | 'artifacts';
|
||||
}) => {
|
||||
const diffArtifacts = artifacts.filter(
|
||||
(artifact) =>
|
||||
artifact.contentType === 'text/x-diff' || artifact.kind === 'diff',
|
||||
);
|
||||
const visible =
|
||||
mode === 'diffs'
|
||||
? diffArtifacts
|
||||
: artifacts.filter((artifact) => !diffArtifacts.includes(artifact));
|
||||
|
||||
return (
|
||||
<Card className='gap-3'>
|
||||
<Text className='text-foreground font-semibold'>
|
||||
{mode === 'diffs' ? 'Diffs' : 'Artifacts'}
|
||||
</Text>
|
||||
{visible.length ? (
|
||||
visible.map((artifact) => (
|
||||
<View key={artifact._id} className='gap-2'>
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
{artifact.title}
|
||||
</Text>
|
||||
{mode === 'diffs' ? (
|
||||
<DiffPreview content={artifact.content} />
|
||||
) : (
|
||||
<>
|
||||
<Text className='bg-muted text-foreground rounded-md p-3 font-mono text-xs leading-5'>
|
||||
{artifact.content.slice(0, 2_000)}
|
||||
</Text>
|
||||
<Button
|
||||
variant='outline'
|
||||
onPress={() =>
|
||||
void Clipboard.setStringAsync(artifact.content)
|
||||
}
|
||||
>
|
||||
Copy artifact
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
{mode === 'diffs'
|
||||
? 'Diff artifacts will appear here when the worker records them.'
|
||||
: 'No non-diff artifacts recorded.'}
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Card } from '~/components/ui/card';
|
||||
import { ChipRow } from '~/components/ui/chip-row';
|
||||
import { formatDateTime, titleize } from '~/utils/format';
|
||||
|
||||
type Event = {
|
||||
_id: string;
|
||||
createdAt: number;
|
||||
level: string;
|
||||
message: string;
|
||||
phase: string;
|
||||
};
|
||||
|
||||
export const WorkspaceEvents = ({ events }: { events: Event[] }) => {
|
||||
const [level, setLevel] = useState<'all' | 'info' | 'warn' | 'error'>('all');
|
||||
const filtered =
|
||||
level === 'all' ? events : events.filter((event) => event.level === level);
|
||||
|
||||
return (
|
||||
<Card className='gap-3'>
|
||||
<Text className='text-foreground font-semibold'>Events</Text>
|
||||
<ChipRow
|
||||
options={[
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Warn', value: 'warn' },
|
||||
{ label: 'Error', value: 'error' },
|
||||
]}
|
||||
value={level}
|
||||
onChange={setLevel}
|
||||
/>
|
||||
{filtered.length ? (
|
||||
filtered.map((event) => (
|
||||
<View key={event._id} className='border-border border-b pb-2'>
|
||||
<Text className='text-muted-foreground text-xs'>
|
||||
{formatDateTime(event.createdAt)} · {titleize(event.phase)} ·{' '}
|
||||
{titleize(event.level)}
|
||||
</Text>
|
||||
<Text className='text-foreground mt-1'>{event.message}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text className='text-muted-foreground text-sm'>No events.</Text>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Card } from '~/components/ui/card';
|
||||
import { titleize } from '~/utils/format';
|
||||
|
||||
export const WorkspaceMessages = ({
|
||||
messages,
|
||||
}: {
|
||||
messages: { _id: string; content: string; role: string; status: string }[];
|
||||
}) => (
|
||||
<Card className='gap-3'>
|
||||
<Text className='text-foreground font-semibold'>Messages</Text>
|
||||
{messages.length ? (
|
||||
messages.map((message) => (
|
||||
<View key={message._id} className='border-border border-b pb-2'>
|
||||
<Text className='text-muted-foreground text-xs'>
|
||||
{titleize(message.role)} · {titleize(message.status)}
|
||||
</Text>
|
||||
<Text className='text-foreground mt-1'>{message.content}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text className='text-muted-foreground text-sm'>No messages yet.</Text>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Linking, Text, View } from 'react-native';
|
||||
|
||||
import { Badge } from '~/components/ui/badge';
|
||||
import { Button } from '~/components/ui/button';
|
||||
import { Card } from '~/components/ui/card';
|
||||
import { ConfirmButton } from '~/components/ui/confirm-button';
|
||||
import { CopyRow } from '~/components/ui/copy-row';
|
||||
import { formatDateTime, titleize } from '~/utils/format';
|
||||
|
||||
export const WorkspaceSummary = ({
|
||||
cancelling,
|
||||
job,
|
||||
onCancel,
|
||||
}: {
|
||||
cancelling: boolean;
|
||||
job: {
|
||||
completedAt?: number;
|
||||
model: string;
|
||||
pullRequestUrl?: string;
|
||||
reasoningEffort: string;
|
||||
startedAt?: number;
|
||||
status: string;
|
||||
workBranch: string;
|
||||
workspaceStatus?: string;
|
||||
};
|
||||
onCancel: () => void;
|
||||
}) => (
|
||||
<Card className='gap-3'>
|
||||
<View className='flex-row flex-wrap gap-2'>
|
||||
<Badge label={titleize(job.status)} tone='primary' />
|
||||
<Badge label={titleize(job.workspaceStatus ?? 'not_started')} />
|
||||
</View>
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
Branch: {job.workBranch}
|
||||
</Text>
|
||||
<Text className='text-muted-foreground text-sm'>Model: {job.model}</Text>
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
Reasoning: {titleize(job.reasoningEffort)}
|
||||
</Text>
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
Started: {formatDateTime(job.startedAt)}
|
||||
</Text>
|
||||
{job.completedAt ? (
|
||||
<Text className='text-muted-foreground text-sm'>
|
||||
Completed: {formatDateTime(job.completedAt)}
|
||||
</Text>
|
||||
) : null}
|
||||
<CopyRow label='Draft PR' value={job.pullRequestUrl} />
|
||||
{job.pullRequestUrl ? (
|
||||
<Button onPress={() => void Linking.openURL(job.pullRequestUrl ?? '')}>
|
||||
Open draft PR
|
||||
</Button>
|
||||
) : null}
|
||||
<ConfirmButton
|
||||
confirmLabel='Cancel job'
|
||||
destructive
|
||||
disabled={
|
||||
cancelling ||
|
||||
['cancelled', 'draft_pr_opened', 'failed'].includes(job.status)
|
||||
}
|
||||
message='Cancel this workspace job? Running work will be stopped where possible.'
|
||||
title='Cancel job'
|
||||
onConfirm={onCancel}
|
||||
>
|
||||
{cancelling ? 'Cancelling...' : 'Cancel job'}
|
||||
</ConfirmButton>
|
||||
</Card>
|
||||
);
|
||||
Reference in New Issue
Block a user