create-supyagent-app 0.1.32 → 0.1.34

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": "create-supyagent-app",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Create a supyagent-powered chatbot app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,6 +56,7 @@ export function ToolMessage({ part, addToolApprovalResponse }: ToolMessageProps)
56
56
  const { data: polledData } = useJobPolling({
57
57
  initialData: rawData,
58
58
  enabled: !!formatterType && POLLABLE_TYPES.has(formatterType),
59
+ formatterType,
59
60
  });
60
61
 
61
62
  // Compute summary and rendered output from (possibly polled) data
@@ -5,6 +5,8 @@ import { useState, useEffect, useRef } from "react";
5
5
  interface UseJobPollingOptions {
6
6
  initialData: Record<string, unknown> | undefined;
7
7
  enabled: boolean;
8
+ /** Formatter type (e.g. "image", "video", "audio") — used to produce the right field names */
9
+ formatterType?: string;
8
10
  }
9
11
 
10
12
  interface UseJobPollingResult {
@@ -19,6 +21,7 @@ interface UseJobPollingResult {
19
21
  export function useJobPolling({
20
22
  initialData,
21
23
  enabled,
24
+ formatterType,
22
25
  }: UseJobPollingOptions): UseJobPollingResult {
23
26
  const [polledData, setPolledData] = useState<
24
27
  Record<string, unknown> | undefined
@@ -63,25 +66,22 @@ export function useJobPolling({
63
66
  });
64
67
 
65
68
  if (!res.ok) {
66
- // Retry on transient errors
67
69
  attempt++;
68
70
  timeoutRef.current = setTimeout(poll, getDelay(attempt));
69
71
  return;
70
72
  }
71
73
 
72
74
  const json = await res.json();
73
- const normalized = normalizeJobData(json, initialData);
75
+ const normalized = normalizeJobData(json, initialData, formatterType);
74
76
 
75
77
  setPolledData(normalized);
76
78
 
77
79
  if (normalized.status !== "processing") {
78
- // Terminal state — stop polling
79
80
  setIsPolling(false);
80
81
  return;
81
82
  }
82
83
  } catch (err: unknown) {
83
84
  if (err instanceof DOMException && err.name === "AbortError") return;
84
- // Retry on network errors
85
85
  }
86
86
 
87
87
  attempt++;
@@ -105,15 +105,42 @@ export function useJobPolling({
105
105
  }
106
106
 
107
107
  /**
108
- * Normalizes job API responses so renderers can find fields at the top level.
108
+ * Extracts a URL from a job result object.
109
+ * Job results from providers like Replicate store the URL in `output`, `url`,
110
+ * or a media-specific key like `image_url` / `audio_url` / `video_url`.
111
+ */
112
+ function extractUrlFromResult(
113
+ result: Record<string, unknown>
114
+ ): string | undefined {
115
+ for (const key of [
116
+ "output",
117
+ "url",
118
+ "image_url",
119
+ "audio_url",
120
+ "video_url",
121
+ ]) {
122
+ if (typeof result[key] === "string") return result[key] as string;
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ /**
128
+ * Normalizes job API responses so existing renderers can consume them directly.
109
129
  *
110
- * Job API may return `{ ok: true, data: { status, result: { image_url } } }`.
111
- * Renderers expect `d.image_url`, `d.audio_url`, `d.result.output`, etc.
112
- * We unwrap the envelope and spread `result` fields to the top level.
130
+ * The job polling API returns: `{ ok, data: { status, result: { output: "https://..." } } }`
131
+ *
132
+ * But renderers expect specific top-level keys:
133
+ * - image.tsx checks `d.image_url`
134
+ * - audio.tsx checks `d.audio_url`
135
+ * - video.tsx checks `d.result.output` (already works with raw shape)
136
+ *
137
+ * Without this normalization, `d.poll_url && !d.image_url` stays true and
138
+ * the image renderer shows the spinner forever.
113
139
  */
114
140
  function normalizeJobData(
115
141
  json: Record<string, unknown>,
116
- initial: Record<string, unknown>
142
+ initial: Record<string, unknown>,
143
+ formatterType?: string
117
144
  ): Record<string, unknown> {
118
145
  // Unwrap { ok, data } envelope
119
146
  let payload =
@@ -124,10 +151,26 @@ function normalizeJobData(
124
151
  // Merge with initial data so fields like poll_url, job_id carry through
125
152
  payload = { ...initial, ...payload };
126
153
 
127
- // Spread result fields to top level (image_url, audio_url, video_url, etc.)
128
- if (payload.result && typeof payload.result === "object") {
154
+ // If completed and result has a URL, set the renderer-specific field
155
+ if (
156
+ payload.status !== "processing" &&
157
+ payload.result &&
158
+ typeof payload.result === "object"
159
+ ) {
129
160
  const result = payload.result as Record<string, unknown>;
130
- payload = { ...result, ...payload };
161
+ const url = extractUrlFromResult(result);
162
+
163
+ if (url) {
164
+ switch (formatterType) {
165
+ case "image":
166
+ if (!payload.image_url) payload = { ...payload, image_url: url };
167
+ break;
168
+ case "audio":
169
+ if (!payload.audio_url) payload = { ...payload, audio_url: url };
170
+ break;
171
+ // video renderer already reads d.result.output — no top-level field needed
172
+ }
173
+ }
131
174
  }
132
175
 
133
176
  return payload;
@@ -17,7 +17,7 @@
17
17
  "{{aiProviderPackage}}": "{{aiProviderVersion}}",
18
18
  "@prisma/client": "^6.2.0",
19
19
  "@radix-ui/react-collapsible": "^1.1.0",
20
- "@supyagent/sdk": "^0.1.19",
20
+ "@supyagent/sdk": "^0.1.25",
21
21
  "class-variance-authority": "^0.7.0",
22
22
  "ai": "^6.0.0",
23
23
  "clsx": "^2.1.0",