assistant-ui 0.0.119 → 0.0.121
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 +60 -1
- package/dist/commands/cloud.d.ts +25 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +194 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +8 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/info.js +1 -1
- package/dist/lib/agent-skill.d.ts +2 -2
- package/dist/lib/agent-skill.js +1 -1
- package/dist/lib/agent-skill.js.map +1 -1
- package/dist/lib/cloud-abort.d.ts +2 -0
- package/dist/lib/cloud-abort.d.ts.map +1 -0
- package/dist/lib/cloud-abort.js +13 -0
- package/dist/lib/cloud-abort.js.map +1 -0
- package/dist/lib/cloud-auth.d.ts +28 -0
- package/dist/lib/cloud-auth.d.ts.map +1 -0
- package/dist/lib/cloud-auth.js +169 -0
- package/dist/lib/cloud-auth.js.map +1 -0
- package/dist/lib/cloud-config.d.ts +21 -0
- package/dist/lib/cloud-config.d.ts.map +1 -0
- package/dist/lib/cloud-config.js +76 -0
- package/dist/lib/cloud-config.js.map +1 -0
- package/dist/lib/cloud-provision.d.ts +20 -0
- package/dist/lib/cloud-provision.d.ts.map +1 -0
- package/dist/lib/cloud-provision.js +55 -0
- package/dist/lib/cloud-provision.js.map +1 -0
- package/dist/lib/cloud-setup-login.d.ts +9 -0
- package/dist/lib/cloud-setup-login.d.ts.map +1 -0
- package/dist/lib/cloud-setup-login.js +137 -0
- package/dist/lib/cloud-setup-login.js.map +1 -0
- package/dist/lib/cloud-url.d.ts +2 -0
- package/dist/lib/cloud-url.d.ts.map +1 -0
- package/dist/lib/cloud-url.js +14 -0
- package/dist/lib/cloud-url.js.map +1 -0
- package/dist/lib/transform.js +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -1
- package/dist/program.js.map +1 -1
- package/package.json +17 -13
- package/src/commands/cloud.test.ts +238 -0
- package/src/commands/cloud.ts +455 -0
- package/src/commands/create.ts +8 -0
- package/src/lib/agent-skill.ts +1 -1
- package/src/lib/cloud-abort.ts +12 -0
- package/src/lib/cloud-auth.test.ts +500 -0
- package/src/lib/cloud-auth.ts +273 -0
- package/src/lib/cloud-config.test.ts +82 -0
- package/src/lib/cloud-config.ts +138 -0
- package/src/lib/cloud-provision.test.ts +132 -0
- package/src/lib/cloud-provision.ts +93 -0
- package/src/lib/cloud-setup-login.test.ts +361 -0
- package/src/lib/cloud-setup-login.ts +240 -0
- package/src/lib/cloud-url.ts +19 -0
- package/src/program.ts +2 -0
- package/templates/cloud-harness/.assistant-ui/template.json +3 -0
- package/templates/cloud-harness/.env.example +1 -0
- package/templates/cloud-harness/.gitignore +42 -0
- package/templates/cloud-harness/README.md +28 -0
- package/templates/cloud-harness/app/api/chat/route.ts +13 -0
- package/templates/cloud-harness/app/api/credential/route.ts +14 -0
- package/templates/cloud-harness/app/assistant.tsx +83 -0
- package/templates/cloud-harness/app/favicon.ico +0 -0
- package/templates/cloud-harness/app/globals.css +139 -0
- package/templates/cloud-harness/app/layout.tsx +37 -0
- package/templates/cloud-harness/app/page.tsx +5 -0
- package/templates/cloud-harness/components/assistant-ui/elements/attachment.aui.tsx +279 -0
- package/templates/cloud-harness/components/assistant-ui/elements/markdown-text.tsx +238 -0
- package/templates/cloud-harness/components/assistant-ui/elements/thread.aui.tsx +521 -0
- package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +1153 -0
- package/templates/cloud-harness/components/assistant-ui/elements/tooltip-icon-button.tsx +49 -0
- package/templates/cloud-harness/components/ui/avatar.tsx +109 -0
- package/templates/cloud-harness/components/ui/button.tsx +58 -0
- package/templates/cloud-harness/components/ui/collapsible.tsx +21 -0
- package/templates/cloud-harness/components/ui/dialog.tsx +159 -0
- package/templates/cloud-harness/components/ui/textarea.tsx +18 -0
- package/templates/cloud-harness/components/ui/tooltip.tsx +69 -0
- package/templates/cloud-harness/components.json +24 -0
- package/templates/cloud-harness/hooks/use-attachment-src.ts +46 -0
- package/templates/cloud-harness/hooks/use-copy-to-clipboard.ts +54 -0
- package/templates/cloud-harness/lib/utils.ts +1 -0
- package/templates/cloud-harness/next.config.ts +6 -0
- package/templates/cloud-harness/package.json +51 -0
- package/templates/cloud-harness/postcss.config.mjs +5 -0
- package/templates/cloud-harness/tsconfig.json +33 -0
- package/templates/cloud-harness/vendor/README.md +13 -0
- package/templates/cloud-harness/vendor/assistant-ui-react-harness-sdk-0.0.2.tgz +0 -0
- package/templates/cloud-harness/vendor/harness-sdk-0.3.1.tgz +0 -0
- package/templates/cloud-harness/vendor/provenance.json +38 -0
- package/templates/cloud-harness/vendor/statewire-0.19.3.tgz +0 -0
- /package/{LICENSE → templates/cloud-harness/vendor/LICENSE} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const validateCloudUrl = (value: string): string => {
|
|
2
|
+
const url = new URL(value);
|
|
3
|
+
if (
|
|
4
|
+
url.username ||
|
|
5
|
+
url.password ||
|
|
6
|
+
url.search ||
|
|
7
|
+
url.hash ||
|
|
8
|
+
(url.protocol !== "https:" &&
|
|
9
|
+
!(
|
|
10
|
+
url.protocol === "http:" &&
|
|
11
|
+
["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)
|
|
12
|
+
))
|
|
13
|
+
) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Cloud, accounts and setup URLs must use HTTPS or HTTP localhost for development.",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return value.replace(/\/+$/, "");
|
|
19
|
+
};
|
package/src/program.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { mcp } from "./commands/mcp";
|
|
|
8
8
|
import { agent } from "./commands/agent";
|
|
9
9
|
import { info } from "./commands/info";
|
|
10
10
|
import { doctor } from "./commands/doctor";
|
|
11
|
+
import { cloud } from "./commands/cloud";
|
|
11
12
|
|
|
12
13
|
export function buildProgram() {
|
|
13
14
|
return new Command()
|
|
@@ -21,6 +22,7 @@ export function buildProgram() {
|
|
|
21
22
|
.addCommand(upgradeCommand)
|
|
22
23
|
.addCommand(update)
|
|
23
24
|
.addCommand(agent)
|
|
25
|
+
.addCommand(cloud)
|
|
24
26
|
.addCommand(info)
|
|
25
27
|
.addCommand(doctor);
|
|
26
28
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
OPENAI_API_KEY=
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Shared cloud chat
|
|
2
|
+
|
|
3
|
+
This Next.js starter connects assistant-ui to a hosted harness. Browser clients
|
|
4
|
+
on the same URL share conversation state, replies, and cancellation through
|
|
5
|
+
`HarnessCloudThreadList`.
|
|
6
|
+
|
|
7
|
+
Run `npx assistant-ui@latest cloud setup .` from this app to sign in and configure a project and harness.
|
|
8
|
+
Add your `OPENAI_API_KEY` to `.env.local`, install dependencies, and run `npm run dev`.
|
|
9
|
+
Open the app in two browser windows, or use **Share this chat** to copy its link.
|
|
10
|
+
Send a message from either window; both show the same reply as it streams.
|
|
11
|
+
Reload either window to restore the conversation.
|
|
12
|
+
|
|
13
|
+
The URL fragment selects the thread. A fresh app uses `main` in the
|
|
14
|
+
configured shared workspace. The server mints short-lived browser credentials for the
|
|
15
|
+
shared `hackathon` user. `ASSISTANT_API_KEY` and `OPENAI_API_KEY` stay in the
|
|
16
|
+
server environment. This starter admits visitors as the same shared user.
|
|
17
|
+
Both `/api/credential` and `/api/chat` admit public visitors in this hackathon
|
|
18
|
+
starter. Add your application authentication to both routes before deploying a
|
|
19
|
+
private workspace.
|
|
20
|
+
|
|
21
|
+
During local development, the existing harness runtime serves model requests
|
|
22
|
+
through the browser's localhost tunnel. Keep a browser connected while a run
|
|
23
|
+
is active. For deployment, add the application's HTTPS `/api/chat` endpoint to
|
|
24
|
+
the harness's allowed endpoints.
|
|
25
|
+
|
|
26
|
+
The harness runtimes are bundled as local package tarballs while their public
|
|
27
|
+
packages are in preview. See [vendor/README.md](vendor/README.md) for their source
|
|
28
|
+
revision, license, and reproduction command.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { openai } from "@ai-sdk/openai";
|
|
2
|
+
import { convertToModelMessages, streamText, type UIMessage } from "ai";
|
|
3
|
+
|
|
4
|
+
export const maxDuration = 30;
|
|
5
|
+
|
|
6
|
+
export const POST = async (request: Request) => {
|
|
7
|
+
const { messages } = (await request.json()) as { messages: UIMessage[] };
|
|
8
|
+
const result = streamText({
|
|
9
|
+
model: openai.responses("gpt-6-luna"),
|
|
10
|
+
messages: await convertToModelMessages(messages),
|
|
11
|
+
});
|
|
12
|
+
return result.toUIMessageStreamResponse();
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AssistantCloud } from "assistant-cloud";
|
|
2
|
+
|
|
3
|
+
export const GET = async () => {
|
|
4
|
+
const cloud = new AssistantCloud({
|
|
5
|
+
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
|
|
6
|
+
apiKey: process.env.ASSISTANT_API_KEY!,
|
|
7
|
+
workspaceId: process.env.NEXT_PUBLIC_ASSISTANT_WORKSPACE_ID!,
|
|
8
|
+
userId: "hackathon",
|
|
9
|
+
});
|
|
10
|
+
const credential = await cloud.auth.tokens.create();
|
|
11
|
+
return Response.json(credential, {
|
|
12
|
+
headers: { "Cache-Control": "no-store" },
|
|
13
|
+
});
|
|
14
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AuiConfig, AuiProvider } from "@assistant-ui/react";
|
|
4
|
+
import { HarnessCloudThreadList } from "@assistant-ui/react-harness-sdk";
|
|
5
|
+
import { useSyncExternalStore } from "react";
|
|
6
|
+
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
|
|
9
|
+
|
|
10
|
+
const credential = async () => {
|
|
11
|
+
const response = await fetch("/api/credential", { cache: "no-store" });
|
|
12
|
+
if (!response.ok) throw new Error(await response.text());
|
|
13
|
+
const { token } = (await response.json()) as { token: string };
|
|
14
|
+
return token;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ShareThread = () => {
|
|
18
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
19
|
+
return (
|
|
20
|
+
<Button
|
|
21
|
+
variant="outline"
|
|
22
|
+
size="sm"
|
|
23
|
+
onClick={() => copyToClipboard(window.location.href)}
|
|
24
|
+
>
|
|
25
|
+
{isCopied ? "Link copied" : "Share this chat"}
|
|
26
|
+
</Button>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ConnectedAssistant = ({ threadId }: { threadId: string }) => {
|
|
31
|
+
const config = AuiConfig({
|
|
32
|
+
threads: HarnessCloudThreadList({
|
|
33
|
+
url: "/api/chat",
|
|
34
|
+
origin: process.env.NEXT_PUBLIC_ASSISTANT_HARNESS_URL!,
|
|
35
|
+
workspaceId: process.env.NEXT_PUBLIC_ASSISTANT_WORKSPACE_ID!,
|
|
36
|
+
credential,
|
|
37
|
+
threadId,
|
|
38
|
+
onThreadIdChange: (threadId) => {
|
|
39
|
+
window.location.hash = threadId ?? crypto.randomUUID();
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
return (
|
|
44
|
+
<AuiProvider config={config}>
|
|
45
|
+
<div className="flex h-dvh flex-col">
|
|
46
|
+
<header className="flex items-center justify-between border-b px-6 py-3">
|
|
47
|
+
<div>
|
|
48
|
+
<h1 className="font-semibold">Shared chat</h1>
|
|
49
|
+
<p className="text-muted-foreground text-sm">
|
|
50
|
+
Open the same link in another browser to chat together.
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
<ShareThread />
|
|
54
|
+
</header>
|
|
55
|
+
<div className="min-h-0 flex-1">
|
|
56
|
+
<Thread />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</AuiProvider>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Assistant = () => {
|
|
64
|
+
const threadId = useSyncExternalStore(
|
|
65
|
+
subscribeToHash,
|
|
66
|
+
currentThreadId,
|
|
67
|
+
() => null,
|
|
68
|
+
);
|
|
69
|
+
if (threadId === null)
|
|
70
|
+
return (
|
|
71
|
+
<p className="p-6" role="status">
|
|
72
|
+
Connecting to your chat…
|
|
73
|
+
</p>
|
|
74
|
+
);
|
|
75
|
+
return <ConnectedAssistant threadId={threadId} />;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const currentThreadId = () => window.location.hash.slice(1) || "main";
|
|
79
|
+
|
|
80
|
+
const subscribeToHash = (onChange: () => void) => {
|
|
81
|
+
window.addEventListener("hashchange", onChange);
|
|
82
|
+
return () => window.removeEventListener("hashchange", onChange);
|
|
83
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "tw-shimmer";
|
|
4
|
+
|
|
5
|
+
@custom-variant data-open (&:where([data-state="open"], [data-open]:not([data-open="false"])));
|
|
6
|
+
@custom-variant data-closed (&:where([data-state="closed"], [data-closed]:not([data-closed="false"])));
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
@keyframes collapsible-down {
|
|
10
|
+
from {
|
|
11
|
+
height: 0;
|
|
12
|
+
}
|
|
13
|
+
to {
|
|
14
|
+
height: var(
|
|
15
|
+
--radix-collapsible-content-height,
|
|
16
|
+
var(--collapsible-panel-height, auto)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
@keyframes collapsible-up {
|
|
21
|
+
from {
|
|
22
|
+
height: var(
|
|
23
|
+
--radix-collapsible-content-height,
|
|
24
|
+
var(--collapsible-panel-height, auto)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
to {
|
|
28
|
+
height: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@custom-variant dark (&:is(.dark *));
|
|
34
|
+
|
|
35
|
+
@theme inline {
|
|
36
|
+
--color-background: var(--background);
|
|
37
|
+
--color-foreground: var(--foreground);
|
|
38
|
+
--font-sans: var(--font-inter);
|
|
39
|
+
--font-mono: var(--font-ibm-plex-mono);
|
|
40
|
+
--color-chart-5: var(--chart-5);
|
|
41
|
+
--color-chart-4: var(--chart-4);
|
|
42
|
+
--color-chart-3: var(--chart-3);
|
|
43
|
+
--color-chart-2: var(--chart-2);
|
|
44
|
+
--color-chart-1: var(--chart-1);
|
|
45
|
+
--color-ring: var(--ring);
|
|
46
|
+
--color-input: var(--input);
|
|
47
|
+
--color-border: var(--border);
|
|
48
|
+
--color-destructive: var(--destructive);
|
|
49
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
50
|
+
--color-accent: var(--accent);
|
|
51
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
52
|
+
--color-muted: var(--muted);
|
|
53
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
54
|
+
--color-secondary: var(--secondary);
|
|
55
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
56
|
+
--color-primary: var(--primary);
|
|
57
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
58
|
+
--color-popover: var(--popover);
|
|
59
|
+
--color-card-foreground: var(--card-foreground);
|
|
60
|
+
--color-card: var(--card);
|
|
61
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
62
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
63
|
+
--radius-lg: var(--radius);
|
|
64
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
65
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
66
|
+
--radius-3xl: 1.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
:root {
|
|
70
|
+
--radius: 0.625rem;
|
|
71
|
+
--background: oklch(1 0 0);
|
|
72
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
73
|
+
--card: oklch(1 0 0);
|
|
74
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
75
|
+
--popover: oklch(1 0 0);
|
|
76
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
77
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
78
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
79
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
80
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
81
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
82
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
83
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
84
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
85
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
86
|
+
--border: oklch(0.92 0.004 286.32);
|
|
87
|
+
--input: oklch(0.92 0.004 286.32);
|
|
88
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
89
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
90
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
91
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
92
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
93
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dark {
|
|
97
|
+
--background: oklch(0.141 0.005 285.823);
|
|
98
|
+
--foreground: oklch(0.985 0 0);
|
|
99
|
+
--card: oklch(0.21 0.006 285.885);
|
|
100
|
+
--card-foreground: oklch(0.985 0 0);
|
|
101
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
102
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
103
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
104
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
105
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
106
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
107
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
108
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
109
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
110
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
111
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
112
|
+
--border: oklch(1 0 0 / 10%);
|
|
113
|
+
--input: oklch(1 0 0 / 15%);
|
|
114
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
115
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
116
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
117
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
118
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
119
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@layer base {
|
|
123
|
+
* {
|
|
124
|
+
@apply border-border;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:root {
|
|
128
|
+
color-scheme: light;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
:root.dark {
|
|
132
|
+
color-scheme: dark;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
body {
|
|
136
|
+
@apply bg-background text-foreground;
|
|
137
|
+
font-optical-sizing: auto;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { IBM_Plex_Mono, Inter } from "next/font/google";
|
|
3
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
|
+
import "./globals.css";
|
|
5
|
+
|
|
6
|
+
const inter = Inter({
|
|
7
|
+
variable: "--font-inter",
|
|
8
|
+
subsets: ["latin"],
|
|
9
|
+
axes: ["opsz"],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const ibmPlexMono = IBM_Plex_Mono({
|
|
13
|
+
variable: "--font-ibm-plex-mono",
|
|
14
|
+
subsets: ["latin"],
|
|
15
|
+
weight: ["400", "500", "600"],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: "Shared cloud chat",
|
|
20
|
+
description: "Generated by create-assistant-ui",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RootLayout = ({
|
|
24
|
+
children,
|
|
25
|
+
}: Readonly<{
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}>) => {
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en" className="dark">
|
|
30
|
+
<body className={`${inter.variable} ${ibmPlexMono.variable} antialiased`}>
|
|
31
|
+
<TooltipProvider>{children}</TooltipProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default RootLayout;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type PropsWithChildren,
|
|
5
|
+
useState,
|
|
6
|
+
type FC,
|
|
7
|
+
isValidElement,
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
XIcon,
|
|
11
|
+
PlusIcon,
|
|
12
|
+
FileText,
|
|
13
|
+
Loader2Icon,
|
|
14
|
+
AlertCircleIcon,
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
import {
|
|
17
|
+
AttachmentPrimitive,
|
|
18
|
+
ComposerPrimitive,
|
|
19
|
+
MessagePrimitive,
|
|
20
|
+
useAuiState,
|
|
21
|
+
useAui,
|
|
22
|
+
} from "@assistant-ui/react";
|
|
23
|
+
import {
|
|
24
|
+
Tooltip,
|
|
25
|
+
TooltipContent,
|
|
26
|
+
TooltipProvider,
|
|
27
|
+
TooltipTrigger,
|
|
28
|
+
} from "@/components/ui/tooltip";
|
|
29
|
+
import {
|
|
30
|
+
Dialog,
|
|
31
|
+
DialogTitle,
|
|
32
|
+
DialogContent,
|
|
33
|
+
DialogTrigger,
|
|
34
|
+
} from "@/components/ui/dialog";
|
|
35
|
+
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
|
36
|
+
import { TooltipIconButton } from "@/components/assistant-ui/elements/tooltip-icon-button";
|
|
37
|
+
import { useAttachmentSrc } from "@/hooks/use-attachment-src";
|
|
38
|
+
import { cn } from "@/lib/utils";
|
|
39
|
+
|
|
40
|
+
type AttachmentPreviewProps = {
|
|
41
|
+
src: string;
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src, name }) => {
|
|
46
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
47
|
+
return (
|
|
48
|
+
<img
|
|
49
|
+
src={src}
|
|
50
|
+
alt={`Preview of ${name}`}
|
|
51
|
+
className={cn(
|
|
52
|
+
"block h-auto max-h-[80vh] w-auto max-w-full rounded-sm object-contain transition-opacity duration-300 motion-reduce:transition-none",
|
|
53
|
+
isLoaded
|
|
54
|
+
? "aui-attachment-preview-image-loaded opacity-100"
|
|
55
|
+
: "aui-attachment-preview-image-loading opacity-0",
|
|
56
|
+
)}
|
|
57
|
+
onLoad={() => setIsLoaded(true)}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type AttachmentPreviewDialogProps = PropsWithChildren<{
|
|
63
|
+
src: string | undefined;
|
|
64
|
+
name: string;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
const AttachmentPreviewDialog: FC<AttachmentPreviewDialogProps> = ({
|
|
68
|
+
children,
|
|
69
|
+
src,
|
|
70
|
+
name,
|
|
71
|
+
}) => {
|
|
72
|
+
if (!src) return children;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Dialog>
|
|
76
|
+
<DialogTrigger
|
|
77
|
+
nativeButton={false}
|
|
78
|
+
className="aui-attachment-preview-trigger cursor-zoom-in"
|
|
79
|
+
render={
|
|
80
|
+
isValidElement(children) ? (
|
|
81
|
+
children
|
|
82
|
+
) : (
|
|
83
|
+
<button type="button">{children}</button>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
87
|
+
<DialogContent className="aui-attachment-preview-dialog-content [&>button]:bg-foreground/60 [&>button]:hover:bg-foreground/80 [&_svg]:text-background p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0!">
|
|
88
|
+
<DialogTitle className="aui-sr-only sr-only">
|
|
89
|
+
Preview {name}
|
|
90
|
+
</DialogTitle>
|
|
91
|
+
<div className="aui-attachment-preview bg-background relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden rounded-sm">
|
|
92
|
+
<AttachmentPreview src={src} name={name} />
|
|
93
|
+
</div>
|
|
94
|
+
</DialogContent>
|
|
95
|
+
</Dialog>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const AttachmentThumb: FC<{ src: string | undefined }> = ({ src }) => {
|
|
100
|
+
return (
|
|
101
|
+
<Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none after:hidden">
|
|
102
|
+
<AvatarImage
|
|
103
|
+
src={src}
|
|
104
|
+
alt=""
|
|
105
|
+
className="aui-attachment-tile-image rounded-none object-cover"
|
|
106
|
+
/>
|
|
107
|
+
<AvatarFallback>
|
|
108
|
+
<FileText className="aui-attachment-tile-fallback-icon text-muted-foreground/80 size-6 stroke-[1.5]" />
|
|
109
|
+
</AvatarFallback>
|
|
110
|
+
</Avatar>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const AttachmentUI: FC = () => {
|
|
115
|
+
const aui = useAui();
|
|
116
|
+
const isComposer = aui.attachment.source !== "message";
|
|
117
|
+
const src = useAttachmentSrc();
|
|
118
|
+
|
|
119
|
+
const isImage = useAuiState((s) => s.attachment.type === "image");
|
|
120
|
+
const name = useAuiState((s) => s.attachment.name);
|
|
121
|
+
const typeLabel = useAuiState((s) => {
|
|
122
|
+
const type = s.attachment.type;
|
|
123
|
+
switch (type) {
|
|
124
|
+
case "image":
|
|
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
|
+
);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<TooltipProvider>
|
|
159
|
+
<Tooltip>
|
|
160
|
+
<AttachmentPrimitive.Root
|
|
161
|
+
className={cn(
|
|
162
|
+
"aui-attachment-root relative",
|
|
163
|
+
isComposer &&
|
|
164
|
+
"animate-in fade-in-0 zoom-in-95 duration-200 motion-reduce:animate-none",
|
|
165
|
+
isImage &&
|
|
166
|
+
!isComposer &&
|
|
167
|
+
"aui-attachment-root-message only:*:first:size-24",
|
|
168
|
+
)}
|
|
169
|
+
>
|
|
170
|
+
<AttachmentPreviewDialog src={src} name={name}>
|
|
171
|
+
<TooltipTrigger
|
|
172
|
+
render={
|
|
173
|
+
<div
|
|
174
|
+
className={cn(
|
|
175
|
+
"aui-attachment-tile bg-muted hover:after:bg-foreground/10 focus-visible:ring-ring/50 relative size-14 overflow-hidden rounded-[calc(var(--composer-radius,1rem)-var(--composer-padding,8px))] transition-transform outline-none after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-black/10 after:transition-colors after:ring-inset focus-visible:ring-1 motion-reduce:transition-none dark:after:ring-white/10",
|
|
176
|
+
src
|
|
177
|
+
? "cursor-zoom-in active:scale-[0.96]"
|
|
178
|
+
: "cursor-default",
|
|
179
|
+
isError &&
|
|
180
|
+
"after:ring-destructive/60 dark:after:ring-destructive/60",
|
|
181
|
+
)}
|
|
182
|
+
role={src ? "button" : "group"}
|
|
183
|
+
tabIndex={0}
|
|
184
|
+
aria-label={`${src ? `Preview ${name}` : `${typeLabel} attachment ${name}`}${
|
|
185
|
+
isError
|
|
186
|
+
? ", upload failed"
|
|
187
|
+
: isUploading
|
|
188
|
+
? ", uploading"
|
|
189
|
+
: ""
|
|
190
|
+
}`}
|
|
191
|
+
/>
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
<AttachmentThumb src={src} />
|
|
195
|
+
{isUploading && (
|
|
196
|
+
<div
|
|
197
|
+
aria-hidden="true"
|
|
198
|
+
className="aui-attachment-tile-uploading bg-background/60 animate-in fade-in-0 absolute inset-0 flex items-center justify-center backdrop-blur-[2px] motion-reduce:animate-none"
|
|
199
|
+
>
|
|
200
|
+
<Loader2Icon className="text-muted-foreground size-4 animate-spin" />
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
{isError && (
|
|
204
|
+
<div
|
|
205
|
+
aria-hidden="true"
|
|
206
|
+
className="aui-attachment-tile-error bg-background/70 animate-in fade-in-0 absolute inset-0 flex items-center justify-center backdrop-blur-[2px] motion-reduce:animate-none"
|
|
207
|
+
>
|
|
208
|
+
<AlertCircleIcon className="text-destructive size-4" />
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</TooltipTrigger>
|
|
212
|
+
</AttachmentPreviewDialog>
|
|
213
|
+
{isComposer && <AttachmentRemove name={name} />}
|
|
214
|
+
</AttachmentPrimitive.Root>
|
|
215
|
+
<TooltipContent side="top">
|
|
216
|
+
<AttachmentPrimitive.Name />
|
|
217
|
+
{errorMessage && (
|
|
218
|
+
<p className="aui-attachment-error-message">{errorMessage}</p>
|
|
219
|
+
)}
|
|
220
|
+
</TooltipContent>
|
|
221
|
+
</Tooltip>
|
|
222
|
+
</TooltipProvider>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const AttachmentRemove: FC<{ name: string }> = ({ name }) => {
|
|
227
|
+
return (
|
|
228
|
+
<AttachmentPrimitive.Remove
|
|
229
|
+
render={
|
|
230
|
+
<TooltipIconButton
|
|
231
|
+
tooltip={`Remove ${name}`}
|
|
232
|
+
className="aui-attachment-tile-remove absolute end-1 top-1 size-5 rounded-full bg-black/50! text-white after:absolute after:-inset-1.5 hover:bg-black/70! hover:text-white! active:scale-[0.96] motion-reduce:transition-none"
|
|
233
|
+
side="top"
|
|
234
|
+
/>
|
|
235
|
+
}
|
|
236
|
+
>
|
|
237
|
+
<XIcon className="aui-attachment-remove-icon size-3 stroke-[2.5]" />
|
|
238
|
+
</AttachmentPrimitive.Remove>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const UserMessageAttachments: FC = () => {
|
|
243
|
+
return (
|
|
244
|
+
<div className="aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2 empty:hidden">
|
|
245
|
+
<MessagePrimitive.Attachments>
|
|
246
|
+
{() => <AttachmentUI />}
|
|
247
|
+
</MessagePrimitive.Attachments>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const ComposerAttachments: FC = () => {
|
|
253
|
+
return (
|
|
254
|
+
<div className="aui-composer-attachments flex w-full flex-row items-center gap-2 overflow-x-auto empty:hidden">
|
|
255
|
+
<ComposerPrimitive.Attachments>
|
|
256
|
+
{() => <AttachmentUI />}
|
|
257
|
+
</ComposerPrimitive.Attachments>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const ComposerAddAttachment: FC = () => {
|
|
263
|
+
return (
|
|
264
|
+
<ComposerPrimitive.AddAttachment
|
|
265
|
+
render={
|
|
266
|
+
<TooltipIconButton
|
|
267
|
+
tooltip="Add Attachment"
|
|
268
|
+
side="bottom"
|
|
269
|
+
variant="ghost"
|
|
270
|
+
size="icon"
|
|
271
|
+
className="aui-composer-add-attachment text-muted-foreground hover:text-foreground hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-7 rounded-full active:scale-[0.96] motion-reduce:transition-none"
|
|
272
|
+
aria-label="Add Attachment"
|
|
273
|
+
/>
|
|
274
|
+
}
|
|
275
|
+
>
|
|
276
|
+
<PlusIcon className="aui-attachment-add-icon size-4" />
|
|
277
|
+
</ComposerPrimitive.AddAttachment>
|
|
278
|
+
);
|
|
279
|
+
};
|