hadadrjt commited on
Commit
091295b
·
1 Parent(s): 8270985

ai: Migrate to assistant-ui.

Browse files

* Patched for Hugging Face Spaces deployment support

* Patched for Demo Playground with proper multimodal support

https://huggingface.co/spaces/umint/ai/blob/main/CREDITS

Signed-off-by: Hadad <hadad@linuxmail.org>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +0 -0
  2. .gitignore +41 -0
  3. COPYING +7 -0
  4. CREDITS +5 -0
  5. Dockerfile +6 -4
  6. LICENSE +22 -0
  7. README.md +23 -3
  8. app/api/chat/route.ts +26 -0
  9. app/assistant.tsx +85 -0
  10. {assets/images → app}/favicon.ico +0 -0
  11. app/globals.css +131 -0
  12. app/layout.tsx +38 -0
  13. app/page.tsx +5 -0
  14. assets/css/animation/style.css +0 -1628
  15. assets/css/button.css +0 -65
  16. assets/css/chat/bubble.css +0 -56
  17. assets/css/chat/markdown.css +0 -179
  18. assets/css/chat/style.css +0 -58
  19. assets/css/footer.css +0 -21
  20. assets/css/header.css +0 -30
  21. assets/css/icon.css +0 -83
  22. assets/css/input.css +0 -41
  23. assets/css/logo.css +0 -20
  24. assets/css/prompts.css +0 -46
  25. assets/css/screen/1200.css +0 -15
  26. assets/css/screen/2000.css +0 -50
  27. assets/css/screen/320.css +0 -82
  28. assets/css/screen/360.css +0 -82
  29. assets/css/screen/3840.css +0 -51
  30. assets/css/screen/480.css +0 -78
  31. assets/css/screen/720.css +0 -88
  32. assets/css/screen/7680.css +0 -51
  33. assets/css/screen/common.css +0 -66
  34. assets/css/style.css +0 -94
  35. assets/css/webkit.css +0 -27
  36. assets/plugins/loader.js +0 -315
  37. components.json +21 -0
  38. components/assistant-ui/attachment.tsx +266 -0
  39. components/assistant-ui/markdown-text.tsx +232 -0
  40. components/assistant-ui/thread-list.tsx +108 -0
  41. components/assistant-ui/thread.tsx +387 -0
  42. components/assistant-ui/threadlist-sidebar.tsx +94 -0
  43. components/assistant-ui/tool-fallback.tsx +46 -0
  44. components/assistant-ui/tooltip-icon-button.tsx +42 -0
  45. components/ui/avatar.tsx +53 -0
  46. components/ui/breadcrumb.tsx +109 -0
  47. components/ui/button.tsx +59 -0
  48. components/ui/dialog.tsx +143 -0
  49. components/ui/input.tsx +21 -0
  50. components/ui/separator.tsx +28 -0
