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
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
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
|
-
//
|
|
128
|
-
if (
|
|
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
|
-
|
|
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.
|
|
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",
|