@workerdeck/ui 0.6.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/ui",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Styled agent-control component library for WorkerDeck: session panel, transcript with streaming, tool-call cards, permission prompts, session list, composer. Tailwind v4 + Base UI + cva. Pairs with the headless @workerdeck/react layer.",
6
6
  "license": "MIT",
@@ -27,9 +27,9 @@
27
27
  "streamdown": "^2.5.0",
28
28
  "tailwind-merge": "^3.6.0",
29
29
  "use-stick-to-bottom": "^1.1.6",
30
- "@workerdeck/protocol": "0.6.0",
31
- "@workerdeck/client": "0.6.0",
32
- "@workerdeck/react": "0.6.0"
30
+ "@workerdeck/client": "0.9.0",
31
+ "@workerdeck/protocol": "0.9.0",
32
+ "@workerdeck/react": "0.9.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
@@ -90,6 +90,7 @@ export function SessionPanel({ client, sessionId, header, className }: SessionPa
90
90
  <Transcript
91
91
  state={state}
92
92
  fileUrl={sessionId ? (path) => client.sessionFileUrl(sessionId, path) : undefined}
93
+ attachmentUrl={sessionId ? (id) => client.attachmentUrl(sessionId, id) : undefined}
93
94
  />
94
95
  {state.pendingApprovals.length > 0 ? (
95
96
  <div className='px-3 pb-2'>
@@ -1,3 +1,4 @@
1
+ import type { MessageAttachment } from '@workerdeck/protocol'
1
2
  import type { TranscriptItem, TranscriptState } from '@workerdeck/react'
2
3
  import { cn } from '../../lib/utils.ts'
3
4
  import { formatCost, formatDuration } from '../../lib/format.ts'
@@ -40,15 +41,21 @@ function NoticeRow({ item }: { item: Extract<TranscriptItem, { kind: 'notice' }>
40
41
  function TranscriptItemView({
41
42
  item,
42
43
  fileUrl,
44
+ attachmentUrl,
43
45
  }: {
44
46
  item: TranscriptItem
45
47
  fileUrl?: (path: string) => string
48
+ attachmentUrl?: (attachmentId: string) => string
46
49
  }) {
47
50
  switch (item.kind) {
48
51
  case 'user':
49
52
  return (
50
53
  <Message from='user'>
51
- <MessageContent>{item.text}</MessageContent>
54
+ {item.attachments?.length ? (
55
+ <SentAttachments attachments={item.attachments} attachmentUrl={attachmentUrl} />
56
+ ) : null}
57
+ {/* A photo can be the whole message — an empty bubble under it says nothing. */}
58
+ {item.text ? <MessageContent>{item.text}</MessageContent> : null}
52
59
  </Message>
53
60
  )
54
61
  case 'assistant_text':
@@ -85,15 +92,51 @@ function showLoader(state: TranscriptState): boolean {
85
92
  return last.kind !== 'turn_result' || state.status === 'running'
86
93
  }
87
94
 
95
+ /** Files sent with a message: thumbnails for images, named chips for the rest.
96
+ * References only — the bytes are fetched from the gateway. */
97
+ function SentAttachments({
98
+ attachments,
99
+ attachmentUrl,
100
+ }: {
101
+ attachments: MessageAttachment[]
102
+ attachmentUrl?: (attachmentId: string) => string
103
+ }) {
104
+ return (
105
+ <div className='mb-1 flex flex-wrap justify-end gap-1.5'>
106
+ {attachments.map((attachment) => {
107
+ const href = attachmentUrl?.(attachment.id)
108
+ return attachment.mediaType.startsWith('image/') && href ? (
109
+ <img
110
+ key={attachment.id}
111
+ src={href}
112
+ alt={attachment.name}
113
+ className='size-20 rounded-md border border-border object-cover'
114
+ />
115
+ ) : (
116
+ <span
117
+ key={attachment.id}
118
+ className='rounded-full border border-border bg-surface px-2.5 py-1 text-body-xs text-fg-3'>
119
+ {attachment.name}
120
+ </span>
121
+ )
122
+ })}
123
+ </div>
124
+ )
125
+ }
126
+
88
127
  export interface TranscriptProps {
89
128
  state: TranscriptState
90
129
  /** Builds the download URL for a delivered file (see FileCard). Typically
91
130
  * `(path) => client.sessionFileUrl(sessionId, path)`. */
92
131
  fileUrl?: (path: string) => string
132
+ /** Builds the URL for an uploaded attachment. Typically
133
+ * `(id) => client.attachmentUrl(sessionId, id)`. Same-origin and
134
+ * cookie-authenticated, which is what lets an `<img src>` render one. */
135
+ attachmentUrl?: (attachmentId: string) => string
93
136
  className?: string
94
137
  }
95
138
 
96
- export function Transcript({ state, fileUrl, className }: TranscriptProps) {
139
+ export function Transcript({ state, fileUrl, attachmentUrl, className }: TranscriptProps) {
97
140
  return (
98
141
  <Conversation className={className}>
99
142
  <ConversationContent>
@@ -101,7 +144,12 @@ export function Transcript({ state, fileUrl, className }: TranscriptProps) {
101
144
  <div className='py-12 text-center text-body-sm text-fg-4'>No messages yet.</div>
102
145
  ) : (
103
146
  state.items.map((item) => (
104
- <TranscriptItemView key={`${item.kind}:${item.id}`} item={item} fileUrl={fileUrl} />
147
+ <TranscriptItemView
148
+ key={`${item.kind}:${item.id}`}
149
+ item={item}
150
+ fileUrl={fileUrl}
151
+ attachmentUrl={attachmentUrl}
152
+ />
105
153
  ))
106
154
  )}
107
155
  {showLoader(state) ? (