.gitattributes CHANGED
File without changes
.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
COPYING ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ The Demo Playground project is provided under the terms of the MIT License, as stated in:
2
+ LICENSE
3
+
4
+ This project includes and modifies components from assistant-ui (https://github.com/assistant-ui/assistant-ui),
5
+ which are also licensed under the MIT License.
6
+
7
+ All contributions to the Demo Playground project are subject to this COPYING file.
CREDITS ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ CREDITS FOR DEMO PLAYGROUND
2
+
3
+ THIS PROJECT IS BASED ON ASSISTANT-UI (https://github.com/assistant-ui/assistant-ui), WITH EXTENSIVE MODIFICATIONS TO ENHANCE STABILITY, ENSURE COMPATIBILITY WITH HUGGING FACE SPACES, AND ADAPT IT FOR USE IN THE DEMO PLAYGROUND ENVIRONMENT.
4
+
5
+ COPYRIGHT FOR THE ORIGINAL WORK BELONGS ENTIRELY TO THE ASSISTANT-UI PROJECT AND ITS CONTRIBUTORS.
Dockerfile CHANGED
@@ -1,22 +1,24 @@
1
  #
2
  # SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- # SPDX-License-Identifier: Apache-2.0
4
  #
5
 
6
  # Use a specific container image for the sever
7
  FROM hadadrjt/playground:public-latest
8
 
9
  # Set the main working directory inside the container
10
- WORKDIR /node
11
 
12
  # Copy all files into the container
13
  COPY . .
14
 
15
  # Install all dependencies
16
- RUN npm install
 
 
17
 
18
  # Open the port so the web can be accessed
19
- EXPOSE 7860
20
 
21
  # Start the server
22
  CMD ["npm", "start"]
 
1
  #
2
  # SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ # SPDX-License-Identifier: MIT
4
  #
5
 
6
  # Use a specific container image for the sever
7
  FROM hadadrjt/playground:public-latest
8
 
9
  # Set the main working directory inside the container
10
+ WORKDIR /app
11
 
12
  # Copy all files into the container
13
  COPY . .
14
 
15
  # Install all dependencies
16
+ RUN npx assistant-ui@latest add syntax-highlighter \
17
+ && npm install \
18
+ && npm run build
19
 
20
  # Open the port so the web can be accessed
21
+ EXPOSE 3000
22
 
23
  # Start the server
24
  CMD ["npm", "start"]
LICENSE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AgentbaseAI Inc.
4
+ Copyright (c) 2025 Hadad <hadad@linuxmail.org>
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,14 @@
1
  ---
2
  title: Demo Playground
3
- license: apache-2.0
 
 
4
  emoji: ⚡
5
  colorFrom: purple
6
  colorTo: green
7
  sdk: docker
8
- app_port: 7860
9
- pinned: false
10
  thumbnail: >-
11
  https://cdn-uploads.huggingface.co/production/uploads/686e28b405d4ddcdd96adeb2/i9iufR3L-rgj39mk_B9QW.jpeg
12
  # Used to promote this Hugging Face Space
@@ -85,7 +87,25 @@ models:
85
  - Wan-AI/Wan2.2-Animate-14B
86
  - decart-ai/Lucy-Edit-Dev
87
  - ibm-granite/granite-docling-258M
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # Used to promote this Hugging Face Space
89
  datasets:
90
  - fka/awesome-chatgpt-prompts
 
91
  ---
 
1
  ---
2
  title: Demo Playground
3
+ short_description: The first journey begins here
4
+ license: mit
5
+ license_link: https://huggingface.co/spaces/umint/ai/blob/main/LICENSE
6
  emoji: ⚡
7
  colorFrom: purple
8
  colorTo: green
9
  sdk: docker
10
+ app_port: 3000
11
+ pinned: true
12
  thumbnail: >-
13
  https://cdn-uploads.huggingface.co/production/uploads/686e28b405d4ddcdd96adeb2/i9iufR3L-rgj39mk_B9QW.jpeg
14
  # Used to promote this Hugging Face Space
 
87
  - Wan-AI/Wan2.2-Animate-14B
88
  - decart-ai/Lucy-Edit-Dev
89
  - ibm-granite/granite-docling-258M
90
+ - Qwen/Qwen3-Omni-30B-A3B-Instruct
91
+ - Qwen/Qwen-Image-Edit-2509
92
+ - deepseek-ai/DeepSeek-V3.1-Terminus
93
+ - Qwen/Qwen3-VL-235B-A22B-Thinking
94
+ - Qwen/Qwen3-VL-235B-A22B-Instruct
95
+ - Qwen/Qwen3-Omni-30B-A3B-Thinking
96
+ - deepseek-ai/DeepSeek-V3.2-Exp
97
+ - inclusionAI/Ring-1T-preview
98
+ - zai-org/GLM-4.6
99
+ - neuphonic/neutts-air
100
+ - ibm-granite/granite-4.0-h-small
101
+ - ServiceNow-AI/Apriel-1.5-15b-Thinker
102
+ - Qwen/Qwen3-VL-30B-A3B-Instruct
103
+ - ibm-granite/granite-4.0-micro
104
+ - chetwinlow1/Ovi
105
+ - inclusionAI/Ling-1T
106
+ - microsoft/UserLM-8b
107
  # Used to promote this Hugging Face Space
108
  datasets:
109
  - fka/awesome-chatgpt-prompts
110
+ - openai/gdpval
111
  ---
app/api/chat/route.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+
6
+ import { createOpenAI } from "@ai-sdk/openai";
7
+ import { streamText, convertToModelMessages } from "ai";
8
+ import config from '../../../config';
9
+
10
+ export async function POST(req: Request) {
11
+ if (!config.api.baseUrl || !config.api.key) {
12
+ throw new Error("API configuration missing");
13
+ }
14
+
15
+ const { messages } = await req.json();
16
+
17
+ const result = streamText({
18
+ model: createOpenAI({
19
+ baseURL: config.api.baseUrl,
20
+ apiKey: config.api.key
21
+ }).chat(config.api.model),
22
+ messages: convertToModelMessages(messages),
23
+ });
24
+
25
+ return result.toUIMessageStreamResponse();
26
+ }
app/assistant.tsx ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+
6
+ "use client";
7
+
8
+ import { AssistantRuntimeProvider } from "@assistant-ui/react";
9
+ import {
10
+ useChatRuntime,
11
+ AssistantChatTransport,
12
+ } from "@assistant-ui/react-ai-sdk";
13
+ import { Thread } from "@/components/assistant-ui/thread";
14
+ import {
15
+ SidebarInset,
16
+ SidebarProvider,
17
+ SidebarTrigger,
18
+ } from "@/components/ui/sidebar";
19
+ import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar";
20
+ import { Separator } from "@/components/ui/separator";
21
+ import {
22
+ Breadcrumb,
23
+ BreadcrumbItem,
24
+ BreadcrumbList,
25
+ BreadcrumbPage,
26
+ } from "@/components/ui/breadcrumb";
27
+
28
+ export const Assistant = () => {
29
+ const runtime = useChatRuntime({
30
+ transport: new AssistantChatTransport({
31
+ api: "/api/chat",
32
+ }),
33
+ });
34
+
35
+ return (
36
+ <AssistantRuntimeProvider runtime={runtime}>
37
+ <SidebarProvider>
38
+ <div className="flex h-dvh w-full pr-0.5">
39
+ <ThreadListSidebar />
40
+ <SidebarInset>
41
+ <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
42
+ <SidebarTrigger />
43
+ <Separator orientation="vertical" className="mr-2 h-4" />
44
+ <Breadcrumb>
45
+ <BreadcrumbList>
46
+ <BreadcrumbItem>
47
+ <BreadcrumbPage>
48
+ <a
49
+ href="https://huggingface.co/spaces/umint/ai/discussions"
50
+ target="_blank"
51
+ rel="noopener noreferrer"
52
+ className="inline-flex items-center gap-1"
53
+ >
54
+ Changelogs
55
+ <svg
56
+ width="7.5"
57
+ height="7.5"
58
+ viewBox="0 0 12 12"
59
+ fill="none"
60
+ xmlns="http://www.w3.org/2000/svg"
61
+ className="inline-block"
62
+ >
63
+ <path
64
+ d="M3.5 1.5L8.5 1.5L8.5 6.5M8.5 1.5L1.5 8.5L8.5 1.5Z"
65
+ stroke="currentColor"
66
+ strokeWidth="1.5"
67
+ strokeLinecap="round"
68
+ strokeLinejoin="round"
69
+ />
70
+ </svg>
71
+ </a>
72
+ </BreadcrumbPage>
73
+ </BreadcrumbItem>
74
+ </BreadcrumbList>
75
+ </Breadcrumb>
76
+ </header>
77
+ <div className="flex-1 overflow-hidden">
78
+ <Thread />
79
+ </div>
80
+ </SidebarInset>
81
+ </div>
82
+ </SidebarProvider>
83
+ </AssistantRuntimeProvider>
84
+ );
85
+ };
{assets/images → app}/favicon.ico RENAMED
File without changes
app/globals.css ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-geist-sans);
10
+ --font-mono: var(--font-geist-mono);
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --radius-sm: calc(var(--radius) - 4px);
41
+ --radius-md: calc(var(--radius) - 2px);
42
+ --radius-lg: var(--radius);
43
+ --radius-xl: calc(var(--radius) + 4px);
44
+ }
45
+
46
+ :root {
47
+ --radius: 0.625rem;
48
+ --background: oklch(1 0 0);
49
+ --foreground: oklch(0.141 0.005 285.823);
50
+ --card: oklch(1 0 0);
51
+ --card-foreground: oklch(0.141 0.005 285.823);
52
+ --popover: oklch(1 0 0);
53
+ --popover-foreground: oklch(0.141 0.005 285.823);
54
+ --primary: oklch(0.21 0.006 285.885);
55
+ --primary-foreground: oklch(0.985 0 0);
56
+ --secondary: oklch(0.967 0.001 286.375);
57
+ --secondary-foreground: oklch(0.21 0.006 285.885);
58
+ --muted: oklch(0.967 0.001 286.375);
59
+ --muted-foreground: oklch(0.552 0.016 285.938);
60
+ --accent: oklch(0.967 0.001 286.375);
61
+ --accent-foreground: oklch(0.21 0.006 285.885);
62
+ --destructive: oklch(0.577 0.245 27.325);
63
+ --border: oklch(0.92 0.004 286.32);
64
+ --input: oklch(0.92 0.004 286.32);
65
+ --ring: oklch(0.705 0.015 286.067);
66
+ --chart-1: oklch(0.646 0.222 41.116);
67
+ --chart-2: oklch(0.6 0.118 184.704);
68
+ --chart-3: oklch(0.398 0.07 227.392);
69
+ --chart-4: oklch(0.828 0.189 84.429);
70
+ --chart-5: oklch(0.769 0.188 70.08);
71
+ --sidebar: oklch(0.985 0 0);
72
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
73
+ --sidebar-primary: oklch(0.21 0.006 285.885);
74
+ --sidebar-primary-foreground: oklch(0.985 0 0);
75
+ --sidebar-accent: oklch(0.967 0.001 286.375);
76
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
77
+ --sidebar-border: oklch(0.92 0.004 286.32);
78
+ --sidebar-ring: oklch(0.705 0.015 286.067);
79
+ }
80
+
81
+ .dark {
82
+ --background: oklch(0.141 0.005 285.823);
83
+ --foreground: oklch(0.985 0 0);
84
+ --card: oklch(0.21 0.006 285.885);
85
+ --card-foreground: oklch(0.985 0 0);
86
+ --popover: oklch(0.21 0.006 285.885);
87
+ --popover-foreground: oklch(0.985 0 0);
88
+ --primary: oklch(0.92 0.004 286.32);
89
+ --primary-foreground: oklch(0.21 0.006 285.885);
90
+ --secondary: oklch(0.274 0.006 286.033);
91
+ --secondary-foreground: oklch(0.985 0 0);
92
+ --muted: oklch(0.274 0.006 286.033);
93
+ --muted-foreground: oklch(0.705 0.015 286.067);
94
+ --accent: oklch(0.274 0.006 286.033);
95
+ --accent-foreground: oklch(0.985 0 0);
96
+ --destructive: oklch(0.704 0.191 22.216);
97
+ --border: oklch(1 0 0 / 10%);
98
+ --input: oklch(1 0 0 / 15%);
99
+ --ring: oklch(0.552 0.016 285.938);
100
+ --chart-1: oklch(0.488 0.243 264.376);
101
+ --chart-2: oklch(0.696 0.17 162.48);
102
+ --chart-3: oklch(0.769 0.188 70.08);
103
+ --chart-4: oklch(0.627 0.265 303.9);
104
+ --chart-5: oklch(0.645 0.246 16.439);
105
+ --sidebar: oklch(0.21 0.006 285.885);
106
+ --sidebar-foreground: oklch(0.985 0 0);
107
+ --sidebar-primary: oklch(0.488 0.243 264.376);
108
+ --sidebar-primary-foreground: oklch(0.985 0 0);
109
+ --sidebar-accent: oklch(0.274 0.006 286.033);
110
+ --sidebar-accent-foreground: oklch(0.985 0 0);
111
+ --sidebar-border: oklch(1 0 0 / 10%);
112
+ --sidebar-ring: oklch(0.552 0.016 285.938);
113
+ }
114
+
115
+ @layer base {
116
+ * {
117
+ @apply border-border outline-ring/50;
118
+ }
119
+
120
+ :root {
121
+ color-scheme: light;
122
+ }
123
+
124
+ :root.dark {
125
+ color-scheme: dark;
126
+ }
127
+
128
+ body {
129
+ @apply bg-background text-foreground;
130
+ }
131
+ }
app/layout.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const geistSans = Geist({
6
+ variable: "--font-geist-sans",
7
+ subsets: ["latin"],
8
+ });
9
+
10
+ const geistMono = Geist_Mono({
11
+ variable: "--font-geist-mono",
12
+ subsets: ["latin"],
13
+ });
14
+
15
+ export const metadata: Metadata = {
16
+ title: "UltimaX Intelligence",
17
+ description:
18
+ "UltimaX Intelligence is a free AI platform unifying multiple premium AI models " +
19
+ "with an intuitive ChatGPT-like interface. Powered by Pollinations open-source AI " +
20
+ "and integrated with OpenWebUI, it offers advanced features, no cost, no registration, " +
21
+ "and ensures user privacy with temporary, unsaved conversations.",
22
+ };
23
+
24
+ export default function RootLayout({
25
+ children,
26
+ }: Readonly<{
27
+ children: React.ReactNode;
28
+ }>) {
29
+ return (
30
+ <html lang="en">
31
+ <body
32
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
33
+ >
34
+ {children}
35
+ </body>
36
+ </html>
37
+ );
38
+ }
app/page.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { Assistant } from "./assistant";
2
+
3
+ export default function Home() {
4
+ return <Assistant />;
5
+ }
assets/css/animation/style.css DELETED
@@ -1,1628 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .loading {
7
- display: inline-block;
8
- width: 1.1rem;
9
- height: 1.1rem;
10
- border: 2px solid #999;
11
- border-top-color: transparent;
12
- border-radius: 50%;
13
- animation: spin 1s linear infinite;
14
- }
15
-
16
- @keyframes spin {
17
- to {
18
- transform: rotate(360deg);
19
- }
20
- }
21
-
22
- @keyframes fadeIn {
23
- to {
24
- opacity: 1;
25
- }
26
- }
27
-
28
- @keyframes fadeInDown {
29
- from {
30
- opacity: 0;
31
- transform: translateY(-20px);
32
- }
33
- to {
34
- opacity: 1;
35
- transform: translateY(0);
36
- }
37
- }
38
-
39
- @keyframes slideInUp {
40
- from {
41
- opacity: 0;
42
- transform: translateY(20px);
43
- }
44
- to {
45
- opacity: 1;
46
- transform: translateY(0);
47
- }
48
- }
49
-
50
- .pulse-animation {
51
- animation: pulse 2s infinite;
52
- }
53
-
54
- @keyframes pulse {
55
- 0% {
56
- box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
57
- }
58
- 70% {
59
- box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
60
- }
61
- 100% {
62
- box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
63
- }
64
- }
65
-
66
- .floating-animation {
67
- animation: floating 3s ease-in-out infinite;
68
- }
69
-
70
- @keyframes floating {
71
- 0% {
72
- transform: translateY(0px);
73
- }
74
- 50% {
75
- transform: translateY(-10px);
76
- }
77
- 100% {
78
- transform: translateY(0px);
79
- }
80
- }
81
-
82
- .gradient-text {
83
- background: linear-gradient(90deg, #3b82f6, #8b5cf6, #ec4899);
84
- -webkit-background-clip: text;
85
- -webkit-text-fill-color: transparent;
86
- background-clip: text;
87
- }
88
-
89
- .typing-animation::after {
90
- content: "|";
91
- animation: blink 1s infinite;
92
- margin-left: 2px;
93
- }
94
-
95
- @keyframes blink {
96
- 0%, 100% { opacity: 1; }
97
- 50% { opacity: 0; }
98
- }
99
-
100
- .shine-effect {
101
- position: relative;
102
- overflow: hidden;
103
- }
104
-
105
- .shine-effect::before {
106
- content: "";
107
- position: absolute;
108
- top: 0;
109
- left: -100%;
110
- width: 100%;
111
- height: 100%;
112
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
113
- transition: 0.5s;
114
- }
115
-
116
- .shine-effect:hover::before {
117
- left: 100%;
118
- }
119
-
120
- .bounce-in {
121
- animation: bounceIn 0.6s ease-out;
122
- }
123
-
124
- @keyframes bounceIn {
125
- 0% {
126
- transform: scale(0.8);
127
- opacity: 0;
128
- }
129
- 50% {
130
- transform: scale(1.05);
131
- }
132
- 100% {
133
- transform: scale(1);
134
- opacity: 1;
135
- }
136
- }
137
-
138
- .slide-in-left {
139
- animation: slideInLeft 0.5s ease-out;
140
- }
141
-
142
- @keyframes slideInLeft {
143
- from {
144
- transform: translateX(-100%);
145
- opacity: 0;
146
- }
147
- to {
148
- transform: translateX(0);
149
- opacity: 1;
150
- }
151
- }
152
-
153
- .slide-in-right {
154
- animation: slideInRight 0.5s ease-out;
155
- }
156
-
157
- @keyframes slideInRight {
158
- from {
159
- transform: translateX(100%);
160
- opacity: 0;
161
- }
162
- to {
163
- transform: translateX(0);
164
- opacity: 1;
165
- }
166
- }
167
-
168
- .zoom-in {
169
- animation: zoomIn 0.4s ease-out;
170
- }
171
-
172
- @keyframes zoomIn {
173
- from {
174
- transform: scale(0.8);
175
- opacity: 0;
176
- }
177
- to {
178
- transform: scale(1);
179
- opacity: 1;
180
- }
181
- }
182
-
183
- .flip-in {
184
- animation: flipIn 0.6s ease-out;
185
- }
186
-
187
- @keyframes flipIn {
188
- from {
189
- transform: rotateY(90deg);
190
- opacity: 0;
191
- }
192
- to {
193
- transform: rotateY(0deg);
194
- opacity: 1;
195
- }
196
- }
197
-
198
- .fade-in-up {
199
- animation: fadeInUp 0.5s ease-out;
200
- }
201
-
202
- @keyframes fadeInUp {
203
- from {
204
- transform: translateY(20px);
205
- opacity: 0;
206
- }
207
- to {
208
- transform: translateY(0);
209
- opacity: 1;
210
- }
211
- }
212
-
213
- .fade-in-down {
214
- animation: fadeInDown 0.5s ease-out;
215
- }
216
-
217
- @keyframes fadeInDown {
218
- from {
219
- transform: translateY(-20px);
220
- opacity: 0;
221
- }
222
- to {
223
- transform: translateY(0);
224
- opacity: 1;
225
- }
226
- }
227
-
228
- .rotate-in {
229
- animation: rotateIn 0.6s ease-out;
230
- }
231
-
232
- @keyframes rotateIn {
233
- from {
234
- transform: rotate(15deg);
235
- opacity: 0;
236
- }
237
- to {
238
- transform: rotate(0deg);
239
- opacity: 1;
240
- }
241
- }
242
-
243
- .scale-pulse {
244
- animation: scalePulse 1.5s infinite;
245
- }
246
-
247
- @keyframes scalePulse {
248
- 0%, 100% {
249
- transform: scale(1);
250
- }
251
- 50% {
252
- transform: scale(1.05);
253
- }
254
- }
255
-
256
- .wave-animation {
257
- animation: wave 2s infinite linear;
258
- }
259
-
260
- @keyframes wave {
261
- 0% {
262
- background-position: 0 0;
263
- }
264
- 100% {
265
- background-position: 100% 0;
266
- }
267
- }
268
-
269
- .glow-effect {
270
- box-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
271
- transition: box-shadow 0.3s ease;
272
- }
273
-
274
- .glow-effect:hover {
275
- box-shadow: 0 0 20px rgba(59, 130, 246, 0.8);
276
- }
277
-
278
- .neon-glow {
279
- text-shadow: 0 0 5px #3b82f6, 0 0 10px #3b82f6, 0 0 15px #3b82f6, 0 0 20px #3b82f6;
280
- animation: neonPulse 2s infinite alternate;
281
- }
282
-
283
- @keyframes neonPulse {
284
- from {
285
- text-shadow: 0 0 5px #3b82f6, 0 0 10px #3b82f6, 0 0 15px #3b82f6, 0 0 20px #3b82f6;
286
- }
287
- to {
288
- text-shadow: 0 0 10px #3b82f6, 0 0 20px #3b82f6, 0 0 30px #3b82f6, 0 0 40px #3b82f6;
289
- }
290
- }
291
-
292
- .rainbow-text {
293
- background: linear-gradient(90deg, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080);
294
- background-size: 200% 200%;
295
- -webkit-background-clip: text;
296
- -webkit-text-fill-color: transparent;
297
- background-clip: text;
298
- animation: rainbow 3s linear infinite;
299
- }
300
-
301
- @keyframes rainbow {
302
- 0% {
303
- background-position: 0% 50%;
304
- }
305
- 100% {
306
- background-position: 200% 50%;
307
- }
308
- }
309
-
310
- .hologram-effect {
311
- position: relative;
312
- overflow: hidden;
313
- }
314
-
315
- .hologram-effect::before {
316
- content: "";
317
- position: absolute;
318
- top: 0;
319
- left: 0;
320
- width: 100%;
321
- height: 100%;
322
- background: linear-gradient(45deg, rgba(255,255,255,0.1) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0.1) 75%, transparent 75%, transparent);
323
- background-size: 20px 20px;
324
- animation: hologram 1s linear infinite;
325
- pointer-events: none;
326
- }
327
-
328
- @keyframes hologram {
329
- 0% {
330
- background-position: 0 0;
331
- }
332
- 100% {
333
- background-position: 20px 20px;
334
- }
335
- }
336
-
337
- .matrix-effect {
338
- position: relative;
339
- overflow: hidden;
340
- }
341
-
342
- .matrix-effect::before {
343
- content: "";
344
- position: absolute;
345
- top: 0;
346
- left: 0;
347
- width: 100%;
348
- height: 100%;
349
- background: linear-gradient(rgba(0, 255, 0, 0.1) 50%, transparent 50%);
350
- background-size: 100% 4px;
351
- animation: matrix 0.5s linear infinite;
352
- pointer-events: none;
353
- }
354
-
355
- @keyframes matrix {
356
- 0% {
357
- background-position: 0 0;
358
- }
359
- 100% {
360
- background-position: 0 4px;
361
- }
362
- }
363
-
364
- .glass-effect {
365
- background: rgba(255, 255, 255, 0.05);
366
- backdrop-filter: blur(10px);
367
- -webkit-backdrop-filter: blur(10px);
368
- border: 1px solid rgba(255, 255, 255, 0.1);
369
- }
370
-
371
- .card-hover {
372
- transition: all 0.3s ease;
373
- transform: translateY(0);
374
- }
375
-
376
- .card-hover:hover {
377
- transform: translateY(-5px);
378
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
379
- }
380
-
381
- .text-glow {
382
- text-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
383
- transition: text-shadow 0.3s ease;
384
- }
385
-
386
- .text-glow:hover {
387
- text-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
388
- }
389
-
390
- .border-glow {
391
- border: 1px solid rgba(255, 255, 255, 0.1);
392
- transition: border-color 0.3s ease;
393
- }
394
-
395
- .border-glow:hover {
396
- border-color: rgba(59, 130, 246, 0.5);
397
- }
398
-
399
- .shadow-glow {
400
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
401
- transition: box-shadow 0.3s ease;
402
- }
403
-
404
- .shadow-glow:hover {
405
- box-shadow: 0 8px 15px rgba(59, 130, 246, 0.3);
406
- }
407
-
408
- .pulse-glow {
409
- animation: pulseGlow 2s infinite;
410
- }
411
-
412
- @keyframes pulseGlow {
413
- 0% {
414
- box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
415
- }
416
- 70% {
417
- box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
418
- }
419
- 100% {
420
- box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
421
- }
422
- }
423
-
424
- .floating-glow {
425
- animation: floatingGlow 3s ease-in-out infinite;
426
- }
427
-
428
- @keyframes floatingGlow {
429
- 0% {
430
- transform: translateY(0);
431
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
432
- }
433
- 50% {
434
- transform: translateY(-10px);
435
- box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
436
- }
437
- 100% {
438
- transform: translateY(0);
439
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
440
- }
441
- }
442
-
443
- .rotate-glow {
444
- animation: rotateGlow 4s linear infinite;
445
- }
446
-
447
- @keyframes rotateGlow {
448
- 0% {
449
- transform: rotate(0deg);
450
- box-shadow: 0 0 5px rgba(59, 130, 246, 0.5);
451
- }
452
- 25% {
453
- box-shadow: 5px 0 5px rgba(59, 130, 246, 0.5);
454
- }
455
- 50% {
456
- box-shadow: 0 -5px 5px rgba(59, 130, 246, 0.5);
457
- }
458
- 75% {
459
- box-shadow: -5px 0 5px rgba(59, 130, 246, 0.5);
460
- }
461
- 100% {
462
- transform: rotate(360deg);
463
- box-shadow: 0 0 5px rgba(59, 130, 246, 0.5);
464
- }
465
- }
466
-
467
- .wave-glow {
468
- position: relative;
469
- overflow: hidden;
470
- }
471
-
472
- .wave-glow::before {
473
- content: "";
474
- position: absolute;
475
- top: 0;
476
- left: -100%;
477
- width: 100%;
478
- height: 100%;
479
- background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.2), transparent);
480
- animation: waveGlow 2s linear infinite;
481
- }
482
-
483
- @keyframes waveGlow {
484
- 0% {
485
- left: -100%;
486
- }
487
- 100% {
488
- left: 100%;
489
- }
490
- }
491
-
492
- .sparkle {
493
- position: relative;
494
- }
495
-
496
- .sparkle::before {
497
- content: "";
498
- position: absolute;
499
- top: -2px;
500
- right: -2px;
501
- width: 6px;
502
- height: 6px;
503
- background: #fff;
504
- border-radius: 50%;
505
- box-shadow: 0 0 10px #fff, 0 0 20px #fff;
506
- animation: sparkle 1.5s infinite;
507
- }
508
-
509
- @keyframes sparkle {
510
- 0%, 100% {
511
- opacity: 0;
512
- }
513
- 50% {
514
- opacity: 1;
515
- }
516
- }
517
-
518
- .shimmer {
519
- background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%);
520
- background-size: 200% 100%;
521
- animation: shimmer 2s infinite;
522
- }
523
-
524
- @keyframes shimmer {
525
- 0% {
526
- background-position: -200% 0;
527
- }
528
- 100% {
529
- background-position: 200% 0;
530
- }
531
- }
532
-
533
- .ripple {
534
- position: relative;
535
- overflow: hidden;
536
- }
537
-
538
- .ripple::after {
539
- content: "";
540
- position: absolute;
541
- top: 50%;
542
- left: 50%;
543
- width: 0;
544
- height: 0;
545
- border-radius: 50%;
546
- background: rgba(255, 255, 255, 0.2);
547
- transform: translate(-50%, -50%);
548
- opacity: 0;
549
- }
550
-
551
- .ripple:active::after {
552
- width: 200px;
553
- height: 200px;
554
- opacity: 1;
555
- transition: width 0.3s, height 0.3s, opacity 0.3s;
556
- }
557
-
558
- .morph {
559
- transition: all 0.3s ease;
560
- border-radius: 10px;
561
- }
562
-
563
- .morph:hover {
564
- border-radius: 20px;
565
- }
566
-
567
- .tilt {
568
- transition: transform 0.3s ease;
569
- transform-style: preserve-3d;
570
- }
571
-
572
- .tilt:hover {
573
- transform: rotateY(10deg) rotateX(10deg);
574
- }
575
-
576
- .pop {
577
- transition: all 0.2s ease;
578
- }
579
-
580
- .pop:active {
581
- transform: scale(0.95);
582
- }
583
-
584
- .skew {
585
- transition: transform 0.3s ease;
586
- }
587
-
588
- .skew:hover {
589
- transform: skew(-5deg);
590
- }
591
-
592
- .stretch {
593
- transition: all 0.3s ease;
594
- }
595
-
596
- .stretch:hover {
597
- transform: scaleX(1.05);
598
- }
599
-
600
- .compress {
601
- transition: all 0.3s ease;
602
- }
603
-
604
- .compress:hover {
605
- transform: scaleY(0.95);
606
- }
607
-
608
- .swing {
609
- transition: transform 0.3s ease;
610
- transform-origin: top center;
611
- }
612
-
613
- .swing:hover {
614
- transform: rotate(5deg);
615
- }
616
-
617
- .wobble {
618
- animation: wobble 1s ease-in-out;
619
- }
620
-
621
- @keyframes wobble {
622
- 0%, 100% {
623
- transform: translateX(0);
624
- }
625
- 25% {
626
- transform: translateX(-10px);
627
- }
628
- 75% {
629
- transform: translateX(10px);
630
- }
631
- }
632
-
633
- .jello {
634
- animation: jello 0.6s ease;
635
- }
636
-
637
- @keyframes jello {
638
- 0%, 100% {
639
- transform: scale(1, 1);
640
- }
641
- 25% {
642
- transform: scale(1.1, 0.9);
643
- }
644
- 50% {
645
- transform: scale(0.9, 1.1);
646
- }
647
- 75% {
648
- transform: scale(1.05, 0.95);
649
- }
650
- }
651
-
652
- .heartbeat {
653
- animation: heartbeat 1.5s ease-in-out infinite;
654
- }
655
-
656
- @keyframes heartbeat {
657
- 0%, 100% {
658
- transform: scale(1);
659
- }
660
- 50% {
661
- transform: scale(1.1);
662
- }
663
- }
664
-
665
- .bounce {
666
- animation: bounce 1s infinite;
667
- }
668
-
669
- @keyframes bounce {
670
- 0%, 100% {
671
- transform: translateY(0);
672
- }
673
- 50% {
674
- transform: translateY(-20px);
675
- }
676
- }
677
-
678
- .flip {
679
- animation: flip 1s ease;
680
- }
681
-
682
- @keyframes flip {
683
- 0% {
684
- transform: rotateY(0deg);
685
- }
686
- 100% {
687
- transform: rotateY(360deg);
688
- }
689
- }
690
-
691
- .roll {
692
- animation: roll 1s ease;
693
- }
694
-
695
- @keyframes roll {
696
- 0% {
697
- transform: rotate(0deg);
698
- }
699
- 100% {
700
- transform: rotate(360deg);
701
- }
702
- }
703
-
704
- .rubberBand {
705
- animation: rubberBand 1s ease;
706
- }
707
-
708
- @keyframes rubberBand {
709
- 0%, 100% {
710
- transform: scale(1, 1);
711
- }
712
- 30% {
713
- transform: scaleX(1.25) scaleY(0.75);
714
- }
715
- 40% {
716
- transform: scaleX(0.75) scaleY(1.25);
717
- }
718
- 60% {
719
- transform: scaleX(1.15) scaleY(0.85);
720
- }
721
- }
722
-
723
- .shake {
724
- animation: shake 0.5s ease;
725
- }
726
-
727
- @keyframes shake {
728
- 0%, 100% {
729
- transform: translateX(0);
730
- }
731
- 25% {
732
- transform: translateX(-5px);
733
- }
734
- 75% {
735
- transform: translateX(5px);
736
- }
737
- }
738
-
739
- .tada {
740
- animation: tada 1s ease;
741
- }
742
-
743
- @keyframes tada {
744
- 0%, 100% {
745
- transform: scale(1);
746
- }
747
- 10%, 20% {
748
- transform: scale(0.9) rotate(-3deg);
749
- }
750
- 30%, 50%, 70%, 90% {
751
- transform: scale(1.1) rotate(3deg);
752
- }
753
- 40%, 60%, 80% {
754
- transform: scale(1.1) rotate(-3deg);
755
- }
756
- }
757
-
758
- .lightSpeedIn {
759
- animation: lightSpeedIn 0.5s ease-out;
760
- }
761
-
762
- @keyframes lightSpeedIn {
763
- 0% {
764
- transform: translateX(100%) skewX(-30deg);
765
- opacity: 0;
766
- }
767
- 60% {
768
- transform: translateX(-20%) skewX(30deg);
769
- opacity: 1;
770
- }
771
- 80% {
772
- transform: translateX(0%) skewX(-15deg);
773
- opacity: 1;
774
- }
775
- 100% {
776
- transform: translateX(0%) skewX(0deg);
777
- opacity: 1;
778
- }
779
- }
780
-
781
- .lightSpeedOut {
782
- animation: lightSpeedOut 0.5s ease-in;
783
- }
784
-
785
- @keyframes lightSpeedOut {
786
- 0% {
787
- transform: translateX(0%) skewX(0deg);
788
- opacity: 1;
789
- }
790
- 100% {
791
- transform: translateX(100%) skewX(-30deg);
792
- opacity: 0;
793
- }
794
- }
795
-
796
- .rotateInDownLeft {
797
- animation: rotateInDownLeft 0.6s ease-out;
798
- }
799
-
800
- @keyframes rotateInDownLeft {
801
- 0% {
802
- transform-origin: left bottom;
803
- transform: rotate(-90deg);
804
- opacity: 0;
805
- }
806
- 100% {
807
- transform-origin: left bottom;
808
- transform: rotate(0deg);
809
- opacity: 1;
810
- }
811
- }
812
-
813
- .rotateInDownRight {
814
- animation: rotateInDownRight 0.6s ease-out;
815
- }
816
-
817
- @keyframes rotateInDownRight {
818
- 0% {
819
- transform-origin: right bottom;
820
- transform: rotate(90deg);
821
- opacity: 0;
822
- }
823
- 100% {
824
- transform-origin: right bottom;
825
- transform: rotate(0deg);
826
- opacity: 1;
827
- }
828
- }
829
-
830
- .rotateInUpLeft {
831
- animation: rotateInUpLeft 0.6s ease-out;
832
- }
833
-
834
- @keyframes rotateInUpLeft {
835
- 0% {
836
- transform-origin: left bottom;
837
- transform: rotate(90deg);
838
- opacity: 0;
839
- }
840
- 100% {
841
- transform-origin: left bottom;
842
- transform: rotate(0deg);
843
- opacity: 1;
844
- }
845
- }
846
-
847
- .rotateInUpRight {
848
- animation: rotateInUpRight 0.6s ease-out;
849
- }
850
-
851
- @keyframes rotateInUpRight {
852
- 0% {
853
- transform-origin: right bottom;
854
- transform: rotate(-90deg);
855
- opacity: 0;
856
- }
857
- 100% {
858
- transform-origin: right bottom;
859
- transform: rotate(0deg);
860
- opacity: 1;
861
- }
862
- }
863
-
864
- .hinge {
865
- animation: hinge 2s ease;
866
- }
867
-
868
- @keyframes hinge {
869
- 0% {
870
- transform-origin: top left;
871
- animation-timing-function: ease-in-out;
872
- }
873
- 20%, 60% {
874
- transform: rotate(80deg);
875
- transform-origin: top left;
876
- animation-timing-function: ease-in-out;
877
- }
878
- 40%, 80% {
879
- transform: rotate(60deg);
880
- transform-origin: top left;
881
- animation-timing-function: ease-in-out;
882
- opacity: 1;
883
- }
884
- 100% {
885
- transform: translateY(700px);
886
- opacity: 0;
887
- }
888
- }
889
-
890
- .jackInTheBox {
891
- animation: jackInTheBox 0.7s ease;
892
- }
893
-
894
- @keyframes jackInTheBox {
895
- 0% {
896
- opacity: 0;
897
- transform: scale(0.1) rotate(30deg);
898
- transform-origin: center bottom;
899
- }
900
- 50% {
901
- transform: rotate(-10deg);
902
- }
903
- 70% {
904
- transform: rotate(3deg);
905
- }
906
- 100% {
907
- opacity: 1;
908
- transform: scale(1);
909
- }
910
- }
911
-
912
- .rollIn {
913
- animation: rollIn 0.7s ease;
914
- }
915
-
916
- @keyframes rollIn {
917
- 0% {
918
- opacity: 0;
919
- transform: translateX(-100%) rotate(-120deg);
920
- }
921
- 100% {
922
- opacity: 1;
923
- transform: translateX(0) rotate(0deg);
924
- }
925
- }
926
-
927
- .rollOut {
928
- animation: rollOut 0.7s ease;
929
- }
930
-
931
- @keyframes rollOut {
932
- 0% {
933
- opacity: 1;
934
- transform: translateX(0) rotate(0deg);
935
- }
936
- 100% {
937
- opacity: 0;
938
- transform: translateX(100%) rotate(120deg);
939
- }
940
- }
941
-
942
- .zoomInDown {
943
- animation: zoomInDown 0.6s ease;
944
- }
945
-
946
- @keyframes zoomInDown {
947
- 0% {
948
- opacity: 0;
949
- transform: scale(0.1) translateY(-1000px);
950
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
951
- }
952
- 60% {
953
- opacity: 1;
954
- transform: scale(0.475) translateY(60px);
955
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
956
- }
957
- }
958
-
959
- .zoomInLeft {
960
- animation: zoomInLeft 0.6s ease;
961
- }
962
-
963
- @keyframes zoomInLeft {
964
- 0% {
965
- opacity: 0;
966
- transform: scale(0.1) translateX(-1000px);
967
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
968
- }
969
- 60% {
970
- opacity: 1;
971
- transform: scale(0.475) translateX(10px);
972
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
973
- }
974
- }
975
-
976
- .zoomInRight {
977
- animation: zoomInRight 0.6s ease;
978
- }
979
-
980
- @keyframes zoomInRight {
981
- 0% {
982
- opacity: 0;
983
- transform: scale(0.1) translateX(1000px);
984
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
985
- }
986
- 60% {
987
- opacity: 1;
988
- transform: scale(0.475) translateX(-10px);
989
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
990
- }
991
- }
992
-
993
- .zoomInUp {
994
- animation: zoomInUp 0.6s ease;
995
- }
996
-
997
- @keyframes zoomInUp {
998
- 0% {
999
- opacity: 0;
1000
- transform: scale(0.1) translateY(1000px);
1001
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
1002
- }
1003
- 60% {
1004
- opacity: 1;
1005
- transform: scale(0.475) translateY(-60px);
1006
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
1007
- }
1008
- }
1009
-
1010
- .zoomOut {
1011
- animation: zoomOut 0.6s ease;
1012
- }
1013
-
1014
- @keyframes zoomOut {
1015
- 0% {
1016
- opacity: 1;
1017
- transform: scale(1);
1018
- }
1019
- 50% {
1020
- opacity: 0;
1021
- transform: scale(0.3);
1022
- }
1023
- 100% {
1024
- opacity: 0;
1025
- }
1026
- }
1027
-
1028
- .zoomOutDown {
1029
- animation: zoomOutDown 0.6s ease;
1030
- }
1031
-
1032
- @keyframes zoomOutDown {
1033
- 40% {
1034
- opacity: 1;
1035
- transform: scale(0.475) translateY(-60px);
1036
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
1037
- }
1038
- 100% {
1039
- opacity: 0;
1040
- transform: scale(0.1) translateY(2000px);
1041
- transform-origin: center bottom;
1042
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
1043
- }
1044
- }
1045
-
1046
- .zoomOutLeft {
1047
- animation: zoomOutLeft 0.6s ease;
1048
- }
1049
-
1050
- @keyframes zoomOutLeft {
1051
- 40% {
1052
- opacity: 1;
1053
- transform: scale(0.475) translateX(42px);
1054
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
1055
- }
1056
- 100% {
1057
- opacity: 0;
1058
- transform: scale(0.1) translateX(-2000px);
1059
- transform-origin: left center;
1060
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
1061
- }
1062
- }
1063
-
1064
- .zoomOutRight {
1065
- animation: zoomOutRight 0.6s ease;
1066
- }
1067
-
1068
- @keyframes zoomOutRight {
1069
- 40% {
1070
- opacity: 1;
1071
- transform: scale(0.475) translateX(-42px);
1072
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
1073
- }
1074
- 100% {
1075
- opacity: 0;
1076
- transform: scale(0.1) translateX(2000px);
1077
- transform-origin: right center;
1078
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
1079
- }
1080
- }
1081
-
1082
- .zoomOutUp {
1083
- animation: zoomOutUp 0.6s ease;
1084
- }
1085
-
1086
- @keyframes zoomOutUp {
1087
- 40% {
1088
- opacity: 1;
1089
- transform: scale(0.475) translateY(60px);
1090
- animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
1091
- }
1092
- 100% {
1093
- opacity: 0;
1094
- transform: scale(0.1) translateY(-2000px);
1095
- transform-origin: center top;
1096
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
1097
- }
1098
- }
1099
-
1100
- .slideInDown {
1101
- animation: slideInDown 0.5s ease-out;
1102
- }
1103
-
1104
- @keyframes slideInDown {
1105
- from {
1106
- transform: translateY(-100%);
1107
- opacity: 0;
1108
- }
1109
- to {
1110
- transform: translateY(0);
1111
- opacity: 1;
1112
- }
1113
- }
1114
-
1115
- .slideInLeft {
1116
- animation: slideInLeft 0.5s ease-out;
1117
- }
1118
-
1119
- @keyframes slideInLeft {
1120
- from {
1121
- transform: translateX(-100%);
1122
- opacity: 0;
1123
- }
1124
- to {
1125
- transform: translateX(0);
1126
- opacity: 1;
1127
- }
1128
- }
1129
-
1130
- .slideInRight {
1131
- animation: slideInRight 0.5s ease-out;
1132
- }
1133
-
1134
- @keyframes slideInRight {
1135
- from {
1136
- transform: translateX(100%);
1137
- opacity: 0;
1138
- }
1139
- to {
1140
- transform: translateX(0);
1141
- opacity: 1;
1142
- }
1143
- }
1144
-
1145
- .slideInUp {
1146
- animation: slideInUp 0.5s ease-out;
1147
- }
1148
-
1149
- @keyframes slideInUp {
1150
- from {
1151
- transform: translateY(100%);
1152
- opacity: 0;
1153
- }
1154
- to {
1155
- transform: translateY(0);
1156
- opacity: 1;
1157
- }
1158
- }
1159
-
1160
- .slideOutDown {
1161
- animation: slideOutDown 0.5s ease-out;
1162
- }
1163
-
1164
- @keyframes slideOutDown {
1165
- from {
1166
- transform: translateY(0);
1167
- opacity: 1;
1168
- }
1169
- to {
1170
- transform: translateY(100%);
1171
- opacity: 0;
1172
- }
1173
- }
1174
-
1175
- .slideOutLeft {
1176
- animation: slideOutLeft 0.5s ease-out;
1177
- }
1178
-
1179
- @keyframes slideOutLeft {
1180
- from {
1181
- transform: translateX(0);
1182
- opacity: 1;
1183
- }
1184
- to {
1185
- transform: translateX(-100%);
1186
- opacity: 0;
1187
- }
1188
- }
1189
-
1190
- .slideOutRight {
1191
- animation: slideOutRight 0.5s ease-out;
1192
- }
1193
-
1194
- @keyframes slideOutRight {
1195
- from {
1196
- transform: translateX(0);
1197
- opacity: 1;
1198
- }
1199
- to {
1200
- transform: translateX(100%);
1201
- opacity: 0;
1202
- }
1203
- }
1204
-
1205
- .slideOutUp {
1206
- animation: slideOutUp 0.5s ease-out;
1207
- }
1208
-
1209
- @keyframes slideOutUp {
1210
- from {
1211
- transform: translateY(0);
1212
- opacity: 1;
1213
- }
1214
- to {
1215
- transform: translateY(-100%);
1216
- opacity: 0;
1217
- }
1218
- }
1219
-
1220
- .fadeIn {
1221
- animation: fadeIn 0.5s ease-out;
1222
- }
1223
-
1224
- .fadeOut {
1225
- animation: fadeOut 0.5s ease-out;
1226
- }
1227
-
1228
- @keyframes fadeOut {
1229
- from {
1230
- opacity: 1;
1231
- }
1232
- to {
1233
- opacity: 0;
1234
- }
1235
- }
1236
-
1237
- .fadeInDown {
1238
- animation: fadeInDown 0.5s ease-out;
1239
- }
1240
-
1241
- .fadeOutDown {
1242
- animation: fadeOutDown 0.5s ease-out;
1243
- }
1244
-
1245
- @keyframes fadeOutDown {
1246
- from {
1247
- opacity: 1;
1248
- }
1249
- to {
1250
- opacity: 0;
1251
- transform: translateY(100%);
1252
- }
1253
- }
1254
-
1255
- .fadeInDownBig {
1256
- animation: fadeInDownBig 0.5s ease-out;
1257
- }
1258
-
1259
- @keyframes fadeInDownBig {
1260
- from {
1261
- opacity: 0;
1262
- transform: translateY(-2000px);
1263
- }
1264
- to {
1265
- opacity: 1;
1266
- transform: translateY(0);
1267
- }
1268
- }
1269
-
1270
- .fadeOutDownBig {
1271
- animation: fadeOutDownBig 0.5s ease-out;
1272
- }
1273
-
1274
- @keyframes fadeOutDownBig {
1275
- from {
1276
- opacity: 1;
1277
- }
1278
- to {
1279
- opacity: 0;
1280
- transform: translateY(2000px);
1281
- }
1282
- }
1283
-
1284
- .fadeInLeft {
1285
- animation: fadeInLeft 0.5s ease-out;
1286
- }
1287
-
1288
- .fadeOutLeft {
1289
- animation: fadeOutLeft 0.5s ease-out;
1290
- }
1291
-
1292
- @keyframes fadeOutLeft {
1293
- from {
1294
- opacity: 1;
1295
- }
1296
- to {
1297
- opacity: 0;
1298
- transform: translateX(-100%);
1299
- }
1300
- }
1301
-
1302
- .fadeInLeftBig {
1303
- animation: fadeInLeftBig 0.5s ease-out;
1304
- }
1305
-
1306
- @keyframes fadeInLeftBig {
1307
- from {
1308
- opacity: 0;
1309
- transform: translateX(-2000px);
1310
- }
1311
- to {
1312
- opacity: 1;
1313
- transform: translateX(0);
1314
- }
1315
- }
1316
-
1317
- .fadeOutLeftBig {
1318
- animation: fadeOutLeftBig 0.5s ease-out;
1319
- }
1320
-
1321
- @keyframes fadeOutLeftBig {
1322
- from {
1323
- opacity: 1;
1324
- }
1325
- to {
1326
- opacity: 0;
1327
- transform: translateX(-2000px);
1328
- }
1329
- }
1330
-
1331
- .fadeInRight {
1332
- animation: fadeInRight 0.5s ease-out;
1333
- }
1334
-
1335
- .fadeOutRight {
1336
- animation: fadeOutRight 0.5s ease-out;
1337
- }
1338
-
1339
- @keyframes fadeOutRight {
1340
- from {
1341
- opacity: 1;
1342
- }
1343
- to {
1344
- opacity: 0;
1345
- transform: translateX(100%);
1346
- }
1347
- }
1348
-
1349
- .fadeInRightBig {
1350
- animation: fadeInRightBig 0.5s ease-out;
1351
- }
1352
-
1353
- @keyframes fadeInRightBig {
1354
- from {
1355
- opacity: 0;
1356
- transform: translateX(2000px);
1357
- }
1358
- to {
1359
- opacity: 1;
1360
- transform: translateX(0);
1361
- }
1362
- }
1363
-
1364
- .fadeOutRightBig {
1365
- animation: fadeOutRightBig 0.5s ease-out;
1366
- }
1367
-
1368
- @keyframes fadeOutRightBig {
1369
- from {
1370
- opacity: 1;
1371
- }
1372
- to {
1373
- opacity: 0;
1374
- transform: translateX(2000px);
1375
- }
1376
- }
1377
-
1378
- .fadeInUp {
1379
- animation: fadeInUp 0.5s ease-out;
1380
- }
1381
-
1382
- .fadeOutUp {
1383
- animation: fadeOutUp 0.5s ease-out;
1384
- }
1385
-
1386
- @keyframes fadeOutUp {
1387
- from {
1388
- opacity: 1;
1389
- }
1390
- to {
1391
- opacity: 0;
1392
- transform: translateY(-100%);
1393
- }
1394
- }
1395
-
1396
- .fadeInUpBig {
1397
- animation: fadeInUpBig 0.5s ease-out;
1398
- }
1399
-
1400
- @keyframes fadeInUpBig {
1401
- from {
1402
- opacity: 0;
1403
- transform: translateY(2000px);
1404
- }
1405
- to {
1406
- opacity: 1;
1407
- transform: translateY(0);
1408
- }
1409
- }
1410
-
1411
- .fadeOutUpBig {
1412
- animation: fadeOutUpBig 0.5s ease-out;
1413
- }
1414
-
1415
- @keyframes fadeOutUpBig {
1416
- from {
1417
- opacity: 1;
1418
- }
1419
- to {
1420
- opacity: 0;
1421
- transform: translateY(-2000px);
1422
- }
1423
- }
1424
-
1425
- .flipInX {
1426
- animation: flipInX 0.6s ease;
1427
- }
1428
-
1429
- @keyframes flipInX {
1430
- 0% {
1431
- transform: perspective(400px) rotateX(90deg);
1432
- opacity: 0;
1433
- }
1434
- 40% {
1435
- transform: perspective(400px) rotateX(-10deg);
1436
- }
1437
- 70% {
1438
- transform: perspective(400px) rotateX(10deg);
1439
- }
1440
- 100% {
1441
- transform: perspective(400px) rotateX(0deg);
1442
- opacity: 1;
1443
- }
1444
- }
1445
-
1446
- .flipInY {
1447
- animation: flipInY 0.6s ease;
1448
- }
1449
-
1450
- @keyframes flipInY {
1451
- 0% {
1452
- transform: perspective(400px) rotateY(90deg);
1453
- opacity: 0;
1454
- }
1455
- 40% {
1456
- transform: perspective(400px) rotateY(-10deg);
1457
- }
1458
- 70% {
1459
- transform: perspective(400px) rotateY(10deg);
1460
- }
1461
- 100% {
1462
- transform: perspective(400px) rotateY(0deg);
1463
- opacity: 1;
1464
- }
1465
- }
1466
-
1467
- .flipOutX {
1468
- animation: flipOutX 0.6s ease;
1469
- }
1470
-
1471
- @keyframes flipOutX {
1472
- 0% {
1473
- transform: perspective(400px) rotateX(0deg);
1474
- opacity: 1;
1475
- }
1476
- 100% {
1477
- transform: perspective(400px) rotateX(90deg);
1478
- opacity: 0;
1479
- }
1480
- }
1481
-
1482
- .flipOutY {
1483
- animation: flipOutY 0.6s ease;
1484
- }
1485
-
1486
- @keyframes flipOutY {
1487
- 0% {
1488
- transform: perspective(400px) rotateY(0deg);
1489
- opacity: 1;
1490
- }
1491
- 100% {
1492
- transform: perspective(400px) rotateY(90deg);
1493
- opacity: 0;
1494
- }
1495
- }
1496
-
1497
- .lightSpeedInRight {
1498
- animation: lightSpeedInRight 0.5s ease-out;
1499
- }
1500
-
1501
- @keyframes lightSpeedInRight {
1502
- 0% {
1503
- transform: translateX(-100%) skewX(30deg);
1504
- opacity: 0;
1505
- }
1506
- 60% {
1507
- transform: translateX(20%) skewX(-30deg);
1508
- opacity: 1;
1509
- }
1510
- 80% {
1511
- transform: translateX(0%) skewX(15deg);
1512
- opacity: 1;
1513
- }
1514
- 100% {
1515
- transform: translateX(0%) skewX(0deg);
1516
- opacity: 1;
1517
- }
1518
- }
1519
-
1520
- .lightSpeedOutLeft {
1521
- animation: lightSpeedOutLeft 0.5s ease-in;
1522
- }
1523
-
1524
- @keyframes lightSpeedOutLeft {
1525
- 0% {
1526
- transform: translateX(0%) skewX(0deg);
1527
- opacity: 1;
1528
- }
1529
- 100% {
1530
- transform: translateX(-100%) skewX(30deg);
1531
- opacity: 0;
1532
- }
1533
- }
1534
-
1535
- .lightSpeedOutRight {
1536
- animation: lightSpeedOutRight 0.5s ease-in;
1537
- }
1538
-
1539
- @keyframes lightSpeedOutRight {
1540
- 0% {
1541
- transform: translateX(0%) skewX(0deg);
1542
- opacity: 1;
1543
- }
1544
- 100% {
1545
- transform: translateX(100%) skewX(-30deg);
1546
- opacity: 0;
1547
- }
1548
- }
1549
-
1550
- .rotateOut {
1551
- animation: rotateOut 0.6s ease;
1552
- }
1553
-
1554
- @keyframes rotateOut {
1555
- 0% {
1556
- transform-origin: center;
1557
- opacity: 1;
1558
- }
1559
- 100% {
1560
- transform-origin: center;
1561
- transform: rotate(200deg);
1562
- opacity: 0;
1563
- }
1564
- }
1565
-
1566
- .rotateOutDownLeft {
1567
- animation: rotateOutDownLeft 0.6s ease;
1568
- }
1569
-
1570
- @keyframes rotateOutDownLeft {
1571
- 0% {
1572
- transform-origin: left bottom;
1573
- opacity: 1;
1574
- }
1575
- 100% {
1576
- transform-origin: left bottom;
1577
- transform: rotate(45deg);
1578
- opacity: 0;
1579
- }
1580
- }
1581
-
1582
- .rotateOutDownRight {
1583
- animation: rotateOutDownRight 0.6s ease;
1584
- }
1585
-
1586
- @keyframes rotateOutDownRight {
1587
- 0% {
1588
- transform-origin: right bottom;
1589
- opacity: 1;
1590
- }
1591
- 100% {
1592
- transform-origin: right bottom;
1593
- transform: rotate(-45deg);
1594
- opacity: 0;
1595
- }
1596
- }
1597
-
1598
- .rotateOutUpLeft {
1599
- animation: rotateOutUpLeft 0.6s ease;
1600
- }
1601
-
1602
- @keyframes rotateOutUpLeft {
1603
- 0% {
1604
- transform-origin: left bottom;
1605
- opacity: 1;
1606
- }
1607
- 100% {
1608
- transform-origin: left bottom;
1609
- transform: rotate(-45deg);
1610
- opacity: 0;
1611
- }
1612
- }
1613
-
1614
- .rotateOutUpRight {
1615
- animation: rotateOutUpRight 0.6s ease;
1616
- }
1617
-
1618
- @keyframes rotateOutUpRight {
1619
- 0% {
1620
- transform-origin: right bottom;
1621
- opacity: 1;
1622
- }
1623
- 100% {
1624
- transform-origin: right bottom;
1625
- transform: rotate(45deg);
1626
- opacity: 0;
1627
- }
1628
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/button.css DELETED
@@ -1,65 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- #sendBtn, #stopBtn {
7
- width: 2.75rem;
8
- height: 2.75rem;
9
- border: none;
10
- border-radius: 50%;
11
- display: flex;
12
- justify-content: center;
13
- align-items: center;
14
- cursor: pointer;
15
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
16
- flex-shrink: 0;
17
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
18
- }
19
-
20
- #sendBtn {
21
- background: linear-gradient(135deg, var(--color-primary), #2563eb);
22
- color: white;
23
- }
24
-
25
- #sendBtn:hover:not(:disabled) {
26
- transform: translateY(-2px) scale(1.05);
27
- box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
28
- }
29
-
30
- #sendBtn:active:not(:disabled) {
31
- transform: translateY(0) scale(0.98);
32
- }
33
-
34
- #sendBtn:disabled {
35
- opacity: 0.6;
36
- cursor: not-allowed;
37
- transform: scale(0.96);
38
- }
39
-
40
- #stopBtn {
41
- background: linear-gradient(135deg, #ef4444, #dc2626);
42
- color: white;
43
- display: none;
44
- box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);
45
- }
46
-
47
- #stopBtn:hover {
48
- transform: translateY(-2px) scale(1.05);
49
- box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3);
50
- }
51
-
52
- #stopBtn:active {
53
- transform: translateY(0) scale(0.98);
54
- }
55
-
56
- #sendIcon {
57
- width: 1.25rem;
58
- height: 1.25rem;
59
- stroke: #fff;
60
- transition: transform 0.2s ease;
61
- }
62
-
63
- #sendBtn:hover:not(:disabled) #sendIcon {
64
- transform: translateX(2px);
65
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/chat/bubble.css DELETED
@@ -1,56 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .bubble {
7
- display: block;
8
- align-self: flex-start;
9
- background: #1f1f1f;
10
- color: #e8e8e8;
11
- border-radius: var(--bubble-radius);
12
- line-height: 1.5;
13
- white-space: normal;
14
- font-family: 'Inter', sans-serif;
15
- font-size: 0.95rem;
16
- position: relative;
17
- opacity: 0;
18
- animation: bubbleAppear 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
19
- margin: 0.75rem 0;
20
- overflow-wrap: break-word;
21
- word-break: break-word;
22
- vertical-align: middle;
23
- padding: 1rem 1rem;
24
- box-sizing: border-box;
25
- max-width: 100%;
26
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
27
- border: 1px solid rgba(255, 255, 255, 0.05);
28
- transition: all 0.3s ease;
29
- }
30
-
31
- .bubble:hover {
32
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
33
- transform: translateY(-1px);
34
- }
35
-
36
- @keyframes bubbleAppear {
37
- to {
38
- opacity: 1;
39
- transform: translateY(0);
40
- }
41
- }
42
-
43
- .bubble-user {
44
- align-self: flex-end;
45
- background: #2b2b2b;
46
- color: #fff;
47
- border-bottom-right-radius: 0.5rem;
48
- box-shadow: none;
49
- }
50
-
51
- .bubble-assist {
52
- align-self: flex-start;
53
- background: transparent;
54
- border: none;
55
- box-shadow: none;
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/chat/markdown.css DELETED
@@ -1,179 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .md-content {
7
- color: inherit;
8
- font-size: 0.95rem;
9
- line-height: 1.6;
10
- overflow-wrap: break-word;
11
- word-break: break-word;
12
- }
13
-
14
- .md-content a {
15
- color: #fb923c;
16
- text-decoration: none;
17
- border-bottom: 1px solid rgba(251, 146, 60, 0.3);
18
- transition: all 0.2s ease;
19
- }
20
-
21
- .md-content a:hover {
22
- color: #ff8c42;
23
- border-bottom-color: #ff8c42;
24
- }
25
-
26
- .md-content a:visited {
27
- color: #fda85e;
28
- }
29
-
30
- .md-content p {
31
- margin: 0.5rem 0;
32
- }
33
-
34
- .md-content pre {
35
- overflow: auto;
36
- padding: 1rem;
37
- border-radius: 0.75rem;
38
- margin: 0.75rem 0;
39
- max-width: 100%;
40
- box-sizing: border-box;
41
- background: rgba(0, 0, 0, 0.3);
42
- border: 1px solid rgba(255, 255, 255, 0.1);
43
- }
44
-
45
- .md-content code {
46
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace;
47
- font-size: 0.85em;
48
- word-break: break-word;
49
- background: rgba(0, 0, 0, 0.2);
50
- padding: 0.2em 0.4em;
51
- border-radius: 0.3rem;
52
- }
53
-
54
- .md-content pre code {
55
- background: transparent;
56
- padding: 0;
57
- }
58
-
59
- .md-content table {
60
- width: 100%;
61
- border-collapse: collapse;
62
- margin: 0.75rem 0;
63
- min-width: 100%;
64
- box-sizing: border-box;
65
- }
66
-
67
- .table-wrapper {
68
- overflow: auto;
69
- border-radius: 0.6rem;
70
- border: 1px solid rgba(255, 255, 255, 0.2);
71
- margin: 0.5rem 0;
72
- max-width: 100%;
73
- box-sizing: border-box;
74
- }
75
-
76
- .md-content th, .md-content td {
77
- padding: 0.6rem 0.8rem;
78
- border: 1px solid rgba(255, 255, 255, 0.2);
79
- text-align: left;
80
- vertical-align: top;
81
- word-break: break-word;
82
- max-width: 200px;
83
- }
84
-
85
- .md-content th, tbody tr:nth-child(odd) td {
86
- background: rgba(0, 0, 0, 0.3);
87
- }
88
-
89
- .md-content ul {
90
- list-style-type: none;
91
- padding-left: 0;
92
- }
93
-
94
- .md-content ul ul {
95
- list-style-type: none;
96
- padding-left: 2em;
97
- }
98
-
99
- .md-content ul ul ul {
100
- list-style-type: none;
101
- padding-left: 2em;
102
- }
103
-
104
- .md-content ol {
105
- list-style-type: none;
106
- padding-left: 0;
107
- counter-reset: list-counter;
108
- }
109
-
110
- .md-content ol ol {
111
- list-style-type: none;
112
- padding-left: 2em;
113
- }
114
-
115
- .md-content ol ol ol {
116
- list-style-type: none;
117
- padding-left: 2em;
118
- }
119
-
120
- .md-content ul > li, .md-content ol > li {
121
- margin-top: 0.5rem;
122
- margin-bottom: 0.5rem;
123
- position: relative;
124
- padding-left: 2.8em;
125
- }
126
-
127
- .md-content ul > li::before {
128
- content: "•";
129
- position: absolute;
130
- left: 1em;
131
- top: 0;
132
- font-size: 1.2em;
133
- }
134
-
135
- .md-content ol > li {
136
- counter-increment: list-counter;
137
- }
138
-
139
- .md-content ol > li::before {
140
- content: counter(list-counter) ".";
141
- position: absolute;
142
- left: 0;
143
- top: 0;
144
- min-width: 2.5em;
145
- text-align: right;
146
- padding-right: 0.5em;
147
- font-weight: 500;
148
- }
149
-
150
- .md-content hr {
151
- border: 0;
152
- height: 2px;
153
- background: #ffffff;
154
- margin: 1.2rem 0;
155
- border-radius: 2px;
156
- }
157
-
158
- .md-content blockquote {
159
- border-left: 4px solid rgba(59, 130, 246, 0.5);
160
- padding: 0.75rem 1rem;
161
- margin: 0.75rem 0;
162
- color: rgba(255, 255, 255, 0.9);
163
- background: rgba(59, 130, 246, 0.1);
164
- border-radius: 0.5rem;
165
- overflow-wrap: break-word;
166
- }
167
-
168
- .md-content img {
169
- max-width: 100%;
170
- height: auto;
171
- display: block;
172
- margin: 0.75rem auto;
173
- border-radius: 8px;
174
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
175
- }
176
-
177
- .md-content table td pre, .md-content table th pre {
178
- overflow: auto;
179
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/chat/style.css DELETED
@@ -1,58 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .chat-title {
7
- font-weight: 700;
8
- font-size: 1rem;
9
- color: #e6eefc;
10
- display: flex;
11
- align-items: center;
12
- gap: 0.5rem;
13
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
14
- white-space: nowrap;
15
- overflow: hidden;
16
- text-overflow: ellipsis;
17
- }
18
-
19
- .chat-controls {
20
- display: flex;
21
- align-items: center;
22
- gap: 0.5rem;
23
- }
24
-
25
- .chat-section {
26
- display: flex;
27
- flex-direction: column;
28
- width: 100%;
29
- height: 100%;
30
- }
31
-
32
- #chatArea {
33
- flex: 1;
34
- display: flex;
35
- flex-direction: column;
36
- overflow-y: auto;
37
- width: 100%;
38
- height: 100%;
39
- box-sizing: border-box;
40
- overflow-wrap: break-word;
41
- word-wrap: break-word;
42
- word-break: break-word;
43
- max-width: 100%;
44
- }
45
-
46
- #chatBox {
47
- display: none;
48
- flex-direction: column;
49
- width: 100%;
50
- height: 100%;
51
- overflow-y: auto;
52
- padding: 0.75rem 0.75rem calc(var(--footer-height) + 1rem) 1rem;
53
- box-sizing: border-box;
54
- max-width: 100%;
55
- overflow-wrap: break-word;
56
- word-wrap: break-word;
57
- word-break: break-word;
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/footer.css DELETED
@@ -1,21 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- #footerForm {
7
- display: flex;
8
- gap: 0.75rem;
9
- background-color: transparent;
10
- align-items: center;
11
- width: 100%;
12
- max-width: 100%;
13
- box-sizing: border-box;
14
- position: fixed;
15
- bottom: 5rem;
16
- left: 0;
17
- right: 0;
18
- z-index: 1000;
19
- animation: slideInUp 0.3s ease-out forwards;
20
- height: 0;
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/header.css DELETED
@@ -1,30 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .main-header, .chat-header {
7
- position: fixed;
8
- top: 0;
9
- left: 0;
10
- right: 0;
11
- z-index: 1000;
12
- display: flex;
13
- align-items: center;
14
- width: 100%;
15
- box-sizing: border-box;
16
- backdrop-filter: blur(10px);
17
- -webkit-backdrop-filter: blur(10px);
18
- transition: all var(--transition-speed) cubic-bezier(0.4, 0, 0.2, 1);
19
- height: var(--header-height);
20
- background-color: rgba(15, 15, 15, 0.85);
21
- }
22
-
23
- .main-header {
24
- justify-content: space-between;
25
- }
26
-
27
- .chat-header {
28
- justify-content: space-between;
29
- display: none;
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/icon.css DELETED
@@ -1,83 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .icon-btn {
7
- background: transparent;
8
- border: none;
9
- cursor: pointer;
10
- padding: 0.5rem;
11
- border-radius: 0.5rem;
12
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
13
- display: flex;
14
- align-items: center;
15
- justify-content: center;
16
- gap: 0.5rem;
17
- color: #dbe3ff;
18
- backdrop-filter: blur(4px);
19
- -webkit-backdrop-filter: blur(4px);
20
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
21
- height: 2.5rem;
22
- width: 2.5rem;
23
- }
24
-
25
- .icon-btn:hover {
26
- background: rgba(255, 255, 255, 0.1);
27
- transform: translateY(-2px);
28
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
29
- }
30
-
31
- .icon-btn:active {
32
- transform: translateY(0);
33
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
34
- }
35
-
36
- .icon {
37
- width: 1.2rem;
38
- height: 1.2rem;
39
- color: #9ca3af;
40
- flex-shrink: 0;
41
- transition: transform 0.2s ease;
42
- }
43
-
44
- .prompt-item:hover .icon {
45
- transform: scale(1.1);
46
- }
47
-
48
- #leftIcon {
49
- display: flex;
50
- align-items: center;
51
- justify-content: center;
52
- padding: 0.2rem;
53
- color: #9ca3af;
54
- cursor: pointer;
55
- transition: all 0.2s ease;
56
- }
57
-
58
- #leftIcon:hover {
59
- color: var(--color-primary);
60
- transform: scale(1.1);
61
- }
62
-
63
- #rightIconGroup {
64
- display: flex;
65
- align-items: center;
66
- justify-content: center;
67
- padding: 0.2rem;
68
- gap: 0.5rem;
69
- }
70
-
71
- .linkedin {
72
- display: inline-block;
73
- width: 1rem;
74
- height: 1rem;
75
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%23dbe3ff' class='bi bi-linkedin' viewBox='0 0 16 16'%3E%3Cpath d='M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z'/%3E%3C/svg%3E");
76
- background-size: contain;
77
- background-repeat: no-repeat;
78
- transition: transform 0.2s ease;
79
- }
80
-
81
- .icon-btn:hover .linkedin {
82
- transform: scale(1.1);
83
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/input.css DELETED
@@ -1,41 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- #inputContainer {
7
- display: flex;
8
- align-items: center;
9
- flex: 1;
10
- background: linear-gradient(145deg, #1a1a1a, #141414);
11
- border-radius: 2rem;
12
- border: 1px solid rgba(255, 255, 255, 0.1);
13
- padding: 0.25rem;
14
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
15
- max-width: 100%;
16
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
17
- }
18
-
19
- #inputContainer:focus-within {
20
- border-color: var(--color-primary);
21
- background: linear-gradient(145deg, #1f1f1f, #1a1a1a);
22
- box-shadow: 0 4px 8px rgba(59, 130, 246, 0.2);
23
- transform: translateY(-1px);
24
- }
25
-
26
- #userInput {
27
- flex: 1;
28
- padding: 0.75rem 0.75rem;
29
- border-radius: 2rem;
30
- border: 1px solid transparent;
31
- background-color: transparent;
32
- color: #e0e0e0;
33
- font-size: 1rem;
34
- outline: none;
35
- transition: border-color 0.2s, background-color 0.2s;
36
- word-break: break-word;
37
- overflow-wrap: break-word;
38
- max-width: 100%;
39
- min-width: 0;
40
- font-family: 'Inter', sans-serif;
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/logo.css DELETED
@@ -1,20 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .logo {
7
- font-weight: 800;
8
- font-size: 1.125rem;
9
- display: flex;
10
- align-items: center;
11
- gap: 0.5rem;
12
- color: #eaeef8;
13
- word-break: break-word;
14
- transition: transform 0.2s ease;
15
- height: 100%;
16
- }
17
-
18
- .logo:hover {
19
- transform: scale(1.05);
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/prompts.css DELETED
@@ -1,46 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .prompts {
7
- display: grid;
8
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
9
- gap: 1rem;
10
- width: 100%;
11
- max-width: 100%;
12
- margin-bottom: 1.5rem;
13
- }
14
-
15
- .prompt-item {
16
- background: linear-gradient(145deg, #1a1a1a, #141414);
17
- padding: 1rem 1.25rem;
18
- border-radius: var(--prompt-radius);
19
- display: flex;
20
- align-items: center;
21
- gap: 0.8rem;
22
- cursor: pointer;
23
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
24
- word-break: break-word;
25
- overflow-wrap: break-word;
26
- border: 1px solid rgba(255, 255, 255, 0.05);
27
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
28
- animation: fadeIn 0.5s ease-out forwards;
29
- transform: translateY(20px);
30
- opacity: 0;
31
- }
32
-
33
- .prompt-item:nth-child(1) { animation-delay: 0.1s; }
34
- .prompt-item:nth-child(2) { animation-delay: 0.2s; }
35
- .prompt-item:nth-child(3) { animation-delay: 0.3s; }
36
- .prompt-item:nth-child(4) { animation-delay: 0.4s; }
37
-
38
- .prompt-item:hover {
39
- background: linear-gradient(145deg, #1f1f1f, #1a1a1a);
40
- transform: translateY(-3px) scale(1.02);
41
- box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
42
- }
43
-
44
- .prompt-item:active {
45
- transform: translateY(0) scale(0.98);
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/1200.css DELETED
@@ -1,15 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (min-width: 1200px) {
7
- .prompts {
8
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
9
- max-width: 1200px;
10
- }
11
-
12
- .title {
13
- font-size: clamp(2rem, 3vw, 3rem);
14
- }
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/2000.css DELETED
@@ -1,50 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (min-width: 2000px) {
7
- :root {
8
- --header-height: 4.5rem;
9
- --footer-height: 6rem;
10
- --border-radius: 1.25rem;
11
- --bubble-radius: 1.25rem;
12
- --prompt-radius: 1.25rem;
13
- font-size: 1.1rem;
14
- }
15
-
16
- .title {
17
- font-size: clamp(2.5rem, 2.5vw, 3.5rem);
18
- }
19
-
20
- .prompts {
21
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
22
- max-width: 1600px;
23
- }
24
-
25
- .prompt-item {
26
- padding: 1.25rem 1.5rem;
27
- border-radius: 1.25rem;
28
- }
29
-
30
- .bubble {
31
- padding: 1.25rem 1.5rem;
32
- font-size: 1.1rem;
33
- border-radius: 1.25rem;
34
- }
35
-
36
- #userInput {
37
- padding: 1rem 1.25rem;
38
- font-size: 1.1rem;
39
- }
40
-
41
- #sendBtn, #stopBtn {
42
- width: 3.25rem;
43
- height: 3.25rem;
44
- }
45
-
46
- .icon-btn {
47
- width: 3rem;
48
- height: 3rem;
49
- }
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/320.css DELETED
@@ -1,82 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (max-width: 320px) {
7
- :root {
8
- --header-height: 2.25rem;
9
- --footer-height: 3.5rem;
10
- --border-radius: 0.5rem;
11
- --bubble-radius: 0.5rem;
12
- --prompt-radius: 0.5rem;
13
- }
14
-
15
- .main-header, .chat-header {
16
- padding: 0.3rem 0.4rem;
17
- }
18
-
19
- #chatBox {
20
- padding: 0.3rem 0.4rem calc(var(--footer-height) + 0.4rem) 0.4rem;
21
- }
22
-
23
- #footerForm {
24
- padding: 0.3rem 0.4rem;
25
- }
26
-
27
- #userInput {
28
- padding: 0.4rem 0.5rem;
29
- font-size: 0.8rem;
30
- }
31
-
32
- #sendBtn, #stopBtn {
33
- width: 1.75rem;
34
- height: 1.75rem;
35
- }
36
-
37
- .title {
38
- font-size: clamp(1rem, 5vw, 1.4rem);
39
- margin-bottom: 0.7rem;
40
- }
41
-
42
- .prompts {
43
- gap: 0.4rem;
44
- margin-bottom: 0.7rem;
45
- }
46
-
47
- .prompt-item {
48
- padding: 0.5rem 0.7rem;
49
- gap: 0.4rem;
50
- }
51
-
52
- .icon {
53
- width: 0.8rem;
54
- height: 0.8rem;
55
- }
56
-
57
- .system {
58
- font-size: 0.6rem;
59
- margin-top: 0.7rem;
60
- }
61
-
62
- .bubble {
63
- padding: 0.5rem 0.7rem;
64
- font-size: 0.75rem;
65
- border-radius: 0.5rem;
66
- margin: 0.3rem 0;
67
- }
68
-
69
- .chat-title {
70
- font-size: 0.8rem;
71
- }
72
-
73
- .icon-btn {
74
- padding: 0.25rem;
75
- width: 1.5rem;
76
- height: 1.5rem;
77
- }
78
-
79
- .logo {
80
- font-size: 0.85rem;
81
- }
82
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/360.css DELETED
@@ -1,82 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (max-width: 360px) {
7
- :root {
8
- --header-height: 2.5rem;
9
- --footer-height: 3.75rem;
10
- --border-radius: 0.625rem;
11
- --bubble-radius: 0.625rem;
12
- --prompt-radius: 0.625rem;
13
- }
14
-
15
- .main-header, .chat-header {
16
- padding: 0.35rem 0.5rem;
17
- }
18
-
19
- #chatBox {
20
- padding: 0.4rem 0.5rem calc(var(--footer-height) + 0.5rem) 0.5rem;
21
- }
22
-
23
- #footerForm {
24
- padding: 0.4rem 0.5rem;
25
- }
26
-
27
- #userInput {
28
- padding: 0.45rem 0.6rem;
29
- font-size: 0.85rem;
30
- }
31
-
32
- #sendBtn, #stopBtn {
33
- width: 2rem;
34
- height: 2rem;
35
- }
36
-
37
- .title {
38
- font-size: clamp(1.1rem, 5vw, 1.6rem);
39
- margin-bottom: 0.8rem;
40
- }
41
-
42
- .prompts {
43
- gap: 0.5rem;
44
- margin-bottom: 0.8rem;
45
- }
46
-
47
- .prompt-item {
48
- padding: 0.6rem 0.8rem;
49
- gap: 0.5rem;
50
- }
51
-
52
- .icon {
53
- width: 0.9rem;
54
- height: 0.9rem;
55
- }
56
-
57
- .system {
58
- font-size: 0.65rem;
59
- margin-top: 0.8rem;
60
- }
61
-
62
- .bubble {
63
- padding: 0.6rem 0.8rem;
64
- font-size: 0.8rem;
65
- border-radius: 0.625rem;
66
- margin: 0.4rem 0;
67
- }
68
-
69
- .chat-title {
70
- font-size: 0.85rem;
71
- }
72
-
73
- .icon-btn {
74
- padding: 0.3rem;
75
- width: 1.75rem;
76
- height: 1.75rem;
77
- }
78
-
79
- .logo {
80
- font-size: 0.9rem;
81
- }
82
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/3840.css DELETED
@@ -1,51 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (min-width: 3840px) {
7
- :root {
8
- --header-height: 5.5rem;
9
- --footer-height: 7rem;
10
- --border-radius: 1.5rem;
11
- --bubble-radius: 1.5rem;
12
- --prompt-radius: 1.5rem;
13
- font-size: 1.3rem;
14
- }
15
-
16
- .title {
17
- font-size: clamp(3rem, 2vw, 5rem);
18
- }
19
-
20
- .prompts {
21
- grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
22
- max-width: 2000px;
23
- gap: 1.5rem;
24
- }
25
-
26
- .prompt-item {
27
- padding: 1.5rem 1.75rem;
28
- border-radius: 1.5rem;
29
- }
30
-
31
- .bubble {
32
- padding: 1.5rem 1.75rem;
33
- font-size: 1.2rem;
34
- border-radius: 1.5rem;
35
- }
36
-
37
- #userInput {
38
- padding: 1.25rem 1.5rem;
39
- font-size: 1.2rem;
40
- }
41
-
42
- #sendBtn, #stopBtn {
43
- width: 3.75rem;
44
- height: 3.75rem;
45
- }
46
-
47
- .icon-btn {
48
- width: 3.5rem;
49
- height: 3.5rem;
50
- }
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/480.css DELETED
@@ -1,78 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (max-width: 480px) {
7
- :root {
8
- --header-height: 2.75rem;
9
- --footer-height: 4rem;
10
- --border-radius: 0.75rem;
11
- --bubble-radius: 0.75rem;
12
- --prompt-radius: 0.75rem;
13
- }
14
-
15
- .main-header, .chat-header {
16
- padding: 0.4rem 0.6rem;
17
- }
18
-
19
- #chatBox {
20
- padding: 0.5rem 0.6rem calc(var(--footer-height) + 0.6rem) 0.6rem;
21
- }
22
-
23
- #footerForm {
24
- padding: 0.5rem 0.6rem;
25
- }
26
-
27
- #userInput {
28
- padding: 0.5rem 0.7rem;
29
- font-size: 0.9rem;
30
- }
31
-
32
- #sendBtn, #stopBtn {
33
- width: 2.25rem;
34
- height: 2.25rem;
35
- }
36
-
37
- .title {
38
- font-size: clamp(1.2rem, 5vw, 1.8rem);
39
- margin-bottom: 1rem;
40
- }
41
-
42
- .prompts {
43
- gap: 0.6rem;
44
- margin-bottom: 1rem;
45
- }
46
-
47
- .prompt-item {
48
- padding: 0.7rem 0.9rem;
49
- gap: 0.6rem;
50
- }
51
-
52
- .icon {
53
- width: 1rem;
54
- height: 1rem;
55
- }
56
-
57
- .system {
58
- font-size: 0.7rem;
59
- margin-top: 1rem;
60
- }
61
-
62
- .bubble {
63
- padding: 0.7rem 0.9rem;
64
- font-size: 0.85rem;
65
- border-radius: 0.75rem;
66
- margin: 0.5rem 0;
67
- }
68
-
69
- .chat-title {
70
- font-size: 0.9rem;
71
- }
72
-
73
- .icon-btn {
74
- padding: 0.35rem;
75
- width: 2rem;
76
- height: 2rem;
77
- }
78
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/720.css DELETED
@@ -1,88 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (max-width: 767px) {
7
- :root {
8
- --header-height: 3rem;
9
- --footer-height: 4.5rem;
10
- --border-radius: 0.875rem;
11
- --bubble-radius: 0.875rem;
12
- --prompt-radius: 0.875rem;
13
- }
14
-
15
- .main-header, .chat-header {
16
- padding: 0.5rem 0.75rem;
17
- }
18
-
19
- #chatBox {
20
- padding: 0.6rem 0.75rem calc(var(--footer-height) + 0.75rem) 0.75rem;
21
- }
22
-
23
- #footerForm {
24
- padding: 0.6rem 0.75rem;
25
- }
26
-
27
- #inputContainer {
28
- padding: 0.2rem;
29
- }
30
-
31
- #userInput {
32
- padding: 0.6rem 0.8rem;
33
- font-size: 0.95rem;
34
- }
35
-
36
- #sendBtn, #stopBtn {
37
- width: 2.5rem;
38
- height: 2.5rem;
39
- }
40
-
41
- .title {
42
- font-size: clamp(1.3rem, 5vw, 2rem);
43
- margin-bottom: 1.2rem;
44
- }
45
-
46
- .prompts {
47
- grid-template-columns: 1fr;
48
- gap: 0.75rem;
49
- margin-bottom: 1.2rem;
50
- }
51
-
52
- .prompt-item {
53
- padding: 0.8rem 1rem;
54
- gap: 0.7rem;
55
- }
56
-
57
- .icon {
58
- width: 1.1rem;
59
- height: 1.1rem;
60
- }
61
-
62
- .system {
63
- font-size: 0.75rem;
64
- margin-top: 1.2rem;
65
- line-height: 1.5;
66
- }
67
-
68
- .bubble {
69
- padding: 0.8rem 1rem;
70
- font-size: 0.9rem;
71
- border-radius: 0.875rem;
72
- margin: 0.6rem 0;
73
- }
74
-
75
- .chat-title {
76
- font-size: 0.95rem;
77
- }
78
-
79
- .chat-controls {
80
- gap: 0.4rem;
81
- }
82
-
83
- .icon-btn {
84
- padding: 0.4rem;
85
- width: 2.25rem;
86
- height: 2.25rem;
87
- }
88
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/7680.css DELETED
@@ -1,51 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- @media (min-width: 7680px) {
7
- :root {
8
- --header-height: 7rem;
9
- --footer-height: 9rem;
10
- --border-radius: 2rem;
11
- --bubble-radius: 2rem;
12
- --prompt-radius: 2rem;
13
- font-size: 1.6rem;
14
- }
15
-
16
- .title {
17
- font-size: clamp(4rem, 1.5vw, 7rem);
18
- }
19
-
20
- .prompts {
21
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
22
- max-width: 3000px;
23
- gap: 2rem;
24
- }
25
-
26
- .prompt-item {
27
- padding: 2rem 2.25rem;
28
- border-radius: 2rem;
29
- }
30
-
31
- .bubble {
32
- padding: 2rem 2.25rem;
33
- font-size: 1.4rem;
34
- border-radius: 2rem;
35
- }
36
-
37
- #userInput {
38
- padding: 1.5rem 2rem;
39
- font-size: 1.4rem;
40
- }
41
-
42
- #sendBtn, #stopBtn {
43
- width: 4.5rem;
44
- height: 4.5rem;
45
- }
46
-
47
- .icon-btn {
48
- width: 4rem;
49
- height: 4rem;
50
- }
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/screen/common.css DELETED
@@ -1,66 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- .max-w-screen-xl {
7
- max-width: min(1280px, 100%);
8
- margin-left: auto;
9
- margin-right: auto;
10
- width: 100%;
11
- padding-left: 1rem;
12
- padding-right: 1rem;
13
- box-sizing: border-box;
14
- }
15
-
16
- @media (min-width: 640px) {
17
- .max-w-screen-xl {
18
- padding-left: 1.5rem;
19
- padding-right: 1.5rem;
20
- }
21
- }
22
-
23
- @media (min-width: 1024px) {
24
- .max-w-screen-xl {
25
- padding-left: 2rem;
26
- padding-right: 2rem;
27
- }
28
- }
29
-
30
- @media (min-width: 1280px) {
31
- .max-w-screen-xl {
32
- padding-left: 3rem;
33
- padding-right: 3rem;
34
- }
35
- }
36
-
37
- @media (min-width: 1536px) {
38
- .max-w-screen-xl {
39
- padding-left: 4rem;
40
- padding-right: 4rem;
41
- }
42
- }
43
-
44
- @media (min-width: 2000px) {
45
- .max-w-screen-xl {
46
- max-width: 1600px;
47
- padding-left: 5rem;
48
- padding-right: 5rem;
49
- }
50
- }
51
-
52
- @media (min-width: 3840px) {
53
- .max-w-screen-xl {
54
- max-width: 2400px;
55
- padding-left: 6rem;
56
- padding-right: 6rem;
57
- }
58
- }
59
-
60
- @media (min-width: 7680px) {
61
- .max-w-screen-xl {
62
- max-width: 4000px;
63
- padding-left: 8rem;
64
- padding-right: 8rem;
65
- }
66
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/style.css DELETED
@@ -1,94 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- *, *::before, *::after {
7
- box-sizing: border-box;
8
- margin: 0;
9
- padding: 0;
10
- }
11
-
12
- html, body {
13
- width: 100%;
14
- height: 100%;
15
- overflow-x: hidden;
16
- font-family: 'Inter', sans-serif;
17
- font-size: 1rem;
18
- -webkit-font-smoothing: antialiased;
19
- -moz-osx-font-smoothing: grayscale;
20
- }
21
-
22
- body {
23
- display: flex;
24
- flex-direction: column;
25
- background-color: var(--background-dark);
26
- color: var(--text-dark);
27
- overflow: hidden;
28
- word-wrap: break-word;
29
- }
30
-
31
- :root {
32
- --color-primary: #3b82f6;
33
- --background-dark: #0f0f0f;
34
- --background-light: #fff;
35
- --text-dark: #e5e7eb;
36
- --text-light: #111;
37
- --white-line-color: #ffffff;
38
- --header-height: 3.5rem;
39
- --footer-height: 5rem;
40
- --transition-speed: 0.3s;
41
- --border-radius: 1rem;
42
- --bubble-radius: 1rem;
43
- --prompt-radius: 1rem;
44
- }
45
-
46
- main {
47
- flex: 1;
48
- display: flex;
49
- flex-direction: column;
50
- margin-top: var(--header-height);
51
- height: calc(100vh - var(--header-height));
52
- overflow: hidden;
53
- }
54
-
55
- #initialContent {
56
- display: flex;
57
- flex-direction: column;
58
- align-items: center;
59
- text-align: center;
60
- max-width: 100%;
61
- width: 100%;
62
- height: 100%;
63
- padding: 1.5rem 1rem;
64
- box-sizing: border-box;
65
- overflow-wrap: break-word;
66
- word-wrap: break-word;
67
- word-break: break-word;
68
- animation: fadeIn 0.8s ease-out forwards;
69
- }
70
-
71
- .title {
72
- font-size: clamp(1.5rem, 4vw, 2.5rem);
73
- font-weight: 800;
74
- color: #eaeef8;
75
- margin-bottom: 1.5rem;
76
- word-break: break-word;
77
- overflow-wrap: break-word;
78
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
79
- animation: slideInUp 0.6s ease-out forwards;
80
- }
81
-
82
- .system {
83
- text-align: center;
84
- margin-top: 5rem;
85
- margin-bottom: 5rem;
86
- font-size: 0.7rem;
87
- color: #9ca3af;
88
- word-break: break-word;
89
- overflow-wrap: break-word;
90
- max-width: 600px;
91
- line-height: 1.6;
92
- animation: fadeIn 1s ease-out 0.6s forwards;
93
- opacity: 0;
94
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/css/webkit.css DELETED
@@ -1,27 +0,0 @@
1
- /*
2
- * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- ::-webkit-scrollbar {
7
- width: 8px;
8
- height: 8px;
9
- }
10
-
11
- ::-webkit-scrollbar-track {
12
- background: rgba(255, 255, 255, 0.05);
13
- border-radius: 4px;
14
- }
15
-
16
- ::-webkit-scrollbar-thumb {
17
- background: rgba(59, 130, 246, 0.5);
18
- border-radius: 4px;
19
- }
20
-
21
- ::-webkit-scrollbar-thumb:hover {
22
- background: rgba(59, 130, 246, 0.7);
23
- }
24
-
25
- * {
26
- -webkit-tap-highlight-color: transparent;
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/plugins/loader.js DELETED
@@ -1,315 +0,0 @@
1
- //
2
- // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
- // SPDX-License-Identifier: Apache-2.0
4
- //
5
-
6
- // Prism.
7
- Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/';
8
-
9
- // WebSocket URL helper.
10
- function createWebSocket() {
11
- return new WebSocket((window.location.protocol === "https:" ? "wss" : "ws") + "//" + window.location.host);
12
- }
13
-
14
- // UI elements.
15
- const chatArea = document.getElementById('chatArea');
16
- const chatBox = document.getElementById('chatBox');
17
- const initialContent = document.getElementById('initialContent');
18
- const form = document.getElementById('footerForm');
19
- const input = document.getElementById('userInput');
20
- const btn = document.getElementById('sendBtn');
21
- const stopBtn = document.getElementById('stopBtn');
22
- const promptItems = document.querySelectorAll('.prompt-item');
23
- const mainHeader = document.getElementById('mainHeader');
24
- const chatHeader = document.getElementById('chatHeader');
25
- const homeBtn = document.getElementById('homeBtn');
26
- const clearBtn = document.getElementById('clearBtn');
27
-
28
- // Track state.
29
- let socket = null;
30
- let streamMsg = null;
31
- let conversationHistory = [];
32
- let currentAssistantText = "";
33
- let isRequestActive = false;
34
- let abortController = null;
35
-
36
- // Render markdown content.
37
- function renderMarkdown(el) {
38
- const raw = el.dataset.text || "";
39
- const html = marked.parse(raw, {
40
- gfm: true,
41
- breaks: true,
42
- smartLists: true,
43
- smartypants: false,
44
- headerIds: false
45
- });
46
- el.innerHTML = '<div class="md-content">' + html + '</div>';
47
- const wrapper = el.querySelector('.md-content');
48
-
49
- // Wrap tables.
50
- const tables = wrapper.querySelectorAll('table');
51
- tables.forEach(t => {
52
- if (t.parentNode && t.parentNode.classList && t.parentNode.classList.contains('table-wrapper')) return;
53
- const div = document.createElement('div');
54
- div.className = 'table-wrapper';
55
- t.parentNode.insertBefore(div, t);
56
- div.appendChild(t);
57
- });
58
-
59
- // Style horizontal rules.
60
- const hrs = wrapper.querySelectorAll('hr');
61
- hrs.forEach(h => {
62
- if (!h.classList.contains('styled-hr')) {
63
- h.classList.add('styled-hr');
64
- }
65
- });
66
-
67
- // Highlight code.
68
- Prism.highlightAllUnder(wrapper);
69
- }
70
-
71
- // Chat view.
72
- function enterChatView() {
73
- mainHeader.style.display = 'none';
74
- chatHeader.style.display = 'flex';
75
- chatHeader.setAttribute('aria-hidden', 'false');
76
- chatBox.style.display = 'flex';
77
- initialContent.style.display = 'none';
78
- }
79
-
80
- // Home view.
81
- function leaveChatView() {
82
- mainHeader.style.display = 'flex';
83
- chatHeader.style.display = 'none';
84
- chatHeader.setAttribute('aria-hidden', 'true');
85
- chatBox.style.display = 'none';
86
- initialContent.style.display = 'flex';
87
- }
88
-
89
- // Chat bubble.
90
- function addMsg(who, text) {
91
- const div = document.createElement('div');
92
- div.className = 'bubble ' + (who === 'user' ? 'bubble-user' : 'bubble-assist');
93
- div.dataset.text = text;
94
- renderMarkdown(div);
95
- chatBox.appendChild(div);
96
- chatBox.style.display = 'flex';
97
- chatBox.scrollTop = chatBox.scrollHeight;
98
- return div;
99
- }
100
-
101
- // Clear all chat.
102
- function clearAllMessages() {
103
- stopStream(true);
104
- conversationHistory = [];
105
- currentAssistantText = "";
106
- if (streamMsg) {
107
- const loadingEl = streamMsg.querySelector('.loading');
108
- if (loadingEl) loadingEl.remove();
109
- streamMsg = null;
110
- }
111
- chatBox.innerHTML = "";
112
- input.value = "";
113
- btn.disabled = true;
114
- stopBtn.style.display = 'none';
115
- btn.style.display = 'inline-flex';
116
- enterChatView();
117
- }
118
-
119
- // Reconnect WebSocket.
120
- let reconnectAttempts = 0;
121
- function setupWebSocket() {
122
- if (socket) {
123
- socket.onopen = null;
124
- socket.onclose = null;
125
- socket.onmessage = null;
126
- socket.onerror = null;
127
- socket.close();
128
- socket = null;
129
- }
130
-
131
- socket = createWebSocket();
132
-
133
- socket.onopen = () => {
134
- reconnectAttempts = 0;
135
- };
136
-
137
- socket.onclose = () => {
138
- reconnectAttempts++;
139
- // Try reconnecting.
140
- const delay = Math.min(30000, 1000 * Math.pow(2, reconnectAttempts)); // 30 seconds.
141
- setTimeout(setupWebSocket, delay);
142
- };
143
-
144
- socket.onmessage = handleSocketMessage;
145
-
146
- socket.onerror = () => {
147
- socket.close();
148
- };
149
- }
150
-
151
- // Handle incoming socket messages.
152
- function handleSocketMessage(e) {
153
- const data = JSON.parse(e.data);
154
- if (data.type === 'chunk') {
155
- if (streamMsg) {
156
- const loadingEl = streamMsg.querySelector('.loading');
157
- if (loadingEl) loadingEl.remove();
158
- streamMsg.dataset.text += data.chunk;
159
- currentAssistantText = streamMsg.dataset.text || "";
160
- renderMarkdown(streamMsg);
161
- chatBox.scrollTop = chatBox.scrollHeight;
162
- }
163
- } else if (data.type === 'end' || data.type === 'error') {
164
- if (streamMsg) {
165
- const loadingEl = streamMsg.querySelector('.loading');
166
- if (loadingEl) loadingEl.remove();
167
- streamMsg.dataset.done = '1';
168
- if (data.type === 'error') {
169
- streamMsg.dataset.text = data.error || 'An error occurred during the request.';
170
- renderMarkdown(streamMsg);
171
- } else {
172
- conversationHistory.push({ role: 'assistant', content: streamMsg.dataset.text });
173
- }
174
- streamMsg = null;
175
- isRequestActive = false;
176
- abortController = null;
177
- }
178
- btn.style.display = 'inline-flex';
179
- stopBtn.style.display = 'none';
180
- stopBtn.style.pointerEvents = 'auto';
181
- }
182
- }
183
-
184
- // Send user message.
185
- async function submitMessage() {
186
- const message = input.value.trim();
187
- if (!message || isRequestActive) return;
188
- enterChatView();
189
- addMsg('user', message);
190
- conversationHistory.push({ role: 'user', content: message });
191
- streamMsg = addMsg('assistant', '');
192
- const loadingEl = document.createElement('span');
193
- loadingEl.className = 'loading';
194
- streamMsg.appendChild(loadingEl);
195
- stopBtn.style.display = 'inline-flex';
196
- btn.style.display = 'none';
197
- input.value = '';
198
- btn.disabled = true;
199
-
200
- isRequestActive = true;
201
-
202
- // Stopping request.
203
- abortController = new AbortController();
204
- try {
205
- socket.send(JSON.stringify({
206
- type: 'ask',
207
- message,
208
- history: conversationHistory,
209
- abortSignal: true
210
- }));
211
- } catch (error) {
212
- if (streamMsg) {
213
- const loadingEl = streamMsg.querySelector('.loading');
214
- if (loadingEl) loadingEl.remove();
215
- streamMsg.dataset.text = error.message || 'An error occurred during the request.';
216
- renderMarkdown(streamMsg);
217
- streamMsg.dataset.done = '1';
218
- streamMsg = null;
219
- isRequestActive = false;
220
- abortController = null;
221
- }
222
- btn.style.display = 'inline-flex';
223
- stopBtn.style.display = 'none';
224
- }
225
- }
226
-
227
- // Stop streaming and cancel the ongoing request.
228
- function stopStream(forceCancel = false) {
229
- if (!isRequestActive) return;
230
-
231
- isRequestActive = false;
232
-
233
- if (abortController) {
234
- abortController.abort();
235
- abortController = null;
236
- }
237
-
238
- // Notify server to stop sending streams / processing.
239
- try {
240
- socket.send(JSON.stringify({ type: 'stop' }));
241
- } catch {}
242
-
243
- if (streamMsg && !forceCancel) {
244
- const loadingEl = streamMsg.querySelector('.loading');
245
- if (loadingEl) loadingEl.remove();
246
- streamMsg.dataset.text += '';
247
- renderMarkdown(streamMsg);
248
- streamMsg.dataset.done = '1';
249
- streamMsg = null;
250
- }
251
-
252
- stopBtn.style.display = 'none';
253
- btn.style.display = 'inline-flex';
254
- stopBtn.style.pointerEvents = 'auto';
255
- }
256
-
257
- // Wait for socket ready.
258
- function sendWhenReady(msgFn) {
259
- if (socket.readyState === WebSocket.OPEN) {
260
- msgFn();
261
- } else {
262
- socket.addEventListener('open', function handler() {
263
- msgFn();
264
- socket.removeEventListener('open', handler);
265
- });
266
- }
267
- }
268
-
269
- // Prompts.
270
- promptItems.forEach(p => {
271
- p.addEventListener('click', () => {
272
- input.value = p.dataset.prompt;
273
- sendWhenReady(submitMessage);
274
- });
275
- });
276
-
277
- // Submit.
278
- form.addEventListener('submit', e => {
279
- e.preventDefault();
280
- submitMessage();
281
- });
282
-
283
- // Stop.
284
- stopBtn.addEventListener('click', () => {
285
- stopBtn.style.pointerEvents = 'none';
286
- stopStream();
287
- });
288
-
289
- // Home.
290
- homeBtn.addEventListener('click', () => {
291
- leaveChatView();
292
- });
293
-
294
- // Clear messages.
295
- clearBtn.addEventListener('click', () => {
296
- clearAllMessages();
297
- });
298
-
299
- // Enable send button only if input has text.
300
- input.addEventListener('input', () => {
301
- btn.disabled = input.value.trim() === '';
302
- });
303
-
304
- // Animations.
305
- document.addEventListener('DOMContentLoaded', function () {
306
- AOS.init({
307
- duration: 800,
308
- easing: 'ease-out-cubic',
309
- once: true,
310
- offset: 50
311
- });
312
- });
313
-
314
- // Initialize WebSocket connection.
315
- setupWebSocket();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
components/assistant-ui/attachment.tsx ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+
6
+ "use client";
7
+
8
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
9
+ import Image from "next/image";
10
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
11
+ import {
12
+ AttachmentPrimitive,
13
+ ComposerPrimitive,
14
+ MessagePrimitive,
15
+ useAssistantState,
16
+ useAssistantApi,
17
+ useComposerRuntime,
18
+ } from "@assistant-ui/react";
19
+ import { useShallow } from "zustand/shallow";
20
+ import {
21
+ Tooltip,
22
+ TooltipContent,
23
+ TooltipTrigger,
24
+ } from "@/components/ui/tooltip";
25
+ import {
26
+ Dialog,
27
+ DialogTitle,
28
+ DialogContent,
29
+ DialogTrigger,
30
+ } from "@/components/ui/dialog";
31
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
32
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
33
+ import { cn } from "@/lib/utils";
34
+
35
+ const useFileSrc = (file: File | undefined) => {
36
+ const [src, setSrc] = useState<string | undefined>(undefined);
37
+
38
+ useEffect(() => {
39
+ if (!file) {
40
+ setSrc(undefined);
41
+ return;
42
+ }
43
+
44
+ const objectUrl = URL.createObjectURL(file);
45
+ setSrc(objectUrl);
46
+
47
+ return () => {
48
+ URL.revokeObjectURL(objectUrl);
49
+ };
50
+ }, [file]);
51
+
52
+ return src;
53
+ };
54
+
55
+ const useAttachmentSrc = () => {
56
+ const { file, src } = useAssistantState(
57
+ useShallow(({ attachment }): { file?: File; src?: string } => {
58
+ if (attachment.type !== "image") return {};
59
+ if (attachment.file) return { file: attachment.file };
60
+ const src = attachment.content?.filter((content) => content.type === "image")[0]
61
+ ?.image;
62
+ if (!src) return {};
63
+ return { src };
64
+ }),
65
+ );
66
+
67
+ return useFileSrc(file) ?? src;
68
+ };
69
+
70
+ type AttachmentPreviewProps = {
71
+ src: string;
72
+ };
73
+
74
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
75
+ const [isLoaded, setIsLoaded] = useState(false);
76
+ return (
77
+ <Image
78
+ src={src}
79
+ alt="Image Preview"
80
+ width={1}
81
+ height={1}
82
+ className={
83
+ isLoaded
84
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
85
+ : "aui-attachment-preview-image-loading hidden"
86
+ }
87
+ onLoadingComplete={() => setIsLoaded(true)}
88
+ priority={false}
89
+ />
90
+ );
91
+ };
92
+
93
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
94
+ const src = useAttachmentSrc();
95
+
96
+ if (!src) return children;
97
+
98
+ return (
99
+ <Dialog>
100
+ <DialogTrigger
101
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
102
+ asChild
103
+ >
104
+ {children}
105
+ </DialogTrigger>
106
+ <DialogContent className="aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&_svg]:text-background [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:!ring-0 [&>button]:hover:[&_svg]:text-destructive">
107
+ <DialogTitle className="aui-sr-only sr-only">
108
+ Image Attachment Preview
109
+ </DialogTitle>
110
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
111
+ <AttachmentPreview src={src} />
112
+ </div>
113
+ </DialogContent>
114
+ </Dialog>
115
+ );
116
+ };
117
+
118
+ const AttachmentThumb: FC = () => {
119
+ const isImage = useAssistantState(
120
+ ({ attachment }) => attachment.type === "image",
121
+ );
122
+ const src = useAttachmentSrc();
123
+
124
+ return (
125
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
126
+ <AvatarImage
127
+ src={src}
128
+ alt="Attachment preview"
129
+ className="aui-attachment-tile-image object-cover"
130
+ />
131
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
132
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
133
+ </AvatarFallback>
134
+ </Avatar>
135
+ );
136
+ };
137
+
138
+ const AttachmentUI: FC = () => {
139
+ const api = useAssistantApi();
140
+ const isComposer = api.attachment.source === "composer";
141
+
142
+ const isImage = useAssistantState(
143
+ ({ attachment }) => attachment.type === "image",
144
+ );
145
+ const typeLabel = useAssistantState(({ attachment }) => {
146
+ const type = attachment.type;
147
+ switch (type) {
148
+ case "image":
149
+ return "Image";
150
+ case "document":
151
+ return "Document";
152
+ case "file":
153
+ return "File";
154
+ default:
155
+ const _exhaustiveCheck: never = type;
156
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
157
+ }
158
+ });
159
+
160
+ return (
161
+ <Tooltip>
162
+ <AttachmentPrimitive.Root
163
+ className={cn(
164
+ "aui-attachment-root relative",
165
+ isImage &&
166
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
167
+ )}
168
+ >
169
+ <AttachmentPreviewDialog>
170
+ <TooltipTrigger asChild>
171
+ <div
172
+ className={cn(
173
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
174
+ isComposer &&
175
+ "aui-attachment-tile-composer border-foreground/20",
176
+ )}
177
+ role="button"
178
+ id="attachment-tile"
179
+ aria-label={`${typeLabel} attachment`}
180
+ >
181
+ <AttachmentThumb />
182
+ </div>
183
+ </TooltipTrigger>
184
+ </AttachmentPreviewDialog>
185
+ {isComposer && <AttachmentRemove />}
186
+ </AttachmentPrimitive.Root>
187
+ <TooltipContent side="top">
188
+ <AttachmentPrimitive.Name />
189
+ </TooltipContent>
190
+ </Tooltip>
191
+ );
192
+ };
193
+
194
+ const AttachmentRemove: FC = () => {
195
+ return (
196
+ <AttachmentPrimitive.Remove asChild>
197
+ <TooltipIconButton
198
+ tooltip="Remove file"
199
+ className="aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:!bg-white [&_svg]:text-black hover:[&_svg]:text-destructive"
200
+ side="top"
201
+ >
202
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
203
+ </TooltipIconButton>
204
+ </AttachmentPrimitive.Remove>
205
+ );
206
+ };
207
+
208
+ export const UserMessageAttachments: FC = () => {
209
+ return (
210
+ <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">
211
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
212
+ </div>
213
+ );
214
+ };
215
+
216
+ export const ComposerAttachments: FC = () => {
217
+ return (
218
+ <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden">
219
+ <ComposerPrimitive.Attachments
220
+ components={{ Attachment: AttachmentUI }}
221
+ />
222
+ </div>
223
+ );
224
+ };
225
+
226
+ export const ComposerAddAttachment: FC = () => {
227
+ const composerRuntime = useComposerRuntime();
228
+
229
+ const handleAddImageAttachment = () => {
230
+ const fileInput = document.createElement("input");
231
+ fileInput.type = "file";
232
+ fileInput.accept = "image/*";
233
+ fileInput.multiple = false;
234
+ fileInput.hidden = true;
235
+ document.body.appendChild(fileInput);
236
+
237
+ fileInput.onchange = async (changeEvent) => {
238
+ const selectedFiles = (changeEvent.target as HTMLInputElement).files;
239
+ if (!selectedFiles) return;
240
+
241
+ for (const selectedFile of selectedFiles) {
242
+ if (selectedFile.type.startsWith("image/")) {
243
+ await composerRuntime.addAttachment(selectedFile);
244
+ }
245
+ }
246
+ document.body.removeChild(fileInput);
247
+ };
248
+
249
+ fileInput.click();
250
+ };
251
+
252
+ return (
253
+ <TooltipIconButton
254
+ type="button"
255
+ onClick={handleAddImageAttachment}
256
+ tooltip="Add Image"
257
+ side="bottom"
258
+ variant="ghost"
259
+ size="icon"
260
+ className="aui-composer-add-attachment size-[34px] rounded-full p-1 text-xs font-semibold hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
261
+ aria-label="Upload Image"
262
+ >
263
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
264
+ </TooltipIconButton>
265
+ );
266
+ };
components/assistant-ui/markdown-text.tsx ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import "@assistant-ui/react-markdown/styles/dot.css";
4
+
5
+ import {
6
+ type CodeHeaderProps,
7
+ MarkdownTextPrimitive,
8
+ unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
9
+ useIsMarkdownCodeBlock,
10
+ } from "@assistant-ui/react-markdown";
11
+ import remarkGfm from "remark-gfm";
12
+ import { type FC, memo, useState } from "react";
13
+ import { CheckIcon, CopyIcon } from "lucide-react";
14
+
15
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ import { SyntaxHighlighter } from "./syntax-highlighter";
19
+
20
+ const MarkdownTextImpl = () => {
21
+ return (
22
+ <MarkdownTextPrimitive
23
+ remarkPlugins={[remarkGfm]}
24
+ className="aui-md"
25
+ components={defaultComponents}
26
+ />
27
+ );
28
+ };
29
+
30
+ export const MarkdownText = memo(MarkdownTextImpl);
31
+
32
+ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
33
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
34
+ const onCopy = () => {
35
+ if (!code || isCopied) return;
36
+ copyToClipboard(code);
37
+ };
38
+
39
+ return (
40
+ <div className="aui-code-header-root mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-muted-foreground/15 px-4 py-2 text-sm font-semibold text-foreground dark:bg-muted-foreground/20">
41
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
42
+ {language}
43
+ </span>
44
+ <TooltipIconButton tooltip="Copy" onClick={onCopy}>
45
+ {!isCopied && <CopyIcon />}
46
+ {isCopied && <CheckIcon />}
47
+ </TooltipIconButton>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const useCopyToClipboard = ({
53
+ copiedDuration = 3000,
54
+ }: {
55
+ copiedDuration?: number;
56
+ } = {}) => {
57
+ const [isCopied, setIsCopied] = useState<boolean>(false);
58
+
59
+ const copyToClipboard = (value: string) => {
60
+ if (!value) return;
61
+
62
+ navigator.clipboard.writeText(value).then(() => {
63
+ setIsCopied(true);
64
+ setTimeout(() => setIsCopied(false), copiedDuration);
65
+ });
66
+ };
67
+
68
+ return { isCopied, copyToClipboard };
69
+ };
70
+
71
+ const defaultComponents = memoizeMarkdownComponents({
72
+ SyntaxHighlighter: SyntaxHighlighter,
73
+
74
+ h1: ({ className, ...props }) => (
75
+ <h1
76
+ className={cn(
77
+ "aui-md-h1 mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ ),
83
+ h2: ({ className, ...props }) => (
84
+ <h2
85
+ className={cn(
86
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ ),
92
+ h3: ({ className, ...props }) => (
93
+ <h3
94
+ className={cn(
95
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
96
+ className,
97
+ )}
98
+ {...props}
99
+ />
100
+ ),
101
+ h4: ({ className, ...props }) => (
102
+ <h4
103
+ className={cn(
104
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
105
+ className,
106
+ )}
107
+ {...props}
108
+ />
109
+ ),
110
+ h5: ({ className, ...props }) => (
111
+ <h5
112
+ className={cn(
113
+ "aui-md-h5 my-4 text-lg font-semibold first:mt-0 last:mb-0",
114
+ className,
115
+ )}
116
+ {...props}
117
+ />
118
+ ),
119
+ h6: ({ className, ...props }) => (
120
+ <h6
121
+ className={cn(
122
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
123
+ className,
124
+ )}
125
+ {...props}
126
+ />
127
+ ),
128
+ p: ({ className, ...props }) => (
129
+ <p
130
+ className={cn(
131
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
132
+ className,
133
+ )}
134
+ {...props}
135
+ />
136
+ ),
137
+ a: ({ className, ...props }) => (
138
+ <a
139
+ className={cn(
140
+ "aui-md-a font-medium text-primary underline underline-offset-4",
141
+ className,
142
+ )}
143
+ {...props}
144
+ />
145
+ ),
146
+ blockquote: ({ className, ...props }) => (
147
+ <blockquote
148
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
149
+ {...props}
150
+ />
151
+ ),
152
+ ul: ({ className, ...props }) => (
153
+ <ul
154
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
155
+ {...props}
156
+ />
157
+ ),
158
+ ol: ({ className, ...props }) => (
159
+ <ol
160
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
161
+ {...props}
162
+ />
163
+ ),
164
+ hr: ({ className, ...props }) => (
165
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
166
+ ),
167
+ table: ({ className, ...props }) => (
168
+ <table
169
+ className={cn(
170
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
171
+ className,
172
+ )}
173
+ {...props}
174
+ />
175
+ ),
176
+ th: ({ className, ...props }) => (
177
+ <th
178
+ className={cn(
179
+ "aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
180
+ className,
181
+ )}
182
+ {...props}
183
+ />
184
+ ),
185
+ td: ({ className, ...props }) => (
186
+ <td
187
+ className={cn(
188
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
189
+ className,
190
+ )}
191
+ {...props}
192
+ />
193
+ ),
194
+ tr: ({ className, ...props }) => (
195
+ <tr
196
+ className={cn(
197
+ "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
198
+ className,
199
+ )}
200
+ {...props}
201
+ />
202
+ ),
203
+ sup: ({ className, ...props }) => (
204
+ <sup
205
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
206
+ {...props}
207
+ />
208
+ ),
209
+ pre: ({ className, ...props }) => (
210
+ <pre
211
+ className={cn(
212
+ "aui-md-pre overflow-x-auto !rounded-t-none rounded-b-lg bg-black p-4 text-white",
213
+ className,
214
+ )}
215
+ {...props}
216
+ />
217
+ ),
218
+ code: function Code({ className, ...props }) {
219
+ const isCodeBlock = useIsMarkdownCodeBlock();
220
+ return (
221
+ <code
222
+ className={cn(
223
+ !isCodeBlock &&
224
+ "aui-md-inline-code rounded border bg-muted font-semibold",
225
+ className,
226
+ )}
227
+ {...props}
228
+ />
229
+ );
230
+ },
231
+ CodeHeader,
232
+ });
components/assistant-ui/thread-list.tsx ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+
6
+ import type { FC } from "react";
7
+ import {
8
+ ThreadListItemPrimitive,
9
+ ThreadListPrimitive,
10
+ useAssistantState,
11
+ } from "@assistant-ui/react";
12
+ import { ArchiveIcon, PlusIcon } from "lucide-react";
13
+
14
+ import { Button } from "@/components/ui/button";
15
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
16
+ import { Skeleton } from "@/components/ui/skeleton";
17
+
18
+ const generateRandomId = (): string => {
19
+ const array = new Uint8Array(8);
20
+ crypto.getRandomValues(array);
21
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
22
+ };
23
+
24
+ export const ThreadList: FC = () => {
25
+ return (
26
+ <ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col items-stretch gap-1.5">
27
+ <ThreadListNew />
28
+ <ThreadListItems />
29
+ </ThreadListPrimitive.Root>
30
+ );
31
+ };
32
+
33
+ const ThreadListNew: FC = () => {
34
+ return (
35
+ <ThreadListPrimitive.New asChild>
36
+ <Button
37
+ className="aui-thread-list-new flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start hover:bg-muted data-active:bg-muted"
38
+ variant="ghost"
39
+ >
40
+ <PlusIcon />
41
+ New Chat
42
+ </Button>
43
+ </ThreadListPrimitive.New>
44
+ );
45
+ };
46
+
47
+ const ThreadListItems: FC = () => {
48
+ const isLoading = useAssistantState(({ threads }) => threads.isLoading);
49
+
50
+ if (isLoading) {
51
+ return <ThreadListSkeleton />;
52
+ }
53
+
54
+ return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;
55
+ };
56
+
57
+ const ThreadListSkeleton: FC = () => {
58
+ return (
59
+ <>
60
+ {Array.from({ length: 5 }, (_, i) => (
61
+ <div
62
+ key={i}
63
+ role="status"
64
+ aria-label="Loading threads"
65
+ aria-live="polite"
66
+ className="aui-thread-list-skeleton-wrapper flex items-center gap-2 rounded-md px-3 py-2"
67
+ >
68
+ <Skeleton className="aui-thread-list-skeleton h-[22px] flex-grow" />
69
+ </div>
70
+ ))}
71
+ </>
72
+ );
73
+ };
74
+
75
+ const ThreadListItem: FC = () => {
76
+ return (
77
+ <ThreadListItemPrimitive.Root className="aui-thread-list-item flex items-center gap-2 rounded-lg transition-all hover:bg-muted focus-visible:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none data-active:bg-muted">
78
+ <ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex-grow px-3 py-2 text-start">
79
+ <ThreadListItemTitle />
80
+ </ThreadListItemPrimitive.Trigger>
81
+ <ThreadListItemArchive />
82
+ </ThreadListItemPrimitive.Root>
83
+ );
84
+ };
85
+
86
+ const ThreadListItemTitle: FC = () => {
87
+ const randomId = generateRandomId().toUpperCase();
88
+
89
+ return (
90
+ <span className="aui-thread-list-item-title text-sm">
91
+ <ThreadListItemPrimitive.Title fallback={`${randomId}`} />
92
+ </span>
93
+ );
94
+ };
95
+
96
+ const ThreadListItemArchive: FC = () => {
97
+ return (
98
+ <ThreadListItemPrimitive.Archive asChild>
99
+ <TooltipIconButton
100
+ className="aui-thread-list-item-archive mr-3 ml-auto size-4 p-0 text-foreground hover:text-primary"
101
+ variant="ghost"
102
+ tooltip="Delete Chat"
103
+ >
104
+ <ArchiveIcon />
105
+ </TooltipIconButton>
106
+ </ThreadListItemPrimitive.Archive>
107
+ );
108
+ };
components/assistant-ui/thread.tsx ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ ArrowDownIcon,
3
+ ArrowUpIcon,
4
+ CheckIcon,
5
+ ChevronLeftIcon,
6
+ ChevronRightIcon,
7
+ CopyIcon,
8
+ PencilIcon,
9
+ RefreshCwIcon,
10
+ Square,
11
+ } from "lucide-react";
12
+
13
+ import {
14
+ ActionBarPrimitive,
15
+ BranchPickerPrimitive,
16
+ ComposerPrimitive,
17
+ ErrorPrimitive,
18
+ MessagePrimitive,
19
+ ThreadPrimitive,
20
+ } from "@assistant-ui/react";
21
+
22
+ import type { FC } from "react";
23
+ import { LazyMotion, MotionConfig, domAnimation } from "motion/react";
24
+ import * as m from "motion/react-m";
25
+
26
+ import { Button } from "@/components/ui/button";
27
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
28
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
29
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
30
+ import {
31
+ ComposerAddAttachment,
32
+ ComposerAttachments,
33
+ UserMessageAttachments,
34
+ } from "@/components/assistant-ui/attachment";
35
+
36
+ import { cn } from "@/lib/utils";
37
+
38
+ export const Thread: FC = () => {
39
+ return (
40
+ <LazyMotion features={domAnimation}>
41
+ <MotionConfig reducedMotion="user">
42
+ <ThreadPrimitive.Root
43
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
44
+ style={{
45
+ ["--thread-max-width" as string]: "44rem",
46
+ }}
47
+ >
48
+ <ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4">
49
+ <ThreadPrimitive.If empty>
50
+ <ThreadWelcome />
51
+ </ThreadPrimitive.If>
52
+
53
+ <ThreadPrimitive.Messages
54
+ components={{
55
+ UserMessage,
56
+ EditComposer,
57
+ AssistantMessage,
58
+ }}
59
+ />
60
+
61
+ <ThreadPrimitive.If empty={false}>
62
+ <div className="aui-thread-viewport-spacer min-h-8 grow" />
63
+ </ThreadPrimitive.If>
64
+
65
+ <Composer />
66
+ </ThreadPrimitive.Viewport>
67
+ </ThreadPrimitive.Root>
68
+ </MotionConfig>
69
+ </LazyMotion>
70
+ );
71
+ };
72
+
73
+ const ThreadScrollToBottom: FC = () => {
74
+ return (
75
+ <ThreadPrimitive.ScrollToBottom asChild>
76
+ <TooltipIconButton
77
+ tooltip="Scroll to bottom"
78
+ variant="outline"
79
+ className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
80
+ >
81
+ <ArrowDownIcon />
82
+ </TooltipIconButton>
83
+ </ThreadPrimitive.ScrollToBottom>
84
+ );
85
+ };
86
+
87
+ const ThreadWelcome: FC = () => {
88
+ return (
89
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
90
+ <div className="aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center">
91
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8">
92
+ <m.div
93
+ initial={{ opacity: 0, y: 10 }}
94
+ animate={{ opacity: 1, y: 0 }}
95
+ exit={{ opacity: 0, y: 10 }}
96
+ className="aui-thread-welcome-message-motion-1 text-2xl font-semibold"
97
+ >
98
+ Hello there!
99
+ </m.div>
100
+ <m.div
101
+ initial={{ opacity: 0, y: 10 }}
102
+ animate={{ opacity: 1, y: 0 }}
103
+ exit={{ opacity: 0, y: 10 }}
104
+ transition={{ delay: 0.1 }}
105
+ className="aui-thread-welcome-message-motion-2 text-2xl text-muted-foreground/65"
106
+ >
107
+ How can I help you today?
108
+ </m.div>
109
+ </div>
110
+ </div>
111
+ <ThreadSuggestions />
112
+ </div>
113
+ );
114
+ };
115
+
116
+ const ThreadSuggestions: FC = () => {
117
+ return (
118
+ <div className="aui-thread-welcome-suggestions grid w-full gap-2 pb-4 @md:grid-cols-2">
119
+ {[
120
+ {
121
+ title: "Tell me about yourself",
122
+ label: "",
123
+ action: "Tell me about yourself",
124
+ },
125
+ {
126
+ title: "Explain React hooks",
127
+ label: "like useState and useEffect",
128
+ action: "Explain React hooks like useState and useEffect",
129
+ },
130
+ {
131
+ title: "Write a SQL query",
132
+ label: "to find top customers",
133
+ action: "Write a SQL query to find top customers",
134
+ },
135
+ {
136
+ title: "Create a meal plan",
137
+ label: "for healthy weight loss",
138
+ action: "Create a meal plan for healthy weight loss",
139
+ },
140
+ ].map((suggestedAction, index) => (
141
+ <m.div
142
+ initial={{ opacity: 0, y: 20 }}
143
+ animate={{ opacity: 1, y: 0 }}
144
+ exit={{ opacity: 0, y: 20 }}
145
+ transition={{ delay: 0.05 * index }}
146
+ key={`suggested-action-${suggestedAction.title}-${index}`}
147
+ className="aui-thread-welcome-suggestion-display [&:nth-child(n+3)]:hidden @md:[&:nth-child(n+3)]:block"
148
+ >
149
+ <ThreadPrimitive.Suggestion
150
+ prompt={suggestedAction.action}
151
+ send
152
+ asChild
153
+ >
154
+ <Button
155
+ variant="ghost"
156
+ className="aui-thread-welcome-suggestion h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-left text-sm @md:flex-col dark:hover:bg-accent/60"
157
+ aria-label={suggestedAction.action}
158
+ >
159
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
160
+ {suggestedAction.title}
161
+ </span>
162
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
163
+ {suggestedAction.label}
164
+ </span>
165
+ </Button>
166
+ </ThreadPrimitive.Suggestion>
167
+ </m.div>
168
+ ))}
169
+ </div>
170
+ );
171
+ };
172
+
173
+ const Composer: FC = () => {
174
+ return (
175
+ <div className="aui-composer-wrapper sticky bottom-0 mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
176
+ <ThreadScrollToBottom />
177
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col rounded-3xl border border-border bg-muted px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)] dark:border-muted-foreground/15">
178
+ <ComposerAttachments />
179
+ <ComposerPrimitive.Input
180
+ placeholder="Send a message..."
181
+ className="aui-composer-input mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none placeholder:text-muted-foreground focus:outline-primary"
182
+ rows={1}
183
+ autoFocus
184
+ aria-label="Message input"
185
+ />
186
+ <ComposerAction />
187
+ </ComposerPrimitive.Root>
188
+ </div>
189
+ );
190
+ };
191
+
192
+ const ComposerAction: FC = () => {
193
+ return (
194
+ <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between">
195
+ <ComposerAddAttachment />
196
+
197
+ <ThreadPrimitive.If running={false}>
198
+ <ComposerPrimitive.Send asChild>
199
+ <TooltipIconButton
200
+ tooltip="Send message"
201
+ side="bottom"
202
+ type="submit"
203
+ variant="default"
204
+ size="icon"
205
+ className="aui-composer-send size-[34px] rounded-full p-1"
206
+ aria-label="Send message"
207
+ >
208
+ <ArrowUpIcon className="aui-composer-send-icon size-5" />
209
+ </TooltipIconButton>
210
+ </ComposerPrimitive.Send>
211
+ </ThreadPrimitive.If>
212
+
213
+ <ThreadPrimitive.If running>
214
+ <ComposerPrimitive.Cancel asChild>
215
+ <Button
216
+ type="button"
217
+ variant="default"
218
+ size="icon"
219
+ className="aui-composer-cancel size-[34px] rounded-full border border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90"
220
+ aria-label="Stop generating"
221
+ >
222
+ <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" />
223
+ </Button>
224
+ </ComposerPrimitive.Cancel>
225
+ </ThreadPrimitive.If>
226
+ </div>
227
+ );
228
+ };
229
+
230
+ const MessageError: FC = () => {
231
+ return (
232
+ <MessagePrimitive.Error>
233
+ <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive dark:bg-destructive/5 dark:text-red-200">
234
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
235
+ </ErrorPrimitive.Root>
236
+ </MessagePrimitive.Error>
237
+ );
238
+ };
239
+
240
+ const AssistantMessage: FC = () => {
241
+ return (
242
+ <MessagePrimitive.Root asChild>
243
+ <div
244
+ className="aui-assistant-message-root relative mx-auto w-full max-w-[var(--thread-max-width)] animate-in py-4 duration-150 ease-out fade-in slide-in-from-bottom-1 last:mb-24"
245
+ data-role="assistant"
246
+ >
247
+ <div className="aui-assistant-message-content mx-2 leading-7 break-words text-foreground">
248
+ <MessagePrimitive.Parts
249
+ components={{
250
+ Text: MarkdownText,
251
+ tools: { Fallback: ToolFallback },
252
+ }}
253
+ />
254
+ <MessageError />
255
+ </div>
256
+
257
+ <div className="aui-assistant-message-footer mt-2 ml-2 flex">
258
+ <BranchPicker />
259
+ <AssistantActionBar />
260
+ </div>
261
+ </div>
262
+ </MessagePrimitive.Root>
263
+ );
264
+ };
265
+
266
+ const AssistantActionBar: FC = () => {
267
+ return (
268
+ <ActionBarPrimitive.Root
269
+ hideWhenRunning
270
+ autohide="not-last"
271
+ autohideFloat="single-branch"
272
+ className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
273
+ >
274
+ <ActionBarPrimitive.Copy asChild>
275
+ <TooltipIconButton tooltip="Copy">
276
+ <MessagePrimitive.If copied>
277
+ <CheckIcon />
278
+ </MessagePrimitive.If>
279
+ <MessagePrimitive.If copied={false}>
280
+ <CopyIcon />
281
+ </MessagePrimitive.If>
282
+ </TooltipIconButton>
283
+ </ActionBarPrimitive.Copy>
284
+ <ActionBarPrimitive.Reload asChild>
285
+ <TooltipIconButton tooltip="Refresh">
286
+ <RefreshCwIcon />
287
+ </TooltipIconButton>
288
+ </ActionBarPrimitive.Reload>
289
+ </ActionBarPrimitive.Root>
290
+ );
291
+ };
292
+
293
+ const UserMessage: FC = () => {
294
+ return (
295
+ <MessagePrimitive.Root asChild>
296
+ <div
297
+ className="aui-user-message-root mx-auto grid w-full max-w-[var(--thread-max-width)] animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 px-2 py-4 duration-150 ease-out fade-in slide-in-from-bottom-1 first:mt-3 last:mb-5 [&:where(>*)]:col-start-2"
298
+ data-role="user"
299
+ >
300
+ <UserMessageAttachments />
301
+
302
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
303
+ <div className="aui-user-message-content rounded-3xl bg-muted px-5 py-2.5 break-words text-foreground">
304
+ <MessagePrimitive.Parts />
305
+ </div>
306
+ <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
307
+ <UserActionBar />
308
+ </div>
309
+ </div>
310
+
311
+ <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
312
+ </div>
313
+ </MessagePrimitive.Root>
314
+ );
315
+ };
316
+
317
+ const UserActionBar: FC = () => {
318
+ return (
319
+ <ActionBarPrimitive.Root
320
+ hideWhenRunning
321
+ autohide="not-last"
322
+ className="aui-user-action-bar-root flex flex-col items-end"
323
+ >
324
+ <ActionBarPrimitive.Edit asChild>
325
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
326
+ <PencilIcon />
327
+ </TooltipIconButton>
328
+ </ActionBarPrimitive.Edit>
329
+ </ActionBarPrimitive.Root>
330
+ );
331
+ };
332
+
333
+ const EditComposer: FC = () => {
334
+ return (
335
+ <div className="aui-edit-composer-wrapper mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-2 first:mt-4">
336
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-7/8 flex-col rounded-xl bg-muted">
337
+ <ComposerPrimitive.Input
338
+ className="aui-edit-composer-input flex min-h-[60px] w-full resize-none bg-transparent p-4 text-foreground outline-none"
339
+ autoFocus
340
+ />
341
+
342
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end">
343
+ <ComposerPrimitive.Cancel asChild>
344
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
345
+ Cancel
346
+ </Button>
347
+ </ComposerPrimitive.Cancel>
348
+ <ComposerPrimitive.Send asChild>
349
+ <Button size="sm" aria-label="Update message">
350
+ Update
351
+ </Button>
352
+ </ComposerPrimitive.Send>
353
+ </div>
354
+ </ComposerPrimitive.Root>
355
+ </div>
356
+ );
357
+ };
358
+
359
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
360
+ className,
361
+ ...rest
362
+ }) => {
363
+ return (
364
+ <BranchPickerPrimitive.Root
365
+ hideWhenSingleBranch
366
+ className={cn(
367
+ "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-xs text-muted-foreground",
368
+ className,
369
+ )}
370
+ {...rest}
371
+ >
372
+ <BranchPickerPrimitive.Previous asChild>
373
+ <TooltipIconButton tooltip="Previous">
374
+ <ChevronLeftIcon />
375
+ </TooltipIconButton>
376
+ </BranchPickerPrimitive.Previous>
377
+ <span className="aui-branch-picker-state font-medium">
378
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
379
+ </span>
380
+ <BranchPickerPrimitive.Next asChild>
381
+ <TooltipIconButton tooltip="Next">
382
+ <ChevronRightIcon />
383
+ </TooltipIconButton>
384
+ </BranchPickerPrimitive.Next>
385
+ </BranchPickerPrimitive.Root>
386
+ );
387
+ };
components/assistant-ui/threadlist-sidebar.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ // SPDX-License-Identifier: MIT
4
+ //
5
+
6
+ import * as React from "react";
7
+ import { MessagesSquare } from "lucide-react";
8
+ import Link from "next/link";
9
+ import {
10
+ Sidebar,
11
+ SidebarContent,
12
+ SidebarFooter,
13
+ SidebarHeader,
14
+ SidebarMenu,
15
+ SidebarMenuButton,
16
+ SidebarMenuItem,
17
+ SidebarRail,
18
+ } from "@/components/ui/sidebar";
19
+ import { ThreadList } from "@/components/assistant-ui/thread-list";
20
+
21
+ export function ThreadListSidebar({
22
+ ...props
23
+ }: React.ComponentProps<typeof Sidebar>) {
24
+ return (
25
+ <Sidebar {...props}>
26
+ <SidebarHeader className="aui-sidebar-header mb-2 border-b">
27
+ <div className="aui-sidebar-header-content flex items-center justify-between">
28
+ <SidebarMenu>
29
+ <SidebarMenuItem>
30
+ <SidebarMenuButton size="lg" asChild>
31
+ <Link
32
+ href="https://umint-ai.hf.space"
33
+ target="_blank"
34
+ rel="noopener noreferrer"
35
+ >
36
+ <div className="aui-sidebar-header-icon-wrapper flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
37
+ <MessagesSquare className="aui-sidebar-header-icon size-4" />
38
+ </div>
39
+ <div className="aui-sidebar-header-heading mr-6 flex flex-col gap-0.5 leading-none">
40
+ <span className="aui-sidebar-header-title font-semibold">
41
+ Demo Playground
42
+ </span>
43
+ </div>
44
+ </Link>
45
+ </SidebarMenuButton>
46
+ </SidebarMenuItem>
47
+ </SidebarMenu>
48
+ </div>
49
+ </SidebarHeader>
50
+ <SidebarContent className="aui-sidebar-content px-2">
51
+ <ThreadList />
52
+ </SidebarContent>
53
+ <SidebarRail />
54
+ <SidebarFooter className="aui-sidebar-footer border-t">
55
+ <SidebarMenu>
56
+ <SidebarMenuItem>
57
+ <SidebarMenuButton size="lg" asChild>
58
+ <Link
59
+ href="https://umint-openwebui.hf.space"
60
+ target="_blank"
61
+ rel="noopener noreferrer"
62
+ >
63
+ <div className="aui-sidebar-footer-heading flex flex-col gap-0.5 leading-none">
64
+ <span className="aui-sidebar-footer-title font-semibold inline-flex items-center gap-1">
65
+ Switch to the full version
66
+ <svg
67
+ width="7.5"
68
+ height="7.5"
69
+ viewBox="0 0 12 12"
70
+ fill="none"
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ className="inline-block"
73
+ >
74
+ <path
75
+ d="M3.5 1.5L8.5 1.5L8.5 6.5M8.5 1.5L1.5 8.5L8.5 1.5Z"
76
+ stroke="currentColor"
77
+ strokeWidth="1.5"
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ />
81
+ </svg>
82
+ </span>
83
+ <span className="text-[8.5px] text-muted-foreground leading-tight">
84
+ for a more advanced experience, rich in features, and access to a more complete set of premium AI models
85
+ </span>
86
+ </div>
87
+ </Link>
88
+ </SidebarMenuButton>
89
+ </SidebarMenuItem>
90
+ </SidebarMenu>
91
+ </SidebarFooter>
92
+ </Sidebar>
93
+ );
94
+ }
components/assistant-ui/tool-fallback.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
2
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
3
+ import { useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+
6
+ export const ToolFallback: ToolCallMessagePartComponent = ({
7
+ toolName,
8
+ argsText,
9
+ result,
10
+ }) => {
11
+ const [isCollapsed, setIsCollapsed] = useState(true);
12
+ return (
13
+ <div className="aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
14
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
15
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
16
+ <p className="aui-tool-fallback-title flex-grow">
17
+ Used tool: <b>{toolName}</b>
18
+ </p>
19
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
20
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
21
+ </Button>
22
+ </div>
23
+ {!isCollapsed && (
24
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
25
+ <div className="aui-tool-fallback-args-root px-4">
26
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
27
+ {argsText}
28
+ </pre>
29
+ </div>
30
+ {result !== undefined && (
31
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
32
+ <p className="aui-tool-fallback-result-header font-semibold">
33
+ Result:
34
+ </p>
35
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
36
+ {typeof result === "string"
37
+ ? result
38
+ : JSON.stringify(result, null, 2)}
39
+ </pre>
40
+ </div>
41
+ )}
42
+ </div>
43
+ )}
44
+ </div>
45
+ );
46
+ };
components/assistant-ui/tooltip-icon-button.tsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { ComponentPropsWithRef, forwardRef } from "react";
4
+ import { Slottable } from "@radix-ui/react-slot";
5
+
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger,
10
+ } from "@/components/ui/tooltip";
11
+ import { Button } from "@/components/ui/button";
12
+ import { cn } from "@/lib/utils";
13
+
14
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
15
+ tooltip: string;
16
+ side?: "top" | "bottom" | "left" | "right";
17
+ };
18
+
19
+ export const TooltipIconButton = forwardRef<
20
+ HTMLButtonElement,
21
+ TooltipIconButtonProps
22
+ >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
23
+ return (
24
+ <Tooltip>
25
+ <TooltipTrigger asChild>
26
+ <Button
27
+ variant="ghost"
28
+ size="icon"
29
+ {...rest}
30
+ className={cn("aui-button-icon size-6 p-1", className)}
31
+ ref={ref}
32
+ >
33
+ <Slottable>{children}</Slottable>
34
+ <span className="aui-sr-only sr-only">{tooltip}</span>
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent side={side}>{tooltip}</TooltipContent>
38
+ </Tooltip>
39
+ );
40
+ });
41
+
42
+ TooltipIconButton.displayName = "TooltipIconButton";
components/ui/avatar.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function Avatar({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ className={cn(
16
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function AvatarImage({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
+ return (
29
+ <AvatarPrimitive.Image
30
+ data-slot="avatar-image"
31
+ className={cn("aspect-square size-full", className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AvatarFallback({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
+ return (
42
+ <AvatarPrimitive.Fallback
43
+ data-slot="avatar-fallback"
44
+ className={cn(
45
+ "flex size-full items-center justify-center rounded-full bg-muted",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ export { Avatar, AvatarImage, AvatarFallback };
components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
8
+ return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
9
+ }
10
+
11
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
12
+ return (
13
+ <ol
14
+ data-slot="breadcrumb-list"
15
+ className={cn(
16
+ "flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
25
+ return (
26
+ <li
27
+ data-slot="breadcrumb-item"
28
+ className={cn("inline-flex items-center gap-1.5", className)}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function BreadcrumbLink({
35
+ asChild,
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<"a"> & {
39
+ asChild?: boolean;
40
+ }) {
41
+ const Comp = asChild ? Slot : "a";
42
+
43
+ return (
44
+ <Comp
45
+ data-slot="breadcrumb-link"
46
+ className={cn("transition-colors hover:text-foreground", className)}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
53
+ return (
54
+ <span
55
+ data-slot="breadcrumb-page"
56
+ role="link"
57
+ aria-disabled="true"
58
+ aria-current="page"
59
+ className={cn("font-normal text-foreground", className)}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function BreadcrumbSeparator({
66
+ children,
67
+ className,
68
+ ...props
69
+ }: React.ComponentProps<"li">) {
70
+ return (
71
+ <li
72
+ data-slot="breadcrumb-separator"
73
+ role="presentation"
74
+ aria-hidden="true"
75
+ className={cn("[&>svg]:size-3.5", className)}
76
+ {...props}
77
+ >
78
+ {children ?? <ChevronRight />}
79
+ </li>
80
+ );
81
+ }
82
+
83
+ function BreadcrumbEllipsis({
84
+ className,
85
+ ...props
86
+ }: React.ComponentProps<"span">) {
87
+ return (
88
+ <span
89
+ data-slot="breadcrumb-ellipsis"
90
+ role="presentation"
91
+ aria-hidden="true"
92
+ className={cn("flex size-9 items-center justify-center", className)}
93
+ {...props}
94
+ >
95
+ <MoreHorizontal className="size-4" />
96
+ <span className="sr-only">More</span>
97
+ </span>
98
+ );
99
+ }
100
+
101
+ export {
102
+ Breadcrumb,
103
+ BreadcrumbList,
104
+ BreadcrumbItem,
105
+ BreadcrumbLink,
106
+ BreadcrumbPage,
107
+ BreadcrumbSeparator,
108
+ BreadcrumbEllipsis,
109
+ };
components/ui/button.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16
+ outline:
17
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ },
36
+ );
37
+
38
+ function Button({
39
+ className,
40
+ variant,
41
+ size,
42
+ asChild = false,
43
+ ...props
44
+ }: React.ComponentProps<"button"> &
45
+ VariantProps<typeof buttonVariants> & {
46
+ asChild?: boolean;
47
+ }) {
48
+ const Comp = asChild ? Slot : "button";
49
+
50
+ return (
51
+ <Comp
52
+ data-slot="button"
53
+ className={cn(buttonVariants({ variant, size, className }))}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ export { Button, buttonVariants };
components/ui/dialog.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { XIcon } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Dialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
12
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
13
+ }
14
+
15
+ function DialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
18
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
19
+ }
20
+
21
+ function DialogPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
24
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
25
+ }
26
+
27
+ function DialogClose({
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
30
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
31
+ }
32
+
33
+ function DialogOverlay({
34
+ className,
35
+ ...props
36
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
37
+ return (
38
+ <DialogPrimitive.Overlay
39
+ data-slot="dialog-overlay"
40
+ className={cn(
41
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ function DialogContent({
50
+ className,
51
+ children,
52
+ showCloseButton = true,
53
+ ...props
54
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
55
+ showCloseButton?: boolean;
56
+ }) {
57
+ return (
58
+ <DialogPortal data-slot="dialog-portal">
59
+ <DialogOverlay />
60
+ <DialogPrimitive.Content
61
+ data-slot="dialog-content"
62
+ className={cn(
63
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
64
+ className,
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {showCloseButton && (
70
+ <DialogPrimitive.Close
71
+ data-slot="dialog-close"
72
+ className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
73
+ >
74
+ <XIcon />
75
+ <span className="sr-only">Close</span>
76
+ </DialogPrimitive.Close>
77
+ )}
78
+ </DialogPrimitive.Content>
79
+ </DialogPortal>
80
+ );
81
+ }
82
+
83
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="dialog-header"
87
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+
93
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
94
+ return (
95
+ <div
96
+ data-slot="dialog-footer"
97
+ className={cn(
98
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
99
+ className,
100
+ )}
101
+ {...props}
102
+ />
103
+ );
104
+ }
105
+
106
+ function DialogTitle({
107
+ className,
108
+ ...props
109
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
110
+ return (
111
+ <DialogPrimitive.Title
112
+ data-slot="dialog-title"
113
+ className={cn("text-lg leading-none font-semibold", className)}
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ function DialogDescription({
120
+ className,
121
+ ...props
122
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
123
+ return (
124
+ <DialogPrimitive.Description
125
+ data-slot="dialog-description"
126
+ className={cn("text-sm text-muted-foreground", className)}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ export {
133
+ Dialog,
134
+ DialogClose,
135
+ DialogContent,
136
+ DialogDescription,
137
+ DialogFooter,
138
+ DialogHeader,
139
+ DialogOverlay,
140
+ DialogPortal,
141
+ DialogTitle,
142
+ DialogTrigger,
143
+ };
components/ui/input.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
12
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
13
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
components/ui/separator.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function Separator({
9
+ className,
10
+ orientation = "horizontal",
11
+ decorative = true,
12
+ ...props
13
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14
+ return (
15
+ <SeparatorPrimitive.Root
16
+ data-slot="separator"
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ export { Separator };