@windowkit/agent-chat 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/driver.d.ts +13 -0
- package/dist/core/driver.d.ts.map +1 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/markdown.js +29 -0
- package/dist/core/markdown.js.map +1 -1
- package/dist/core/reducer.d.ts.map +1 -1
- package/dist/core/reducer.js +16 -0
- package/dist/core/reducer.js.map +1 -1
- package/dist/core/session.d.ts +3 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +17 -1
- package/dist/core/session.js.map +1 -1
- package/dist/core/types.d.ts +43 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/drivers/claude/index.d.ts.map +1 -1
- package/dist/drivers/claude/index.js +56 -8
- package/dist/drivers/claude/index.js.map +1 -1
- package/dist/drivers/claude/results.d.ts +40 -0
- package/dist/drivers/claude/results.d.ts.map +1 -0
- package/dist/drivers/claude/results.js +235 -0
- package/dist/drivers/claude/results.js.map +1 -0
- package/dist/drivers/claude/tools.d.ts +3 -1
- package/dist/drivers/claude/tools.d.ts.map +1 -1
- package/dist/drivers/claude/tools.js +7 -1
- package/dist/drivers/claude/tools.js.map +1 -1
- package/dist/view/AgentChat.d.ts.map +1 -1
- package/dist/view/AgentChat.js +14 -3
- package/dist/view/AgentChat.js.map +1 -1
- package/dist/view/ArtifactCard.d.ts +6 -0
- package/dist/view/ArtifactCard.d.ts.map +1 -0
- package/dist/view/ArtifactCard.js +6 -0
- package/dist/view/ArtifactCard.js.map +1 -0
- package/dist/view/AttachmentCard.d.ts +8 -0
- package/dist/view/AttachmentCard.d.ts.map +1 -0
- package/dist/view/AttachmentCard.js +55 -0
- package/dist/view/AttachmentCard.js.map +1 -0
- package/dist/view/Composer.d.ts.map +1 -1
- package/dist/view/Composer.js +9 -1
- package/dist/view/Composer.js.map +1 -1
- package/dist/view/DataPartView.d.ts +6 -0
- package/dist/view/DataPartView.d.ts.map +1 -0
- package/dist/view/DataPartView.js +23 -0
- package/dist/view/DataPartView.js.map +1 -0
- package/dist/view/ImageView.d.ts +18 -0
- package/dist/view/ImageView.d.ts.map +1 -0
- package/dist/view/ImageView.js +35 -0
- package/dist/view/ImageView.js.map +1 -0
- package/dist/view/LinkCard.d.ts +14 -0
- package/dist/view/LinkCard.d.ts.map +1 -0
- package/dist/view/LinkCard.js +40 -0
- package/dist/view/LinkCard.js.map +1 -0
- package/dist/view/MessageList.d.ts.map +1 -1
- package/dist/view/MessageList.js +10 -10
- package/dist/view/MessageList.js.map +1 -1
- package/dist/view/PlanChecklist.d.ts +6 -0
- package/dist/view/PlanChecklist.d.ts.map +1 -0
- package/dist/view/PlanChecklist.js +16 -0
- package/dist/view/PlanChecklist.js.map +1 -0
- package/dist/view/PullRequestCard.d.ts +6 -0
- package/dist/view/PullRequestCard.d.ts.map +1 -0
- package/dist/view/PullRequestCard.js +12 -0
- package/dist/view/PullRequestCard.js.map +1 -0
- package/dist/view/SessionCard.d.ts +6 -0
- package/dist/view/SessionCard.d.ts.map +1 -0
- package/dist/view/SessionCard.js +6 -0
- package/dist/view/SessionCard.js.map +1 -0
- package/dist/view/SuggestedReplies.d.ts +5 -0
- package/dist/view/SuggestedReplies.d.ts.map +1 -0
- package/dist/view/SuggestedReplies.js +10 -0
- package/dist/view/SuggestedReplies.js.map +1 -0
- package/dist/view/ToolCard.d.ts.map +1 -1
- package/dist/view/ToolCard.js +4 -1
- package/dist/view/ToolCard.js.map +1 -1
- package/dist/view/Transcript.d.ts.map +1 -1
- package/dist/view/Transcript.js +8 -1
- package/dist/view/Transcript.js.map +1 -1
- package/dist/view/context.d.ts +9 -0
- package/dist/view/context.d.ts.map +1 -1
- package/dist/view/context.js +9 -0
- package/dist/view/context.js.map +1 -1
- package/dist/view/look.d.ts +14 -0
- package/dist/view/look.d.ts.map +1 -1
- package/dist/view/look.js +7 -0
- package/dist/view/look.js.map +1 -1
- package/package.json +1 -1
- package/src/core/driver.ts +4 -0
- package/src/core/markdown.ts +34 -0
- package/src/core/reducer.ts +25 -0
- package/src/core/session.ts +17 -1
- package/src/core/types.ts +51 -1
- package/src/drivers/claude/index.ts +62 -7
- package/src/drivers/claude/results.ts +252 -0
- package/src/drivers/claude/tools.ts +7 -1
- package/src/view/AgentChat.tsx +20 -2
- package/src/view/ArtifactCard.tsx +18 -0
- package/src/view/AttachmentCard.tsx +103 -0
- package/src/view/Composer.tsx +10 -1
- package/src/view/DataPartView.tsx +40 -0
- package/src/view/ImageView.tsx +64 -0
- package/src/view/LinkCard.tsx +99 -0
- package/src/view/MessageList.tsx +21 -39
- package/src/view/PlanChecklist.tsx +49 -0
- package/src/view/PullRequestCard.tsx +28 -0
- package/src/view/SessionCard.tsx +22 -0
- package/src/view/SuggestedReplies.tsx +34 -0
- package/src/view/ToolCard.tsx +10 -1
- package/src/view/Transcript.tsx +9 -0
- package/src/view/context.ts +19 -0
- package/src/view/look.ts +21 -0
package/src/view/MessageList.tsx
CHANGED
|
@@ -6,8 +6,11 @@ import { memo, useState, type ReactElement } from 'react';
|
|
|
6
6
|
import { Button, Icon, useClipboard } from 'react-x11';
|
|
7
7
|
|
|
8
8
|
import { messageMarkdown, sourceLabel } from '../core/markdown.js';
|
|
9
|
-
import type { AgentMessage,
|
|
9
|
+
import type { AgentMessage, FilePart, Part } from '../core/types.js';
|
|
10
|
+
import { DataPartView } from './DataPartView.js';
|
|
11
|
+
import { ImageView } from './ImageView.js';
|
|
10
12
|
import { useLook, type ChatLook } from './look.js';
|
|
13
|
+
import { PlanChecklist } from './PlanChecklist.js';
|
|
11
14
|
import { QuestionCard } from './QuestionCard.js';
|
|
12
15
|
import { TextBlock } from './TextBlock.js';
|
|
13
16
|
import { ToolCard } from './ToolCard.js';
|
|
@@ -89,6 +92,14 @@ function UserMessage({
|
|
|
89
92
|
streaming={false}
|
|
90
93
|
message={message}
|
|
91
94
|
/>
|
|
95
|
+
) : p.type === 'file' && isImage(p) ? (
|
|
96
|
+
// a pasted screenshot is the picture, not its token
|
|
97
|
+
<ImageView
|
|
98
|
+
key={p.id}
|
|
99
|
+
source={{ data: p.data! }}
|
|
100
|
+
maxHeight={look.thumbHeight}
|
|
101
|
+
alt={p.label}
|
|
102
|
+
/>
|
|
92
103
|
) : p.type === 'file' && !textOf(message).includes(`[${p.label}]`) ? (
|
|
93
104
|
// an attachment the composer made is already in the text as its
|
|
94
105
|
// token; one sent some other way gets a line of its own
|
|
@@ -157,7 +168,7 @@ function PartView({
|
|
|
157
168
|
case 'question':
|
|
158
169
|
return <QuestionCard part={part} />;
|
|
159
170
|
case 'plan':
|
|
160
|
-
return <
|
|
171
|
+
return <PlanChecklist part={part} />;
|
|
161
172
|
case 'notice':
|
|
162
173
|
return (
|
|
163
174
|
<text
|
|
@@ -175,13 +186,15 @@ function PartView({
|
|
|
175
186
|
</text>
|
|
176
187
|
);
|
|
177
188
|
case 'file':
|
|
178
|
-
return (
|
|
189
|
+
return isImage(part) ? (
|
|
190
|
+
<ImageView source={{ data: part.data! }} alt={part.label} />
|
|
191
|
+
) : (
|
|
179
192
|
<text
|
|
180
193
|
style={{ color: look.muted, fontSize: 11 }}
|
|
181
194
|
>{`[${part.label}]`}</text>
|
|
182
195
|
);
|
|
183
196
|
case 'data':
|
|
184
|
-
return
|
|
197
|
+
return <DataPartView part={part} />;
|
|
185
198
|
}
|
|
186
199
|
}
|
|
187
200
|
|
|
@@ -232,41 +245,6 @@ function Reasoning({
|
|
|
232
245
|
);
|
|
233
246
|
}
|
|
234
247
|
|
|
235
|
-
function Plan({
|
|
236
|
-
part,
|
|
237
|
-
look,
|
|
238
|
-
}: {
|
|
239
|
-
part: PlanPart;
|
|
240
|
-
look: ChatLook;
|
|
241
|
-
}): ReactElement {
|
|
242
|
-
return (
|
|
243
|
-
<box style={{ flexDirection: 'column', gap: 2 }}>
|
|
244
|
-
{part.entries.map((e, i) => (
|
|
245
|
-
<box
|
|
246
|
-
key={i}
|
|
247
|
-
style={{ flexDirection: 'row', gap: 6, alignItems: 'center' }}
|
|
248
|
-
>
|
|
249
|
-
{e.status === 'completed' ? (
|
|
250
|
-
<Icon name="check" size={12} color={look.success} />
|
|
251
|
-
) : e.status === 'in_progress' ? (
|
|
252
|
-
<Icon name="dot" size={12} color={look.accent} />
|
|
253
|
-
) : (
|
|
254
|
-
<Icon name="dash" size={12} color={look.muted} />
|
|
255
|
-
)}
|
|
256
|
-
<text
|
|
257
|
-
style={{
|
|
258
|
-
color: e.status === 'completed' ? look.muted : look.text,
|
|
259
|
-
fontWeight: e.status === 'in_progress' ? 'bold' : 'normal',
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
{e.text}
|
|
263
|
-
</text>
|
|
264
|
-
</box>
|
|
265
|
-
))}
|
|
266
|
-
</box>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
248
|
function CopyButton({ message }: { message: AgentMessage }): ReactElement {
|
|
271
249
|
const clipboard = useClipboard();
|
|
272
250
|
const [copied, setCopied] = useState(false);
|
|
@@ -285,6 +263,10 @@ function CopyButton({ message }: { message: AgentMessage }): ReactElement {
|
|
|
285
263
|
);
|
|
286
264
|
}
|
|
287
265
|
|
|
266
|
+
function isImage(p: FilePart): boolean {
|
|
267
|
+
return p.mime.startsWith('image/') && !!p.data;
|
|
268
|
+
}
|
|
269
|
+
|
|
288
270
|
function textOf(message: AgentMessage): string {
|
|
289
271
|
return message.parts.map((p) => (p.type === 'text' ? p.text : '')).join('');
|
|
290
272
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The agent's plan as a checklist: Claude Code's task list (or a
|
|
2
|
+
// `TodoWrite`), kept current as it works — what is done in the muted
|
|
3
|
+
// colour, what is under way in bold, and how far along it is.
|
|
4
|
+
|
|
5
|
+
import type { ReactElement } from 'react';
|
|
6
|
+
import { Icon } from 'react-x11';
|
|
7
|
+
|
|
8
|
+
import type { PlanPart } from '../core/types.js';
|
|
9
|
+
import { useLook } from './look.js';
|
|
10
|
+
|
|
11
|
+
export function PlanChecklist({ part }: { part: PlanPart }): ReactElement {
|
|
12
|
+
const look = useLook();
|
|
13
|
+
const done = part.entries.filter((e) => e.status === 'completed').length;
|
|
14
|
+
return (
|
|
15
|
+
<box style={{ flexDirection: 'column', gap: 2 }} aria-label="Plan">
|
|
16
|
+
<text
|
|
17
|
+
style={{
|
|
18
|
+
color: look.muted,
|
|
19
|
+
fontSize: look.smallSize,
|
|
20
|
+
fontWeight: 'bold',
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
{`PLAN · ${done} OF ${part.entries.length} DONE`}
|
|
24
|
+
</text>
|
|
25
|
+
{part.entries.map((e, i) => (
|
|
26
|
+
<box
|
|
27
|
+
key={i}
|
|
28
|
+
style={{ flexDirection: 'row', gap: look.gap, alignItems: 'center' }}
|
|
29
|
+
>
|
|
30
|
+
{e.status === 'completed' ? (
|
|
31
|
+
<Icon name="check" size={look.captionSize} color={look.success} />
|
|
32
|
+
) : e.status === 'in_progress' ? (
|
|
33
|
+
<Icon name="dot" size={look.captionSize} color={look.accent} />
|
|
34
|
+
) : (
|
|
35
|
+
<Icon name="dash" size={look.captionSize} color={look.muted} />
|
|
36
|
+
)}
|
|
37
|
+
<text
|
|
38
|
+
style={{
|
|
39
|
+
color: e.status === 'completed' ? look.muted : look.text,
|
|
40
|
+
fontWeight: e.status === 'in_progress' ? 'bold' : 'normal',
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
{e.text}
|
|
44
|
+
</text>
|
|
45
|
+
</box>
|
|
46
|
+
))}
|
|
47
|
+
</box>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// A pull request a command opened, edited or merged: Bash's
|
|
2
|
+
// `gitOperation.pr`.
|
|
3
|
+
|
|
4
|
+
import type { ReactElement } from 'react';
|
|
5
|
+
|
|
6
|
+
import type { PullRequestData } from '../core/types.js';
|
|
7
|
+
import { LinkCard, shortUrl } from './LinkCard.js';
|
|
8
|
+
|
|
9
|
+
export function PullRequestCard({
|
|
10
|
+
value,
|
|
11
|
+
}: {
|
|
12
|
+
value: PullRequestData;
|
|
13
|
+
}): ReactElement {
|
|
14
|
+
const action = value.action.replace(/-/g, ' ');
|
|
15
|
+
return (
|
|
16
|
+
<LinkCard
|
|
17
|
+
kind="Pull request"
|
|
18
|
+
title={`#${value.number}`}
|
|
19
|
+
detail={[
|
|
20
|
+
action.charAt(0).toUpperCase() + action.slice(1),
|
|
21
|
+
value.url ? shortUrl(value.url) : '',
|
|
22
|
+
]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(' · ')}
|
|
25
|
+
{...(value.url ? { href: value.url } : {})}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// An agent session started somewhere else — a cloud session the Agent tool
|
|
2
|
+
// launched, a workflow — which the user can open and follow.
|
|
3
|
+
|
|
4
|
+
import type { ReactElement } from 'react';
|
|
5
|
+
|
|
6
|
+
import type { SessionLinkData } from '../core/types.js';
|
|
7
|
+
import { LinkCard, shortUrl } from './LinkCard.js';
|
|
8
|
+
|
|
9
|
+
export function SessionCard({
|
|
10
|
+
value,
|
|
11
|
+
}: {
|
|
12
|
+
value: SessionLinkData;
|
|
13
|
+
}): ReactElement {
|
|
14
|
+
return (
|
|
15
|
+
<LinkCard
|
|
16
|
+
kind="Session started"
|
|
17
|
+
title={value.title ?? 'A new session'}
|
|
18
|
+
detail={shortUrl(value.url)}
|
|
19
|
+
href={value.url}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// What the harness suggests the user might say next, as chips under the
|
|
2
|
+
// last reply. Pressing one puts it in the composer to edit or send; it never
|
|
3
|
+
// sends by itself.
|
|
4
|
+
|
|
5
|
+
import type { ReactElement } from 'react';
|
|
6
|
+
import { Button } from 'react-x11';
|
|
7
|
+
|
|
8
|
+
import { useChat } from './context.js';
|
|
9
|
+
import { useLook } from './look.js';
|
|
10
|
+
|
|
11
|
+
export function SuggestedReplies({
|
|
12
|
+
suggestions,
|
|
13
|
+
}: {
|
|
14
|
+
suggestions: readonly string[];
|
|
15
|
+
}): ReactElement {
|
|
16
|
+
const { composer } = useChat();
|
|
17
|
+
const look = useLook();
|
|
18
|
+
return (
|
|
19
|
+
<box
|
|
20
|
+
style={{ flexDirection: 'row', flexWrap: 'wrap', gap: look.gap }}
|
|
21
|
+
aria-label="Suggested replies"
|
|
22
|
+
>
|
|
23
|
+
{suggestions.map((s) => (
|
|
24
|
+
<Button
|
|
25
|
+
key={s}
|
|
26
|
+
size="small"
|
|
27
|
+
variant="outline"
|
|
28
|
+
label={s}
|
|
29
|
+
onPress={() => composer.fill(s)}
|
|
30
|
+
/>
|
|
31
|
+
))}
|
|
32
|
+
</box>
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/view/ToolCard.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { TerminalOutput } from '@react-x11/components/terminal-output';
|
|
|
12
12
|
|
|
13
13
|
import type { ToolOutput, ToolPart } from '../core/types.js';
|
|
14
14
|
import { useChat } from './context.js';
|
|
15
|
+
import { ImageView } from './ImageView.js';
|
|
15
16
|
import { clipLines, useLook, type ChatLook } from './look.js';
|
|
16
17
|
import { MessageList } from './MessageList.js';
|
|
17
18
|
|
|
@@ -235,7 +236,15 @@ function Output({
|
|
|
235
236
|
case 'diff':
|
|
236
237
|
return <Diff output={output} />;
|
|
237
238
|
case 'image':
|
|
238
|
-
return
|
|
239
|
+
return (
|
|
240
|
+
<ImageView
|
|
241
|
+
source={{ data: output.data }}
|
|
242
|
+
{...(output.width && output.height
|
|
243
|
+
? { width: output.width, height: output.height }
|
|
244
|
+
: {})}
|
|
245
|
+
alt={`An image, ${output.mime}`}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
239
248
|
case 'value':
|
|
240
249
|
return (
|
|
241
250
|
<Code source={clipLines(json(output.value), 80)} lang="json" wrap />
|
package/src/view/Transcript.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import type { ScrollableNode, ScrollEvent } from 'react-x11';
|
|
|
15
15
|
import { useChat } from './context.js';
|
|
16
16
|
import { useLook } from './look.js';
|
|
17
17
|
import { MessageList } from './MessageList.js';
|
|
18
|
+
import { SuggestedReplies } from './SuggestedReplies.js';
|
|
18
19
|
import { useSessionSnapshot } from './use-agent.js';
|
|
19
20
|
|
|
20
21
|
/** Within this many pixels of the end counts as at the end. */
|
|
@@ -45,6 +46,13 @@ export function Transcript({ empty }: { empty?: ReactNode }): ReactElement {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
const activity = activityOf(snap.status, snap.messages);
|
|
49
|
+
// offered once the turn is over, under the reply they follow
|
|
50
|
+
const suggestions =
|
|
51
|
+
snap.status === 'idle' &&
|
|
52
|
+
snap.suggestions.length &&
|
|
53
|
+
snap.messages[snap.messages.length - 1]?.role === 'assistant'
|
|
54
|
+
? snap.suggestions
|
|
55
|
+
: null;
|
|
48
56
|
return (
|
|
49
57
|
<box
|
|
50
58
|
ref={pane}
|
|
@@ -59,6 +67,7 @@ export function Transcript({ empty }: { empty?: ReactNode }): ReactElement {
|
|
|
59
67
|
) : (
|
|
60
68
|
(empty ?? null)
|
|
61
69
|
)}
|
|
70
|
+
{suggestions ? <SuggestedReplies suggestions={suggestions} /> : null}
|
|
62
71
|
{activity ? (
|
|
63
72
|
<text style={{ color: look.muted, fontSize: 12 }}>{activity}</text>
|
|
64
73
|
) : null}
|
package/src/view/context.ts
CHANGED
|
@@ -33,6 +33,25 @@ export interface ChatContextValue {
|
|
|
33
33
|
onLink: ((href: string) => void) | undefined;
|
|
34
34
|
/** Base text size, the theme's unless the app said. */
|
|
35
35
|
fontSize: number | undefined;
|
|
36
|
+
/** The message box, for a part that puts text in it without sending. */
|
|
37
|
+
composer: ComposerChannel;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** How a part reaches the composer: it attaches while mounted. */
|
|
41
|
+
export interface ComposerChannel {
|
|
42
|
+
/** Replace what is in the box, and focus it. Sends nothing. */
|
|
43
|
+
fill(text: string): void;
|
|
44
|
+
attach(fill: ((text: string) => void) | null): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function composerChannel(): ComposerChannel {
|
|
48
|
+
let current: ((text: string) => void) | null = null;
|
|
49
|
+
return {
|
|
50
|
+
fill: (text) => current?.(text),
|
|
51
|
+
attach: (fill) => {
|
|
52
|
+
current = fill;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
export const ChatContext = createContext<ChatContextValue | null>(null);
|
package/src/view/look.ts
CHANGED
|
@@ -22,6 +22,20 @@ export interface ChatLook {
|
|
|
22
22
|
mono: string;
|
|
23
23
|
/** The user's bubble: the text colour, faintly, so it shows on any ground. */
|
|
24
24
|
bubble: string;
|
|
25
|
+
/** Labels, states and captions. */
|
|
26
|
+
smallSize: number;
|
|
27
|
+
/** A secondary line: a thought, a notice, a card's detail. */
|
|
28
|
+
captionSize: number;
|
|
29
|
+
/** A card's corners. */
|
|
30
|
+
radius: number;
|
|
31
|
+
/** Inside a card, from its edge. */
|
|
32
|
+
inset: number;
|
|
33
|
+
/** Between a card's pieces. */
|
|
34
|
+
gap: number;
|
|
35
|
+
/** The tallest an image in a reply or a tool's output is drawn. */
|
|
36
|
+
imageHeight: number;
|
|
37
|
+
/** The tallest an image in the user's own message is drawn. */
|
|
38
|
+
thumbHeight: number;
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
export function useLook(): ChatLook {
|
|
@@ -40,6 +54,13 @@ export function useLook(): ChatLook {
|
|
|
40
54
|
warning: String(theme.warning ?? '#e67e22'),
|
|
41
55
|
mono: String(theme.monoFamily ?? 'monospace'),
|
|
42
56
|
bubble: tint(text, 0.07),
|
|
57
|
+
smallSize: 11,
|
|
58
|
+
captionSize: 12,
|
|
59
|
+
radius: 6,
|
|
60
|
+
inset: 8,
|
|
61
|
+
gap: 6,
|
|
62
|
+
imageHeight: 320,
|
|
63
|
+
thumbHeight: 160,
|
|
43
64
|
};
|
|
44
65
|
}, [theme]);
|
|
45
66
|
}
|