assistant-ui 0.0.125 → 0.0.127
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/README.md +18 -2
- package/dist/codemods/utils/createTransformer.js +1 -2
- package/dist/codemods/utils/resolveBinding.js +1 -2
- package/dist/codemods/v0-11/content-part-to-message-part.js +1 -2
- package/dist/codemods/v0-12/assistant-api-to-aui.js +1 -2
- package/dist/codemods/v0-12/event-names-to-camelcase.js +1 -2
- package/dist/codemods/v0-12/primitive-if-to-aui-if.js +1 -2
- package/dist/codemods/v0-15/aui-accessor-calls-to-properties.js +1 -2
- package/dist/codemods/v0-8/ui-package-split.js +1 -2
- package/dist/codemods/v0-9/edge-package-split.js +1 -2
- package/dist/commands/add.js +1 -2
- package/dist/commands/agent.js +1 -2
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +70 -31
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/create.js +1 -2
- package/dist/commands/doctor.js +1 -2
- package/dist/commands/info.js +1 -2
- package/dist/commands/init.js +1 -2
- package/dist/commands/mcp.js +1 -2
- package/dist/commands/update.js +1 -2
- package/dist/commands/upgrade.js +1 -2
- package/dist/index.js +1 -2
- package/dist/lib/agent-skill.d.ts +2 -2
- package/dist/lib/agent-skill.js +2 -3
- package/dist/lib/agent-skill.js.map +1 -1
- package/dist/lib/cloud-abort.js +1 -2
- package/dist/lib/cloud-auth.d.ts +1 -0
- package/dist/lib/cloud-auth.d.ts.map +1 -1
- package/dist/lib/cloud-auth.js +2 -3
- package/dist/lib/cloud-auth.js.map +1 -1
- package/dist/lib/cloud-config.d.ts +11 -1
- package/dist/lib/cloud-config.d.ts.map +1 -1
- package/dist/lib/cloud-config.js +52 -16
- package/dist/lib/cloud-config.js.map +1 -1
- package/dist/lib/cloud-provision.d.ts.map +1 -1
- package/dist/lib/cloud-provision.js +7 -3
- package/dist/lib/cloud-provision.js.map +1 -1
- package/dist/lib/cloud-remote.d.ts +16 -1
- package/dist/lib/cloud-remote.d.ts.map +1 -1
- package/dist/lib/cloud-remote.js +53 -9
- package/dist/lib/cloud-remote.js.map +1 -1
- package/dist/lib/cloud-setup-login.js +1 -2
- package/dist/lib/cloud-url.js +1 -2
- package/dist/lib/create-project.js +1 -2
- package/dist/lib/handle-cli-error.js +1 -2
- package/dist/lib/install-ai-sdk-lib.js +1 -2
- package/dist/lib/install-edge-lib.js +1 -2
- package/dist/lib/run-spawn.js +1 -2
- package/dist/lib/safe-project-file.d.ts +2 -0
- package/dist/lib/safe-project-file.d.ts.map +1 -0
- package/dist/lib/safe-project-file.js +25 -0
- package/dist/lib/safe-project-file.js.map +1 -0
- package/dist/lib/transform.js +1 -2
- package/dist/lib/upgrade.js +1 -2
- package/dist/lib/utils/config.js +1 -2
- package/dist/lib/utils/download.js +1 -2
- package/dist/lib/utils/file-scanner.js +1 -2
- package/dist/lib/utils/logger.js +1 -2
- package/dist/lib/utils/package-installer.js +1 -2
- package/dist/lib/utils/package-json.js +1 -2
- package/dist/lib/utils/package-manager.js +1 -2
- package/dist/lib/utils/registry.js +1 -2
- package/dist/lib/utils/workspace.js +1 -2
- package/dist/program.js +1 -2
- package/dist/run.js +1 -2
- package/package.json +7 -7
- package/src/commands/cloud.test.ts +156 -0
- package/src/commands/cloud.ts +93 -41
- package/src/commands/mcp.test.ts +123 -0
- package/src/lib/agent-skill.ts +1 -1
- package/src/lib/cloud-auth.ts +1 -1
- package/src/lib/cloud-config.test.ts +139 -1
- package/src/lib/cloud-config.ts +59 -8
- package/src/lib/cloud-provision.test.ts +26 -1
- package/src/lib/cloud-provision.ts +3 -0
- package/src/lib/cloud-remote.test.ts +99 -3
- package/src/lib/cloud-remote.ts +93 -7
- package/src/lib/safe-project-file.ts +31 -0
- package/templates/cloud-harness/components/assistant-ui/elements/attachment-state.ts +59 -0
- package/templates/cloud-harness/components/assistant-ui/elements/attachment.aui.tsx +11 -44
- package/templates/cloud-harness/components/assistant-ui/elements/thread.aui.tsx +1 -1
- package/templates/cloud-harness/package.json +12 -12
- package/templates/cloud-harness/.gitignore +0 -42
|
@@ -11,14 +11,19 @@ afterEach(() => {
|
|
|
11
11
|
vi.unstubAllGlobals();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
const harness = (
|
|
14
|
+
const harness = (
|
|
15
|
+
reply: CloudCliResponse,
|
|
16
|
+
token: string | null = "token",
|
|
17
|
+
extra: Partial<Parameters<typeof runCloudCommand>[1]> = {},
|
|
18
|
+
) => {
|
|
15
19
|
const output = { stdout: "", stderr: "" };
|
|
16
|
-
const send = vi.fn(async () => reply);
|
|
20
|
+
const send = vi.fn(async (..._args: unknown[]) => reply);
|
|
17
21
|
return {
|
|
18
22
|
output,
|
|
19
23
|
send,
|
|
20
24
|
run: (args: string[]) =>
|
|
21
25
|
runCloudCommand(args, {
|
|
26
|
+
...extra,
|
|
22
27
|
cloudUrl: "https://cloud.example.com",
|
|
23
28
|
accessToken: async () => token,
|
|
24
29
|
stdout: (text) => {
|
|
@@ -136,7 +141,7 @@ describe("cloud remote commands", () => {
|
|
|
136
141
|
expect(init.headers).toMatchObject({ Authorization: "Bearer tok" });
|
|
137
142
|
expect(JSON.parse(init.body as string)).toMatchObject({
|
|
138
143
|
command: "help 'a b'",
|
|
139
|
-
client: { name: "assistant-ui" },
|
|
144
|
+
client: { name: "assistant-ui", actions: ["print_url", "open_url"] },
|
|
140
145
|
});
|
|
141
146
|
});
|
|
142
147
|
|
|
@@ -153,4 +158,95 @@ describe("cloud remote commands", () => {
|
|
|
153
158
|
)[1];
|
|
154
159
|
expect(init.headers).not.toHaveProperty("Authorization");
|
|
155
160
|
});
|
|
161
|
+
|
|
162
|
+
it("prints a print_url link on stderr and never opens it", async () => {
|
|
163
|
+
const openUrl = vi.fn();
|
|
164
|
+
const { run, output } = harness(
|
|
165
|
+
{
|
|
166
|
+
stdout: "Deleting needs a confirmation.\n",
|
|
167
|
+
exitCode: 0,
|
|
168
|
+
actions: [
|
|
169
|
+
{
|
|
170
|
+
type: "print_url",
|
|
171
|
+
url: "https://cloud.example.com/o/acme/settings",
|
|
172
|
+
label: "Confirm in the dashboard",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
"token",
|
|
177
|
+
{ interactive: true, openUrl },
|
|
178
|
+
);
|
|
179
|
+
await expect(run(["projects", "delete", "web"])).resolves.toBe(0);
|
|
180
|
+
expect(output).toEqual({
|
|
181
|
+
stdout: "Deleting needs a confirmation.\n",
|
|
182
|
+
stderr:
|
|
183
|
+
"Confirm in the dashboard: https://cloud.example.com/o/acme/settings\n",
|
|
184
|
+
});
|
|
185
|
+
expect(openUrl).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("opens an open_url link only when interactive, without --no-open, on the same Cloud", async () => {
|
|
189
|
+
const reply = (url: string): CloudCliResponse => ({
|
|
190
|
+
exitCode: 0,
|
|
191
|
+
actions: [{ type: "open_url", url }],
|
|
192
|
+
});
|
|
193
|
+
const dashboard = "https://cloud.example.com/o/acme";
|
|
194
|
+
const cases: [boolean, boolean, string, boolean][] = [
|
|
195
|
+
[true, false, dashboard, true],
|
|
196
|
+
[true, true, dashboard, false],
|
|
197
|
+
[false, false, dashboard, false],
|
|
198
|
+
[true, false, "https://evil.example.org/", false],
|
|
199
|
+
];
|
|
200
|
+
for (const [interactive, noOpen, url, opens] of cases) {
|
|
201
|
+
const openUrl = vi.fn();
|
|
202
|
+
const { run, output } = harness(reply(url), "token", {
|
|
203
|
+
interactive,
|
|
204
|
+
noOpen,
|
|
205
|
+
openUrl,
|
|
206
|
+
});
|
|
207
|
+
await expect(run(["x"])).resolves.toBe(0);
|
|
208
|
+
expect(output.stderr).toBe(`Open: ${url}\n`);
|
|
209
|
+
expect(openUrl).toHaveBeenCalledTimes(opens ? 1 : 0);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("fails a malformed link action before printing", async () => {
|
|
214
|
+
for (const url of [undefined, 3, "javascript:alert(1)", "not a url"]) {
|
|
215
|
+
const { run, output } = harness({
|
|
216
|
+
stdout: "x\n",
|
|
217
|
+
exitCode: 0,
|
|
218
|
+
actions: [{ type: "open_url", url }],
|
|
219
|
+
});
|
|
220
|
+
await expect(run(["x"])).resolves.toBe(1);
|
|
221
|
+
expect(output.stdout).toBe("");
|
|
222
|
+
expect(output.stderr).toMatch(/malformed "open_url"/);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("passes the directory's default org and project to the server", async () => {
|
|
227
|
+
const { run, send } = harness({ exitCode: 0 }, "token", {
|
|
228
|
+
defaults: async () => ({ org: "org_1", project: "proj_1" }),
|
|
229
|
+
});
|
|
230
|
+
await run(["projects", "show"]);
|
|
231
|
+
expect(send).toHaveBeenCalledWith(
|
|
232
|
+
"https://cloud.example.com",
|
|
233
|
+
["projects", "show"],
|
|
234
|
+
"token",
|
|
235
|
+
{ org: "org_1", project: "proj_1" },
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("sends defaults in the request body only when there are some", async () => {
|
|
240
|
+
const fetchMock = vi.fn(async () => Response.json({ exitCode: 0 }));
|
|
241
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
242
|
+
await sendCloudCommand("https://cloud.example.com", ["x"], null, {
|
|
243
|
+
org: "org_1",
|
|
244
|
+
});
|
|
245
|
+
await sendCloudCommand("https://cloud.example.com", ["x"], null, {});
|
|
246
|
+
const bodies = fetchMock.mock.calls.map((call) =>
|
|
247
|
+
JSON.parse((call as unknown as [string, RequestInit])[1].body as string),
|
|
248
|
+
);
|
|
249
|
+
expect(bodies[0].defaults).toEqual({ org: "org_1" });
|
|
250
|
+
expect(bodies[1]).not.toHaveProperty("defaults");
|
|
251
|
+
});
|
|
156
252
|
});
|
package/src/lib/cloud-remote.ts
CHANGED
|
@@ -4,6 +4,12 @@ export const CLOUD_CLI_RUN_PATH = "/api/cli/run";
|
|
|
4
4
|
|
|
5
5
|
export type CloudCliAction = { type: string; [key: string]: unknown };
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The organization and project a command uses when it is given no --org or
|
|
9
|
+
* --project, read from the `.assistant-ui/cloud.json` that setup writes.
|
|
10
|
+
*/
|
|
11
|
+
export type CloudCliDefaults = { org?: string; project?: string };
|
|
12
|
+
|
|
7
13
|
export type CloudCliResponse = {
|
|
8
14
|
stdout?: string;
|
|
9
15
|
stderr?: string;
|
|
@@ -62,6 +68,7 @@ export const sendCloudCommand = async (
|
|
|
62
68
|
cloudUrl: string,
|
|
63
69
|
args: readonly string[],
|
|
64
70
|
accessToken: string | null,
|
|
71
|
+
defaults?: CloudCliDefaults | null,
|
|
65
72
|
): Promise<CloudCliResponse> => {
|
|
66
73
|
const url = `${validateCloudUrl(cloudUrl)}${CLOUD_CLI_RUN_PATH}`;
|
|
67
74
|
let response: Response;
|
|
@@ -78,7 +85,10 @@ export const sendCloudCommand = async (
|
|
|
78
85
|
name: "assistant-ui",
|
|
79
86
|
version: cliVersion,
|
|
80
87
|
platform: `${process.platform}-${process.arch}`,
|
|
88
|
+
// The server sends only the actions listed here.
|
|
89
|
+
actions: Object.keys(ACTION_HANDLERS),
|
|
81
90
|
},
|
|
91
|
+
...(defaults && (defaults.org || defaults.project) ? { defaults } : {}),
|
|
82
92
|
}),
|
|
83
93
|
redirect: "error",
|
|
84
94
|
signal: AbortSignal.timeout(60_000),
|
|
@@ -98,12 +108,62 @@ export const sendCloudCommand = async (
|
|
|
98
108
|
return body;
|
|
99
109
|
};
|
|
100
110
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
(
|
|
106
|
-
|
|
111
|
+
type ActionContext = {
|
|
112
|
+
cloudUrl: string;
|
|
113
|
+
/** False when the user passed --no-open or no one is at the terminal. */
|
|
114
|
+
open: boolean;
|
|
115
|
+
openUrl: (url: string) => void;
|
|
116
|
+
stderr: (text: string) => void;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
type ActionHandler = {
|
|
120
|
+
/** Checks the action's fields; a malformed action fails before printing. */
|
|
121
|
+
valid(action: CloudCliAction): boolean;
|
|
122
|
+
run(action: CloudCliAction, context: ActionContext): Promise<number> | number;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const httpUrl = (value: unknown): URL | null => {
|
|
126
|
+
if (typeof value !== "string") return null;
|
|
127
|
+
try {
|
|
128
|
+
const url = new URL(value);
|
|
129
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url : null;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const urlAction = (action: CloudCliAction): boolean =>
|
|
136
|
+
!!httpUrl(action["url"]) &&
|
|
137
|
+
(action["label"] === undefined || typeof action["label"] === "string");
|
|
138
|
+
|
|
139
|
+
const printUrl = (action: CloudCliAction, context: ActionContext): void => {
|
|
140
|
+
const label = (action["label"] as string | undefined) ?? "Open";
|
|
141
|
+
context.stderr(`${label}: ${action["url"] as string}\n`);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// The request lists these (`client.actions`) and the server sends no others;
|
|
145
|
+
// a response naming one this client lacks still fails before printing.
|
|
146
|
+
const ACTION_HANDLERS: Record<string, ActionHandler> = {
|
|
147
|
+
print_url: {
|
|
148
|
+
valid: urlAction,
|
|
149
|
+
run: (action, context) => {
|
|
150
|
+
printUrl(action, context);
|
|
151
|
+
return 0;
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
// Prints the link, then opens it in a browser when someone is at the
|
|
155
|
+
// terminal. Only links on the Cloud the command ran against are opened.
|
|
156
|
+
open_url: {
|
|
157
|
+
valid: urlAction,
|
|
158
|
+
run: (action, context) => {
|
|
159
|
+
printUrl(action, context);
|
|
160
|
+
const url = httpUrl(action["url"])!;
|
|
161
|
+
if (context.open && url.origin === new URL(context.cloudUrl).origin)
|
|
162
|
+
context.openUrl(url.href);
|
|
163
|
+
return 0;
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
107
167
|
|
|
108
168
|
export const runCloudCommand = async (
|
|
109
169
|
args: readonly string[],
|
|
@@ -113,10 +173,20 @@ export const runCloudCommand = async (
|
|
|
113
173
|
stdout: (text: string) => void;
|
|
114
174
|
stderr: (text: string) => void;
|
|
115
175
|
send?: typeof sendCloudCommand;
|
|
176
|
+
/** The default org/project for this directory, if setup configured one. */
|
|
177
|
+
defaults?: () => Promise<CloudCliDefaults | null>;
|
|
178
|
+
/** Whether someone is at the terminal to see a browser open. */
|
|
179
|
+
interactive?: boolean;
|
|
180
|
+
/** --no-open: print links without opening them. */
|
|
181
|
+
noOpen?: boolean;
|
|
182
|
+
openUrl?: (url: string) => void;
|
|
116
183
|
},
|
|
117
184
|
): Promise<number> => {
|
|
118
185
|
let response: CloudCliResponse;
|
|
119
186
|
try {
|
|
187
|
+
const defaults = deps.defaults
|
|
188
|
+
? await deps.defaults().catch(() => null)
|
|
189
|
+
: null;
|
|
120
190
|
response = await (deps.send ?? sendCloudCommand)(
|
|
121
191
|
deps.cloudUrl,
|
|
122
192
|
args,
|
|
@@ -126,6 +196,7 @@ export const runCloudCommand = async (
|
|
|
126
196
|
);
|
|
127
197
|
return null;
|
|
128
198
|
}),
|
|
199
|
+
...(defaults ? [defaults] : []),
|
|
129
200
|
);
|
|
130
201
|
} catch (error) {
|
|
131
202
|
deps.stderr(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -140,10 +211,25 @@ export const runCloudCommand = async (
|
|
|
140
211
|
);
|
|
141
212
|
return 1;
|
|
142
213
|
}
|
|
214
|
+
const malformed = response.actions?.find(
|
|
215
|
+
(action) => !ACTION_HANDLERS[action.type]!.valid(action),
|
|
216
|
+
);
|
|
217
|
+
if (malformed) {
|
|
218
|
+
deps.stderr(
|
|
219
|
+
`Unexpected response from the server (malformed "${malformed.type}" action).\n`,
|
|
220
|
+
);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
143
223
|
if (response.stdout) deps.stdout(response.stdout);
|
|
144
224
|
if (response.stderr) deps.stderr(response.stderr);
|
|
225
|
+
const context: ActionContext = {
|
|
226
|
+
cloudUrl: deps.cloudUrl,
|
|
227
|
+
open: !deps.noOpen && !!deps.interactive && !!deps.openUrl,
|
|
228
|
+
openUrl: deps.openUrl ?? (() => {}),
|
|
229
|
+
stderr: deps.stderr,
|
|
230
|
+
};
|
|
145
231
|
for (const action of response.actions ?? []) {
|
|
146
|
-
const code = await ACTION_HANDLERS[action.type]
|
|
232
|
+
const code = await ACTION_HANDLERS[action.type]!.run(action, context);
|
|
147
233
|
if (code !== 0) return code;
|
|
148
234
|
}
|
|
149
235
|
return response.exitCode;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const assertSafeProjectFile = (file: string, projectRoot: string) => {
|
|
5
|
+
try {
|
|
6
|
+
if (fs.lstatSync(file).isSymbolicLink()) {
|
|
7
|
+
throw new Error(`${file} is a symbolic link`);
|
|
8
|
+
}
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const root = fs.realpathSync(projectRoot);
|
|
14
|
+
let parent = path.dirname(file);
|
|
15
|
+
const missing: string[] = [];
|
|
16
|
+
while (!fs.existsSync(parent)) {
|
|
17
|
+
const next = path.dirname(parent);
|
|
18
|
+
if (next === parent) break;
|
|
19
|
+
missing.unshift(path.basename(parent));
|
|
20
|
+
parent = next;
|
|
21
|
+
}
|
|
22
|
+
const resolvedParent = path.join(fs.realpathSync(parent), ...missing);
|
|
23
|
+
const relative = path.relative(root, resolvedParent);
|
|
24
|
+
if (
|
|
25
|
+
relative === ".." ||
|
|
26
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
27
|
+
path.isAbsolute(relative)
|
|
28
|
+
) {
|
|
29
|
+
throw new Error(`${file} has a parent directory outside the project`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAui, useAuiState } from "@assistant-ui/react";
|
|
4
|
+
import { useAttachmentSrc } from "@/hooks/use-attachment-src";
|
|
5
|
+
|
|
6
|
+
export function useAttachmentState() {
|
|
7
|
+
const aui = useAui();
|
|
8
|
+
const isComposer = aui.attachment.source !== "message";
|
|
9
|
+
const src = useAttachmentSrc();
|
|
10
|
+
|
|
11
|
+
const isImage = useAuiState((s) => s.attachment.type === "image");
|
|
12
|
+
const name = useAuiState((s) => s.attachment.name);
|
|
13
|
+
const typeLabel = useAuiState((s) => {
|
|
14
|
+
const type = s.attachment.type;
|
|
15
|
+
switch (type) {
|
|
16
|
+
case "image":
|
|
17
|
+
return "Image";
|
|
18
|
+
case "document":
|
|
19
|
+
return "Document";
|
|
20
|
+
case "file":
|
|
21
|
+
return "File";
|
|
22
|
+
default:
|
|
23
|
+
return type;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// An attachment on a submission is still being prepared, whether or not the
|
|
28
|
+
// adapter reports progress while it uploads.
|
|
29
|
+
const uploadState = useAuiState((s) =>
|
|
30
|
+
s.attachment.status.type === "incomplete" &&
|
|
31
|
+
s.attachment.status.reason === "error"
|
|
32
|
+
? "error"
|
|
33
|
+
: s.attachment.status.type === "running" ||
|
|
34
|
+
(s.optional.message?.submission !== undefined &&
|
|
35
|
+
s.attachment.status.type !== "complete")
|
|
36
|
+
? "uploading"
|
|
37
|
+
: undefined,
|
|
38
|
+
);
|
|
39
|
+
const isUploading = uploadState === "uploading";
|
|
40
|
+
const isError = uploadState === "error";
|
|
41
|
+
|
|
42
|
+
const errorMessage = useAuiState((s) =>
|
|
43
|
+
s.attachment.status.type === "incomplete" &&
|
|
44
|
+
s.attachment.status.reason === "error"
|
|
45
|
+
? (s.attachment.status.message ?? "Upload failed")
|
|
46
|
+
: undefined,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
isComposer,
|
|
51
|
+
src,
|
|
52
|
+
isImage,
|
|
53
|
+
name,
|
|
54
|
+
typeLabel,
|
|
55
|
+
isUploading,
|
|
56
|
+
isError,
|
|
57
|
+
errorMessage,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -17,8 +17,6 @@ import {
|
|
|
17
17
|
AttachmentPrimitive,
|
|
18
18
|
ComposerPrimitive,
|
|
19
19
|
MessagePrimitive,
|
|
20
|
-
useAuiState,
|
|
21
|
-
useAui,
|
|
22
20
|
} from "@assistant-ui/react";
|
|
23
21
|
import {
|
|
24
22
|
Tooltip,
|
|
@@ -34,7 +32,7 @@ import {
|
|
|
34
32
|
} from "@/components/ui/dialog";
|
|
35
33
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
|
36
34
|
import { TooltipIconButton } from "@/components/assistant-ui/elements/tooltip-icon-button";
|
|
37
|
-
import {
|
|
35
|
+
import { useAttachmentState } from "./attachment-state";
|
|
38
36
|
import { cn } from "@/lib/utils";
|
|
39
37
|
|
|
40
38
|
type AttachmentPreviewProps = {
|
|
@@ -112,47 +110,16 @@ const AttachmentThumb: FC<{ src: string | undefined }> = ({ src }) => {
|
|
|
112
110
|
};
|
|
113
111
|
|
|
114
112
|
const AttachmentUI: FC = () => {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return "Image";
|
|
126
|
-
case "document":
|
|
127
|
-
return "Document";
|
|
128
|
-
case "file":
|
|
129
|
-
return "File";
|
|
130
|
-
default:
|
|
131
|
-
return type;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// An attachment on a submission is still being prepared, whether or not the
|
|
136
|
-
// adapter reports progress while it uploads.
|
|
137
|
-
const uploadState = useAuiState((s) =>
|
|
138
|
-
s.attachment.status.type === "incomplete" &&
|
|
139
|
-
s.attachment.status.reason === "error"
|
|
140
|
-
? "error"
|
|
141
|
-
: s.attachment.status.type === "running" ||
|
|
142
|
-
(s.optional.message?.submission !== undefined &&
|
|
143
|
-
s.attachment.status.type !== "complete")
|
|
144
|
-
? "uploading"
|
|
145
|
-
: undefined,
|
|
146
|
-
);
|
|
147
|
-
const isUploading = uploadState === "uploading";
|
|
148
|
-
const isError = uploadState === "error";
|
|
149
|
-
|
|
150
|
-
const errorMessage = useAuiState((s) =>
|
|
151
|
-
s.attachment.status.type === "incomplete" &&
|
|
152
|
-
s.attachment.status.reason === "error"
|
|
153
|
-
? (s.attachment.status.message ?? "Upload failed")
|
|
154
|
-
: undefined,
|
|
155
|
-
);
|
|
113
|
+
const {
|
|
114
|
+
isComposer,
|
|
115
|
+
src,
|
|
116
|
+
isImage,
|
|
117
|
+
name,
|
|
118
|
+
typeLabel,
|
|
119
|
+
isUploading,
|
|
120
|
+
isError,
|
|
121
|
+
errorMessage,
|
|
122
|
+
} = useAttachmentState();
|
|
156
123
|
|
|
157
124
|
return (
|
|
158
125
|
<TooltipProvider>
|
|
@@ -409,7 +409,7 @@ const AssistantActionBar: FC = () => {
|
|
|
409
409
|
className="aui-action-bar-more-content bg-popover/95 text-popover-foreground data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-xl border p-1.5 shadow-lg backdrop-blur-sm"
|
|
410
410
|
>
|
|
411
411
|
<ActionBarPrimitive.ExportMarkdown asChild>
|
|
412
|
-
<ActionBarMorePrimitive.Item className="aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex
|
|
412
|
+
<ActionBarMorePrimitive.Item className="aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm outline-none select-none">
|
|
413
413
|
<DownloadIcon className="size-4" />
|
|
414
414
|
Export as Markdown
|
|
415
415
|
</ActionBarMorePrimitive.Item>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"typecheck": "tsc --noEmit"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@ai-sdk/openai": "^4.0.
|
|
16
|
+
"@ai-sdk/openai": "^4.0.102",
|
|
17
17
|
"@assistant-ui/ai-sdk": "workspace:*",
|
|
18
18
|
"@assistant-ui/core": "workspace:*",
|
|
19
19
|
"@assistant-ui/react": "workspace:*",
|
|
@@ -21,30 +21,30 @@
|
|
|
21
21
|
"@assistant-ui/react-markdown": "workspace:*",
|
|
22
22
|
"@assistant-ui/store": "workspace:*",
|
|
23
23
|
"@assistant-ui/tap": "workspace:*",
|
|
24
|
-
"@base-ui/react": "^1.
|
|
25
|
-
"ai": "^7.0.
|
|
24
|
+
"@base-ui/react": "^1.9.0",
|
|
25
|
+
"ai": "^7.0.153",
|
|
26
26
|
"assistant-cloud": "workspace:*",
|
|
27
27
|
"class-variance-authority": "^0.7.1",
|
|
28
|
-
"cn": "^0.
|
|
28
|
+
"cn": "^0.5.0",
|
|
29
29
|
"harness-sdk": "file:vendor/harness-sdk-0.3.1.tgz",
|
|
30
|
-
"lucide-react": "^1.
|
|
31
|
-
"next": "^16.4.
|
|
32
|
-
"react": "^19.3.
|
|
33
|
-
"react-dom": "^19.3.
|
|
30
|
+
"lucide-react": "^1.61.0",
|
|
31
|
+
"next": "^16.4.3",
|
|
32
|
+
"react": "^19.3.1",
|
|
33
|
+
"react-dom": "^19.3.1",
|
|
34
34
|
"remark-gfm": "^4.0.1",
|
|
35
35
|
"statewire": "file:vendor/statewire-0.19.3.tgz",
|
|
36
36
|
"tw-animate-css": "^1.4.0",
|
|
37
37
|
"tw-shimmer": "^0.4.14",
|
|
38
|
-
"zustand": "^5.0.
|
|
38
|
+
"zustand": "^5.0.16"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@assistant-ui/next": "workspace:*",
|
|
42
42
|
"@tailwindcss/postcss": "^4.3.3",
|
|
43
|
-
"@types/node": "^26.6.
|
|
43
|
+
"@types/node": "^26.6.5",
|
|
44
44
|
"@types/react": "^19.3.0",
|
|
45
45
|
"@types/react-dom": "^19.3.0",
|
|
46
|
-
"oxfmt": "^0.
|
|
47
|
-
"oxlint": "^1.
|
|
46
|
+
"oxfmt": "^0.74.0",
|
|
47
|
+
"oxlint": "^1.89.0",
|
|
48
48
|
"tailwindcss": "^4.3.3",
|
|
49
49
|
"typescript": "^7.0.2"
|
|
50
50
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
-
|
|
3
|
-
# dependencies
|
|
4
|
-
/node_modules
|
|
5
|
-
/.pnp
|
|
6
|
-
.pnp.*
|
|
7
|
-
.yarn/*
|
|
8
|
-
!.yarn/patches
|
|
9
|
-
!.yarn/plugins
|
|
10
|
-
!.yarn/releases
|
|
11
|
-
!.yarn/versions
|
|
12
|
-
|
|
13
|
-
# testing
|
|
14
|
-
/coverage
|
|
15
|
-
|
|
16
|
-
# next.js
|
|
17
|
-
/.next/
|
|
18
|
-
/out/
|
|
19
|
-
|
|
20
|
-
# production
|
|
21
|
-
/build
|
|
22
|
-
|
|
23
|
-
# misc
|
|
24
|
-
.DS_Store
|
|
25
|
-
*.pem
|
|
26
|
-
|
|
27
|
-
# debug
|
|
28
|
-
npm-debug.log*
|
|
29
|
-
yarn-debug.log*
|
|
30
|
-
yarn-error.log*
|
|
31
|
-
.pnpm-debug.log*
|
|
32
|
-
|
|
33
|
-
# env files (can opt-in for committing if needed)
|
|
34
|
-
.env*
|
|
35
|
-
!.env.example
|
|
36
|
-
|
|
37
|
-
# vercel
|
|
38
|
-
.vercel
|
|
39
|
-
|
|
40
|
-
# typescript
|
|
41
|
-
*.tsbuildinfo
|
|
42
|
-
next-env.d.ts
|