BarBar288 commited on
Commit
27127dd
·
verified ·
1 Parent(s): a4c135a

Upload 122 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. OpenAIChatAssistant/.config/configstore/firebase-tools.json +15 -0
  3. OpenAIChatAssistant/.config/configstore/update-notifier-firebase-tools.json +4 -0
  4. OpenAIChatAssistant/.config/gh/config.yml +17 -0
  5. OpenAIChatAssistant/.config/gh/hosts.yml +7 -0
  6. OpenAIChatAssistant/.github/workflows/static.yml +43 -0
  7. OpenAIChatAssistant/.gitignore +38 -0
  8. OpenAIChatAssistant/.replit +45 -0
  9. OpenAIChatAssistant/OpenChat/LICENSE +21 -0
  10. OpenAIChatAssistant/OpenChatAI/.gitattributes +35 -0
  11. OpenAIChatAssistant/OpenChatAI/README.md +10 -0
  12. OpenAIChatAssistant/OpenChatAI/index.html +19 -0
  13. OpenAIChatAssistant/OpenChatAI/style.css +28 -0
  14. OpenAIChatAssistant/README.md +1 -0
  15. OpenAIChatAssistant/attached_assets/content-1746193714894.md +7 -0
  16. OpenAIChatAssistant/attached_assets/content-1746193784985.md +1467 -0
  17. OpenAIChatAssistant/attached_assets/content-1746448603342.md +23 -0
  18. OpenAIChatAssistant/attached_assets/screenshot-1746193704446.png +0 -0
  19. OpenAIChatAssistant/attached_assets/screenshot-1746193776733.png +0 -0
  20. OpenAIChatAssistant/client/index.html +13 -0
  21. OpenAIChatAssistant/client/src/App.tsx +43 -0
  22. OpenAIChatAssistant/client/src/components/ChatHistory.tsx +126 -0
  23. OpenAIChatAssistant/client/src/components/ChatInputForm.tsx +72 -0
  24. OpenAIChatAssistant/client/src/components/ConnectionStatus.tsx +64 -0
  25. OpenAIChatAssistant/client/src/components/ConversationSidebar.tsx +315 -0
  26. OpenAIChatAssistant/client/src/components/ImageGenerator.tsx +319 -0
  27. OpenAIChatAssistant/client/src/components/TypingIndicator.tsx +30 -0
  28. OpenAIChatAssistant/client/src/components/UserSettingsModal.tsx +348 -0
  29. OpenAIChatAssistant/client/src/components/VideoGenerator.tsx +169 -0
  30. OpenAIChatAssistant/client/src/components/ui/accordion.tsx +56 -0
  31. OpenAIChatAssistant/client/src/components/ui/alert-dialog.tsx +139 -0
  32. OpenAIChatAssistant/client/src/components/ui/alert.tsx +59 -0
  33. OpenAIChatAssistant/client/src/components/ui/aspect-ratio.tsx +5 -0
  34. OpenAIChatAssistant/client/src/components/ui/avatar.tsx +50 -0
  35. OpenAIChatAssistant/client/src/components/ui/badge.tsx +36 -0
  36. OpenAIChatAssistant/client/src/components/ui/breadcrumb.tsx +115 -0
  37. OpenAIChatAssistant/client/src/components/ui/button.tsx +56 -0
  38. OpenAIChatAssistant/client/src/components/ui/calendar.tsx +68 -0
  39. OpenAIChatAssistant/client/src/components/ui/card.tsx +79 -0
  40. OpenAIChatAssistant/client/src/components/ui/carousel.tsx +260 -0
  41. OpenAIChatAssistant/client/src/components/ui/chart.tsx +365 -0
  42. OpenAIChatAssistant/client/src/components/ui/checkbox.tsx +28 -0
  43. OpenAIChatAssistant/client/src/components/ui/collapsible.tsx +11 -0
  44. OpenAIChatAssistant/client/src/components/ui/command.tsx +151 -0
  45. OpenAIChatAssistant/client/src/components/ui/context-menu.tsx +198 -0
  46. OpenAIChatAssistant/client/src/components/ui/dialog.tsx +120 -0
  47. OpenAIChatAssistant/client/src/components/ui/drawer.tsx +118 -0
  48. OpenAIChatAssistant/client/src/components/ui/dropdown-menu.tsx +198 -0
  49. OpenAIChatAssistant/client/src/components/ui/form.tsx +176 -0
  50. OpenAIChatAssistant/client/src/components/ui/hover-card.tsx +29 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ OpenAIChatAssistant/generated-icon.png filter=lfs diff=lfs merge=lfs -text
OpenAIChatAssistant/.config/configstore/firebase-tools.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "motd": {
3
+ "cloudBuildErrorAfter": 1594252800000,
4
+ "cloudBuildWarnAfter": 1590019200000,
5
+ "defaultNode10After": 1594252800000,
6
+ "minVersion": "3.0.5",
7
+ "node8DeploysDisabledAfter": 1613390400000,
8
+ "node8RuntimeDisabledAfter": 1615809600000,
9
+ "node8WarnAfter": 1600128000000,
10
+ "fetched": 1746200259916
11
+ },
12
+ "usage": true,
13
+ "analytics-uuid": "39420299-5030-4806-bca2-b74705ec958b",
14
+ "lastError": 1746202219061
15
+ }
OpenAIChatAssistant/.config/configstore/update-notifier-firebase-tools.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "optOut": false,
3
+ "lastUpdateCheck": 1746200259769
4
+ }
OpenAIChatAssistant/.config/gh/config.yml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # The current version of the config schema
2
+ version: 1
3
+ # What protocol to use when performing git operations. Supported values: ssh, https
4
+ git_protocol: https
5
+ # What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment.
6
+ editor:
7
+ # When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
8
+ prompt: enabled
9
+ # A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager.
10
+ pager:
11
+ # Aliases allow you to create nicknames for gh commands
12
+ aliases:
13
+ co: pr checkout
14
+ # The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
15
+ http_unix_socket:
16
+ # What web browser gh should use when opening URLs. If blank, will refer to environment.
17
+ browser:
OpenAIChatAssistant/.config/gh/hosts.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ github.com:
2
+ users:
3
+ Bella288:
4
+ oauth_token: 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
5
+ git_protocol: https
6
+ oauth_token: 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
7
+ user: Bella288
OpenAIChatAssistant/.github/workflows/static.yml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple workflow for deploying static content to GitHub Pages
2
+ name: Deploy static content to Pages
3
+
4
+ on:
5
+ # Runs on pushes targeting the default branch
6
+ push:
7
+ branches: ["main"]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20
+ concurrency:
21
+ group: "pages"
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ # Single deploy job since we're just deploying
26
+ deploy:
27
+ environment:
28
+ name: github-pages
29
+ url: ${{ steps.deployment.outputs.page_url }}
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@v4
34
+ - name: Setup Pages
35
+ uses: actions/configure-pages@v5
36
+ - name: Upload artifact
37
+ uses: actions/upload-pages-artifact@v3
38
+ with:
39
+ # Upload entire repository
40
+ path: '.'
41
+ - name: Deploy to GitHub Pages
42
+ id: deployment
43
+ uses: actions/deploy-pages@v4
OpenAIChatAssistant/.gitignore ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Dependencies
3
+ node_modules
4
+ .pnp
5
+ .pnp.js
6
+
7
+ # Production
8
+ dist
9
+ build
10
+
11
+ # Testing
12
+ coverage
13
+
14
+ # Environment
15
+ .env
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ # Logs
22
+ npm-debug.log*
23
+ yarn-debug.log*
24
+ yarn-error.log*
25
+
26
+ # Editor/IDE
27
+ .vscode
28
+ .idea
29
+ *.swp
30
+ *.swo
31
+
32
+ # OS
33
+ .DS_Store
34
+ Thumbs.db
35
+
36
+ # Replit specific
37
+ .replit
38
+ .config
OpenAIChatAssistant/.replit ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ modules = ["nodejs-20", "web", "postgresql-16", "python-3.11"]
2
+ run = "npm run dev"
3
+ hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"]
4
+
5
+ [nix]
6
+ channel = "stable-24_05"
7
+ packages = ["gh"]
8
+
9
+ [deployment]
10
+ deploymentTarget = "autoscale"
11
+ build = ["sh", "-c", "npm run build"]
12
+ run = ["sh", "-c", "NODE_ENV=production tsx server/index.ts"]
13
+
14
+ [[ports]]
15
+ localPort = 5000
16
+ externalPort = 80
17
+
18
+ [[ports]]
19
+ localPort = 9005
20
+ externalPort = 3000
21
+
22
+ [[ports]]
23
+ localPort = 9006
24
+ externalPort = 3001
25
+
26
+ [workflows]
27
+ runButton = "Project"
28
+
29
+ [[workflows.workflow]]
30
+ name = "Project"
31
+ mode = "parallel"
32
+ author = "agent"
33
+
34
+ [[workflows.workflow.tasks]]
35
+ task = "workflow.run"
36
+ args = "Start application"
37
+
38
+ [[workflows.workflow]]
39
+ name = "Start application"
40
+ author = "agent"
41
+
42
+ [[workflows.workflow.tasks]]
43
+ task = "shell.exec"
44
+ args = "npm run dev"
45
+ waitForPort = 5000
OpenAIChatAssistant/OpenChat/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bella Lawrence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
OpenAIChatAssistant/OpenChatAI/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
OpenAIChatAssistant/OpenChatAI/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: OpenChatAI
3
+ emoji: 🦀
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: static
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
OpenAIChatAssistant/OpenChatAI/index.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width" />
6
+ <title>My static Space</title>
7
+ <link rel="stylesheet" href="style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="card">
11
+ <h1>Welcome to your static Space!</h1>
12
+ <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
+ <p>
14
+ Also don't forget to check the
15
+ <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
+ </p>
17
+ </div>
18
+ </body>
19
+ </html>
OpenAIChatAssistant/OpenChatAI/style.css ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ padding: 2rem;
3
+ font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
+ }
5
+
6
+ h1 {
7
+ font-size: 16px;
8
+ margin-top: 0;
9
+ }
10
+
11
+ p {
12
+ color: rgb(107, 114, 128);
13
+ font-size: 15px;
14
+ margin-bottom: 10px;
15
+ margin-top: 5px;
16
+ }
17
+
18
+ .card {
19
+ max-width: 620px;
20
+ margin: 0 auto;
21
+ padding: 16px;
22
+ border: 1px solid lightgray;
23
+ border-radius: 16px;
24
+ }
25
+
26
+ .card p:last-child {
27
+ margin-bottom: 0;
28
+ }
OpenAIChatAssistant/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # OpenChat
OpenAIChatAssistant/attached_assets/content-1746193714894.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [Skip to content](https://vercel.com/login?next=%2Fbella288s-projects%2Fchatopen%2FE9m12YJ7cJYv5DZawZpXn98haWBJ#geist-skip-nav)
2
+
3
+ # Log in to Vercel
4
+
5
+ Continue withGitHubContinue withGitLabContinue withBitbucket
6
+
7
+ [Don't have an account? Sign Up](https://vercel.com/signup?next=%2Fbella288s-projects%2Fchatopen%2FE9m12YJ7cJYv5DZawZpXn98haWBJ)
OpenAIChatAssistant/attached_assets/content-1746193784985.md ADDED
@@ -0,0 +1,1467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ var __defProp = Object.defineProperty;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+
14
+ // server/index.ts
15
+ import express2 from "express";
16
+
17
+ // server/routes.ts
18
+ import { createServer } from "http";
19
+
20
+ // shared/schema.ts
21
+ var schema_exports = {};
22
+ __export(schema_exports, {
23
+ conversationSchema: () => conversationSchema,
24
+ conversations: () => conversations,
25
+ insertConversationSchema: () => insertConversationSchema,
26
+ insertMessageSchema: () => insertMessageSchema,
27
+ insertUserSchema: () => insertUserSchema,
28
+ messageRoleSchema: () => messageRoleSchema,
29
+ messageSchema: () => messageSchema,
30
+ messages: () => messages,
31
+ personalityTypeSchema: () => personalityTypeSchema,
32
+ updateUserProfileSchema: () => updateUserProfileSchema,
33
+ users: () => users
34
+ });
35
+ import { pgTable, text, serial, integer, timestamp } from "drizzle-orm/pg-core";
36
+ import { createInsertSchema } from "drizzle-zod";
37
+ import { z } from "zod";
38
+ var users = pgTable("users", {
39
+ id: serial("id").primaryKey(),
40
+ username: text("username").notNull().unique(),
41
+ password: text("password").notNull(),
42
+ fullName: text("full_name"),
43
+ location: text("location"),
44
+ interests: text("interests").array(),
45
+ profession: text("profession"),
46
+ pets: text("pets"),
47
+ additionalInfo: text("additional_info"),
48
+ systemContext: text("system_context")
49
+ });
50
+ var insertUserSchema = createInsertSchema(users).pick({
51
+ username: true,
52
+ password: true
53
+ });
54
+ var updateUserProfileSchema = createInsertSchema(users).pick({
55
+ fullName: true,
56
+ location: true,
57
+ interests: true,
58
+ profession: true,
59
+ pets: true,
60
+ additionalInfo: true,
61
+ systemContext: true
62
+ }).partial();
63
+ var messages = pgTable("messages", {
64
+ id: serial("id").primaryKey(),
65
+ content: text("content").notNull(),
66
+ role: text("role").notNull(),
67
+ // 'user' or 'assistant'
68
+ conversationId: text("conversation_id").notNull(),
69
+ createdAt: timestamp("created_at").defaultNow().notNull()
70
+ });
71
+ var insertMessageSchema = createInsertSchema(messages).pick({
72
+ content: true,
73
+ role: true,
74
+ conversationId: true
75
+ });
76
+ var personalityTypeSchema = z.enum([\
77
+ "default",\
78
+ "professional",\
79
+ "friendly",\
80
+ "expert",\
81
+ "poetic",\
82
+ "concise"\
83
+ ]);
84
+ var conversations = pgTable("conversations", {
85
+ id: text("id").primaryKey(),
86
+ title: text("title").notNull(),
87
+ createdAt: timestamp("created_at").defaultNow().notNull(),
88
+ personality: text("personality").default("default").notNull(),
89
+ userId: integer("user_id").references(() => users.id)
90
+ });
91
+ var insertConversationSchema = createInsertSchema(conversations).pick({
92
+ id: true,
93
+ title: true,
94
+ personality: true,
95
+ userId: true
96
+ });
97
+ var messageRoleSchema = z.enum(["user", "assistant", "system"]);
98
+ var messageSchema = z.object({
99
+ content: z.string(),
100
+ role: messageRoleSchema
101
+ });
102
+ var conversationSchema = z.object({
103
+ messages: z.array(messageSchema),
104
+ personality: personalityTypeSchema.optional().default("default"),
105
+ conversationId: z.string().optional(),
106
+ userId: z.number().optional()
107
+ });
108
+
109
+ // server/db.ts
110
+ import { Pool, neonConfig } from "@neondatabase/serverless";
111
+ import { drizzle } from "drizzle-orm/neon-serverless";
112
+ import ws from "ws";
113
+ neonConfig.webSocketConstructor = ws;
114
+ if (!process.env.DATABASE_URL) {
115
+ throw new Error(
116
+ "DATABASE_URL must be set. Did you forget to provision a database?"
117
+ );
118
+ }
119
+ var pool = new Pool({ connectionString: process.env.DATABASE_URL });
120
+ var db = drizzle({ client: pool, schema: schema_exports });
121
+
122
+ // server/storage.ts
123
+ import { eq, desc, asc } from "drizzle-orm";
124
+ import { nanoid } from "nanoid";
125
+ import session from "express-session";
126
+ import connectPgSimple from "connect-pg-simple";
127
+ var DatabaseStorage = class {
128
+ sessionStore;
129
+ constructor() {
130
+ const PgStore = connectPgSimple(session);
131
+ this.sessionStore = new PgStore({
132
+ pool,
133
+ createTableIfMissing: true
134
+ });
135
+ this.initializeDefaultConversation();
136
+ }
137
+ async initializeDefaultConversation() {
138
+ try {
139
+ const defaultConversation = await this.getConversation("default");
140
+ if (!defaultConversation) {
141
+ await this.createConversation({
142
+ id: "default",
143
+ title: "New Conversation",
144
+ personality: "general"
145
+ });
146
+ }
147
+ } catch (error) {
148
+ console.error("Error initializing default conversation:", error);
149
+ }
150
+ }
151
+ // Message operations
152
+ async getMessages(conversationId) {
153
+ return db.select().from(messages).where(eq(messages.conversationId, conversationId)).orderBy(asc(messages.createdAt));
154
+ }
155
+ async createMessage(insertMessage) {
156
+ const [newMessage] = await db.insert(messages).values({
157
+ ...insertMessage,
158
+ createdAt: /* @__PURE__ */ new Date()
159
+ }).returning();
160
+ return newMessage;
161
+ }
162
+ async deleteMessages(conversationId) {
163
+ await db.delete(messages).where(eq(messages.conversationId, conversationId));
164
+ }
165
+ // Conversation operations
166
+ async getConversation(id) {
167
+ const [conversation] = await db.select().from(conversations).where(eq(conversations.id, id));
168
+ return conversation;
169
+ }
170
+ async getConversations() {
171
+ return db.select().from(conversations).orderBy(desc(conversations.createdAt));
172
+ }
173
+ async createConversation(conversation) {
174
+ if (conversation.id) {
175
+ const existingConversation = await this.getConversation(conversation.id);
176
+ if (existingConversation) {
177
+ const [updatedConversation] = await db.update(conversations).set({
178
+ title: conversation.title,
179
+ personality: conversation.personality || "general",
180
+ // Only update userId if provided
181
+ ...conversation.userId && { userId: conversation.userId }
182
+ }).where(eq(conversations.id, conversation.id)).returning();
183
+ return updatedConversation;
184
+ }
185
+ }
186
+ const [newConversation] = await db.insert(conversations).values({
187
+ id: conversation.id || nanoid(),
188
+ title: conversation.title,
189
+ personality: conversation.personality || "general",
190
+ userId: conversation.userId,
191
+ // Include the user ID (can be null for unassociated conversations)
192
+ createdAt: /* @__PURE__ */ new Date()
193
+ }).returning();
194
+ return newConversation;
195
+ }
196
+ async deleteConversation(id) {
197
+ if (id === "default") {
198
+ return false;
199
+ }
200
+ try {
201
+ await this.deleteMessages(id);
202
+ const [deletedConversation] = await db.delete(conversations).where(eq(conversations.id, id)).returning();
203
+ return !!deletedConversation;
204
+ } catch (error) {
205
+ console.error("Error deleting conversation:", error);
206
+ return false;
207
+ }
208
+ }
209
+ async updateConversationPersonality(id, personality) {
210
+ const [updatedConversation] = await db.update(conversations).set({ personality }).where(eq(conversations.id, id)).returning();
211
+ return updatedConversation;
212
+ }
213
+ async updateConversationTitle(id, title) {
214
+ const [updatedConversation] = await db.update(conversations).set({ title }).where(eq(conversations.id, id)).returning();
215
+ return updatedConversation;
216
+ }
217
+ // User operations
218
+ async getUserProfile(id) {
219
+ const [user] = await db.select().from(users).where(eq(users.id, id));
220
+ return user;
221
+ }
222
+ async getUserByUsername(username) {
223
+ const [user] = await db.select().from(users).where(eq(users.username, username));
224
+ return user;
225
+ }
226
+ async createUser(userData) {
227
+ const [user] = await db.insert(users).values(userData).returning();
228
+ return user;
229
+ }
230
+ async updateUserProfile(id, profile) {
231
+ const { password, ...updateData } = profile;
232
+ const [updatedUser] = await db.update(users).set(updateData).where(eq(users.id, id)).returning();
233
+ return updatedUser;
234
+ }
235
+ // Filter conversations by user ID
236
+ async getUserConversations(userId) {
237
+ return db.select().from(conversations).where(eq(conversations.userId, userId)).orderBy(desc(conversations.createdAt));
238
+ }
239
+ };
240
+ var storage = new DatabaseStorage();
241
+
242
+ // server/auth.ts
243
+ import passport from "passport";
244
+
245
+ // server/session.ts
246
+ import session2 from "express-session";
247
+ import connectPg from "connect-pg-simple";
248
+ var PostgresSessionStore = connectPg(session2);
249
+ var sessionStore = new PostgresSessionStore({
250
+ pool,
251
+ createTableIfMissing: true,
252
+ tableName: "session"
253
+ // Default table name
254
+ });
255
+ function setupSession(app2) {
256
+ const sessionSecret = process.env.SESSION_SECRET || __require("crypto").randomBytes(32).toString("hex");
257
+ if (!process.env.SESSION_SECRET) {
258
+ console.warn("SESSION_SECRET not set in environment, using a random value");
259
+ process.env.SESSION_SECRET = sessionSecret;
260
+ }
261
+ const sessionConfig = {
262
+ store: sessionStore,
263
+ secret: sessionSecret,
264
+ resave: false,
265
+ saveUninitialized: false,
266
+ cookie: {
267
+ secure: process.env.NODE_ENV === "production",
268
+ // Use secure cookies in production
269
+ httpOnly: true,
270
+ maxAge: 1e3 * 60 * 60 * 24 * 7
271
+ // 1 week
272
+ }
273
+ };
274
+ if (process.env.NODE_ENV === "production") {
275
+ app2.set("trust proxy", 1);
276
+ if (sessionConfig.cookie) {
277
+ sessionConfig.cookie.secure = true;
278
+ sessionConfig.cookie.sameSite = "none";
279
+ }
280
+ }
281
+ app2.use(session2(sessionConfig));
282
+ }
283
+
284
+ // server/auth.ts
285
+ function setupAuth(app2) {
286
+ setupSession(app2);
287
+ app2.use(passport.initialize());
288
+ app2.use(passport.session());
289
+ passport.serializeUser((user, done) => {
290
+ done(null, user.id);
291
+ });
292
+ passport.deserializeUser(async (id, done) => {
293
+ try {
294
+ const user = await storage.getUserProfile(id);
295
+ done(null, user);
296
+ } catch (error) {
297
+ done(error);
298
+ }
299
+ });
300
+ app2.get("/api/auth/replit", async (req, res) => {
301
+ try {
302
+ const userId = req.headers["x-replit-user-id"];
303
+ const username = req.headers["x-replit-user-name"];
304
+ const profileImage = req.headers["x-replit-user-profile-image"];
305
+ const roles = req.headers["x-replit-user-roles"];
306
+ const teams = req.headers["x-replit-user-teams"];
307
+ if (!userId || !username) {
308
+ return res.status(401).json({ message: "Not authenticated with Replit" });
309
+ }
310
+ let user = await storage.getUserByUsername(username);
311
+ if (!user) {
312
+ user = await storage.createUser({
313
+ username,
314
+ password: userId,
315
+ system_context: `A chat with ${username}. User roles: ${roles || "none"}. Teams: ${teams || "none"}.`,
316
+ full_name: username,
317
+ interests: roles ? roles.split(",") : [],
318
+ location: "",
319
+ // Add default empty values for profile fields
320
+ profession: "",
321
+ pets: ""
322
+ });
323
+ } else {
324
+ user = await storage.updateUserProfile(user.id, {
325
+ full_name: username,
326
+ interests: roles ? roles.split(",") : [],
327
+ system_context: `A chat with ${username}. User roles: ${roles || "none"}. Teams: ${teams || "none"}.`
328
+ });
329
+ }
330
+ req.login(user, (err) => {
331
+ if (err) {
332
+ return res.status(500).json({ message: "Failed to login" });
333
+ }
334
+ const { password, ...userWithoutPassword } = user;
335
+ res.json(userWithoutPassword);
336
+ });
337
+ } catch (error) {
338
+ console.error("Replit auth error:", error);
339
+ res.status(500).json({ message: "Authentication failed" });
340
+ }
341
+ });
342
+ app2.get("/api/user", (req, res) => {
343
+ if (!req.isAuthenticated()) {
344
+ return res.status(401).json({ message: "Not authenticated" });
345
+ }
346
+ const { password, ...userWithoutPassword } = req.user;
347
+ res.json(userWithoutPassword);
348
+ });
349
+ app2.post("/api/logout", (req, res) => {
350
+ if (req.session) {
351
+ req.session.destroy((err) => {
352
+ if (err) {
353
+ console.error("Session destruction error:", err);
354
+ }
355
+ res.clearCookie("connect.sid");
356
+ res.status(200).json({ success: true });
357
+ });
358
+ } else {
359
+ res.status(200).json({ success: true });
360
+ }
361
+ });
362
+ app2.patch("/api/user/profile", async (req, res, next) => {
363
+ if (!req.isAuthenticated()) {
364
+ return res.status(401).json({ message: "Not authenticated" });
365
+ }
366
+ try {
367
+ const userId = req.user.id;
368
+ const updatedUser = await storage.updateUserProfile(userId, req.body);
369
+ if (!updatedUser) {
370
+ return res.status(404).json({ message: "User not found" });
371
+ }
372
+ const { password, ...userWithoutPassword } = updatedUser;
373
+ res.json(userWithoutPassword);
374
+ } catch (error) {
375
+ next(error);
376
+ }
377
+ });
378
+ }
379
+
380
+ // server/openai.ts
381
+ import OpenAI from "openai";
382
+
383
+ // server/fallbackChat.ts
384
+ import { InferenceClient } from "@huggingface/inference";
385
+ var novitaApiKey = process.env.NOVITA_API_KEY || "";
386
+ var huggingFaceClient = new InferenceClient(novitaApiKey);
387
+ var QWEN_MODEL = "Qwen/Qwen3-235B-A22B";
388
+ var MAX_TOKENS = 512;
389
+ var QWEN_SYSTEM_MESSAGE = `You are a helpful AI assistant. Provide clear, concise responses without showing your thinking process.
390
+ Do not use XML tags like <think> or </think> in your responses.
391
+ Keep your responses informative, friendly, and to the point.`;
392
+ function convertMessages(messages2, userSystemContext) {
393
+ let systemContent = QWEN_SYSTEM_MESSAGE;
394
+ if (userSystemContext) {
395
+ const getMatchValue = (match) => {
396
+ if (match && match[1]) {
397
+ return match[1].trim();
398
+ }
399
+ return null;
400
+ };
401
+ const nameMatches = [\
402
+ getMatchValue(userSystemContext.match(/name(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.']+)/i)),\
403
+ getMatchValue(userSystemContext.match(/My name is ([\w\s.']+)/i)),\
404
+ getMatchValue(userSystemContext.match(/I am ([\w\s.']+)/i)),\
405
+ getMatchValue(userSystemContext.match(/I'm ([\w\s.']+)/i))\
406
+ ].filter(Boolean);
407
+ const locationMatches = [\
408
+ getMatchValue(userSystemContext.match(/location(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.,]+)/i)),\
409
+ getMatchValue(userSystemContext.match(/(?:I live|I'm from|I reside) in ([\w\s.,]+)/i)),\
410
+ getMatchValue(userSystemContext.match(/from ([\w\s.,]+)/i))\
411
+ ].filter(Boolean);
412
+ const interestsMatches = [\
413
+ getMatchValue(userSystemContext.match(/interests(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.;{}]+)/i)),\
414
+ getMatchValue(userSystemContext.match(/(?:I like|I enjoy|I love) ([\w\s,.;]+)/i))\
415
+ ].filter(Boolean);
416
+ const professionMatches = [\
417
+ getMatchValue(userSystemContext.match(/profession(?:\s+is)?(?:\s*:\s*|\s+)([\w\s&,.-]+)/i)),\
418
+ getMatchValue(userSystemContext.match(/(?:I work as|I am a|I'm a) ([\w\s&,.-]+)/i)),\
419
+ getMatchValue(userSystemContext.match(/(?:I'm|I am) (?:a|an) ([\w\s&,.-]+)/i))\
420
+ ].filter(Boolean);
421
+ const petsMatches = [\
422
+ getMatchValue(userSystemContext.match(/pets?(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.()]+)/i)),\
423
+ getMatchValue(userSystemContext.match(/(?:I have|I own) (?:a pet|pets|a) ([\w\s,.()]+)/i))\
424
+ ].filter(Boolean);
425
+ const userName = nameMatches.length > 0 ? nameMatches[0] : null;
426
+ const userLocation = locationMatches.length > 0 ? locationMatches[0] : null;
427
+ const userInterests = interestsMatches.length > 0 ? interestsMatches[0] : null;
428
+ const userProfession = professionMatches.length > 0 ? professionMatches[0] : null;
429
+ const userPets = petsMatches.length > 0 ? petsMatches[0] : null;
430
+ let bellaInfo = "";
431
+ if (userSystemContext.includes("Bella Lawrence") || userName && userName.includes("Bella")) {
432
+ bellaInfo = `
433
+ - Your name is Bella Lawrence
434
+ - You live in Fort Wayne, Indiana
435
+ - Your interests include Python
436
+ - Your profession is Student
437
+ - You have pets named Barley (cat), Pebbles (dog), and Buttercup (rabbit)
438
+ `;
439
+ console.log("Using Bella's profile information directly");
440
+ }
441
+ let userInfo = "";
442
+ if (userName) userInfo += `- Your name is ${userName}
443
+ `;
444
+ if (userLocation) userInfo += `- You live in ${userLocation}
445
+ `;
446
+ if (userInterests) userInfo += `- Your interests include ${userInterests}
447
+ `;
448
+ if (userProfession) userInfo += `- Your profession is ${userProfession}
449
+ `;
450
+ if (userPets) userInfo += `- You have pets: ${userPets}
451
+ `;
452
+ const profileInfo = bellaInfo || userInfo || userSystemContext;
453
+ systemContent = `${QWEN_SYSTEM_MESSAGE}
454
+
455
+ IMPORTANT: The following is personal information about the user you are talking with.
456
+ You MUST remember these details and use them in your responses:
457
+
458
+ ${profileInfo}
459
+
460
+ INSTRUCTIONS:
461
+ 1. When asked "What's my name?" respond with the name listed above.
462
+ 2. When asked about name, location, interests, profession, or pets, use EXACTLY the information above.
463
+ 3. NEVER say you don't know or can't access this information - it's right above!
464
+ 4. Answer as if you've always known this information - don't say "according to your profile" or similar phrases.
465
+
466
+ REMEMBER: You already know the user's name and details. ALWAYS use this information when asked.`;
467
+ const hasNameQuestion = messages2.some((msg) => {
468
+ const content = msg.content.toLowerCase();
469
+ return content.includes("what's my name") || content.includes("what is my name") || content.includes("do you know my name") || content.includes("who am i");
470
+ });
471
+ if (hasNameQuestion) {
472
+ console.log("Detected name question - ensuring proper response");
473
+ systemContent += `
474
+
475
+ IMPORTANT REMINDER: The user has asked about their name. Their name is ${userName || "Bella Lawrence"}. DO NOT say you don't know their name.`;
476
+ }
477
+ console.log("Including enhanced user system context in fallback chat");
478
+ if (userName) console.log(`Extracted user name: ${userName}`);
479
+ if (userLocation) console.log(`Extracted user location: ${userLocation}`);
480
+ }
481
+ const formattedMessages = [{\
482
+ role: "system",\
483
+ content: systemContent\
484
+ }];
485
+ const compatibleMessages = messages2.filter((msg) => msg.role !== "system");
486
+ if (compatibleMessages.length === 0) {
487
+ formattedMessages.push({
488
+ role: "user",
489
+ content: "Hello, can you introduce yourself?"
490
+ });
491
+ return formattedMessages;
492
+ }
493
+ const lastMessage = compatibleMessages[compatibleMessages.length - 1];
494
+ if (lastMessage.role !== "user") {
495
+ compatibleMessages.push({
496
+ role: "user",
497
+ content: "Can you help me with this?"
498
+ });
499
+ }
500
+ formattedMessages.push(...compatibleMessages.map((msg) => ({
501
+ role: msg.role,
502
+ content: msg.content
503
+ })));
504
+ return formattedMessages;
505
+ }
506
+ async function generateFallbackResponse(messages2, userSystemContext) {
507
+ try {
508
+ console.log("Generating fallback response using Qwen model");
509
+ const formattedMessages = convertMessages(messages2, userSystemContext);
510
+ const response = await huggingFaceClient.chatCompletion({
511
+ provider: "novita",
512
+ model: QWEN_MODEL,
513
+ messages: formattedMessages,
514
+ max_tokens: MAX_TOKENS
515
+ });
516
+ if (response.choices && response.choices.length > 0 && response.choices[0].message) {
517
+ let content = response.choices[0].message.content || "";
518
+ content = content.replace(/<think>[\s\S]*?<\/think>/g, "");
519
+ content = content.replace(/<[^>]*>/g, "");
520
+ content = content.replace(/^\s+|\s+$/g, "");
521
+ content = content.replace(/\n{3,}/g, "\n\n");
522
+ if (!content.trim()) {
523
+ content = "I'm sorry, I couldn't generate a proper response.";
524
+ }
525
+ return `${content}
526
+
527
+ (Note: I'm currently operating in fallback mode using the Qwen model because the OpenAI API is unavailable)`;
528
+ } else {
529
+ throw new Error("No valid response from Qwen model");
530
+ }
531
+ } catch (error) {
532
+ console.error("Error generating response with Qwen model:", error);
533
+ return "I apologize, but I'm currently experiencing technical difficulties with both primary and fallback AI services. Please try again later.";
534
+ }
535
+ }
536
+ async function canUseOpenAI() {
537
+ try {
538
+ const apiKey = process.env.OPENAI_API_KEY;
539
+ return Boolean(apiKey && apiKey.startsWith("sk-") && apiKey.length > 20);
540
+ } catch (error) {
541
+ console.error("Error checking OpenAI API availability:", error);
542
+ return false;
543
+ }
544
+ }
545
+ async function canUseQwen() {
546
+ try {
547
+ return Boolean(novitaApiKey && novitaApiKey.length > 0);
548
+ } catch (error) {
549
+ console.error("Error checking Qwen availability:", error);
550
+ return false;
551
+ }
552
+ }
553
+
554
+ // server/openai.ts
555
+ var OPENAI_MODEL = "gpt-4o";
556
+ var openai = new OpenAI({
557
+ apiKey: process.env.OPENAI_API_KEY
558
+ });
559
+ var systemMessage = {
560
+ role: "system",
561
+ content: `You are a helpful AI assistant. Provide concise and accurate responses to user queries.
562
+ Your goal is to be informative and educational. Use clear language and provide examples where appropriate.
563
+ Always be respectful and considerate in your responses.`
564
+ };
565
+ var currentModel = "openai";
566
+ async function generateChatResponse(messages2, userSystemContext) {
567
+ try {
568
+ const openAIAvailable = await canUseOpenAI();
569
+ if (!openAIAvailable) {
570
+ const qwenAvailable = await canUseQwen();
571
+ if (qwenAvailable) {
572
+ if (currentModel !== "qwen") {
573
+ console.log("Switching to Qwen model as fallback");
574
+ currentModel = "qwen";
575
+ }
576
+ return await generateFallbackResponse(messages2, userSystemContext);
577
+ } else {
578
+ currentModel = "unavailable";
579
+ throw new Error("Both OpenAI and Qwen models are unavailable. Please check your API keys.");
580
+ }
581
+ }
582
+ if (currentModel !== "openai") {
583
+ console.log("Using OpenAI model");
584
+ currentModel = "openai";
585
+ }
586
+ let enhancedSystemMessage = { ...systemMessage };
587
+ if (userSystemContext) {
588
+ const nameMatch = userSystemContext.match(/name(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.']+)/i);
589
+ const locationMatch = userSystemContext.match(/location(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.,]+)/i);
590
+ const interestsMatch = userSystemContext.match(/interests(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.;]+)/i);
591
+ const professionMatch = userSystemContext.match(/profession(?:\s+is)?(?:\s*:\s*|\s+)([\w\s&,.-]+)/i);
592
+ const petsMatch = userSystemContext.match(/pets?(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.]+)/i);
593
+ let userInfo = "";
594
+ if (nameMatch) userInfo += `- Name: ${nameMatch[1].trim()}
595
+ `;
596
+ if (locationMatch) userInfo += `- Location: ${locationMatch[1].trim()}
597
+ `;
598
+ if (interestsMatch) userInfo += `- Interests: ${interestsMatch[1].trim()}
599
+ `;
600
+ if (professionMatch) userInfo += `- Profession: ${professionMatch[1].trim()}
601
+ `;
602
+ if (petsMatch) userInfo += `- Pets: ${petsMatch[1].trim()}
603
+ `;
604
+ enhancedSystemMessage.content = `${systemMessage.content}
605
+
606
+ USER PROFILE INFORMATION:
607
+ ${userInfo || userSystemContext}
608
+
609
+ IMPORTANT: You must remember these user details and incorporate them naturally in your responses when relevant.
610
+ When the user asks about their name, location, interests, profession, or pets, always answer using the information above.
611
+ Never say you don't know their personal details if they're listed above. Answer as if you already know this information.
612
+
613
+ Original system context provided by user:
614
+ ${userSystemContext}`;
615
+ console.log("Including enhanced user system context in OpenAI chat");
616
+ }
617
+ const conversationWithSystem = [enhancedSystemMessage, ...messages2];
618
+ const response = await openai.chat.completions.create({
619
+ model: OPENAI_MODEL,
620
+ messages: conversationWithSystem,
621
+ temperature: 0.7,
622
+ max_tokens: 1e3
623
+ });
624
+ return response.choices[0].message.content || "I'm sorry, I couldn't generate a response.";
625
+ } catch (error) {
626
+ console.error("AI Model error:", error);
627
+ if (currentModel === "openai") {
628
+ console.log("OpenAI API error, attempting to use Qwen fallback");
629
+ try {
630
+ const qwenAvailable = await canUseQwen();
631
+ if (qwenAvailable) {
632
+ currentModel = "qwen";
633
+ return await generateFallbackResponse(messages2, userSystemContext);
634
+ } else {
635
+ currentModel = "unavailable";
636
+ }
637
+ } catch (fallbackError) {
638
+ console.error("Qwen fallback also failed:", fallbackError);
639
+ currentModel = "unavailable";
640
+ }
641
+ }
642
+ if (error.response) {
643
+ const status = error.response.status;
644
+ if (status === 429) {
645
+ if (error.code === "insufficient_quota") {
646
+ throw new Error("OpenAI API quota exceeded. Your account may need a valid payment method or has reached its limit.");
647
+ } else {
648
+ throw new Error("Rate limit exceeded. Please try again later.");
649
+ }
650
+ } else if (status === 401) {
651
+ throw new Error("API key is invalid or expired.");
652
+ } else {
653
+ throw new Error(`OpenAI API error: ${error.response?.data?.error?.message || "Unknown error"}`);
654
+ }
655
+ } else if (error.request) {
656
+ throw new Error("No response received from AI service. Please check your internet connection.");
657
+ } else {
658
+ throw new Error(`Error: ${error.message}`);
659
+ }
660
+ }
661
+ }
662
+
663
+ // server/personalities.ts
664
+ var personalityConfigs = {
665
+ default: {
666
+ name: "Balanced",
667
+ description: "A helpful, balanced AI assistant that provides informative responses.",
668
+ systemPrompt: `You are a helpful AI assistant. Provide concise and accurate responses to user queries.
669
+ Your goal is to be informative and educational. Use clear language and provide examples where appropriate.
670
+ Always be respectful and considerate in your responses.`,
671
+ temperature: 0.7,
672
+ emoji: "\u{1F916}"
673
+ },
674
+ professional: {
675
+ name: "Professional",
676
+ description: "Formal and business-oriented with precise, structured responses.",
677
+ systemPrompt: `You are a professional AI assistant with expertise in business communication.
678
+ Provide well-structured, formal responses that are precise and to the point.
679
+ Use professional terminology where appropriate, but remain accessible.
680
+ Organize complex information in a clear, logical manner.
681
+ Maintain a courteous and professional tone at all times.`,
682
+ temperature: 0.5,
683
+ emoji: "\u{1F454}"
684
+ },
685
+ friendly: {
686
+ name: "Friendly",
687
+ description: "Casual, warm and conversational with a touch of humor.",
688
+ systemPrompt: `You are a friendly and approachable AI assistant.
689
+ Communicate in a warm, conversational tone as if chatting with a friend.
690
+ Feel free to use casual language, contractions, and the occasional appropriate humor.
691
+ Be encouraging and positive in your responses.
692
+ Make complex topics feel accessible and less intimidating.`,
693
+ temperature: 0.8,
694
+ emoji: "\u{1F60A}"
695
+ },
696
+ expert: {
697
+ name: "Expert",
698
+ description: "Technical and detailed with in-depth knowledge and explanations.",
699
+ systemPrompt: `You are an expert-level AI assistant with comprehensive technical knowledge.
700
+ Provide detailed, nuanced responses that demonstrate expert-level understanding.
701
+ Don't hesitate to use technical terminology and include background context where helpful.
702
+ When appropriate, explain underlying principles and concepts.
703
+ Present multiple perspectives or approaches when relevant.`,
704
+ temperature: 0.4,
705
+ emoji: "\u{1F468}\u200D\u{1F52C}"
706
+ },
707
+ poetic: {
708
+ name: "Poetic",
709
+ description: "Creative and eloquent with a focus on beautiful language.",
710
+ systemPrompt: `You are a poetic and creative AI assistant with a love for beautiful language.
711
+ Express ideas with eloquence, metaphor, and creative flair.
712
+ Draw connections to literature, art, and the human experience.
713
+ Use rich imagery and evocative language in your responses.
714
+ Even when explaining factual information, find ways to make your language sing.`,
715
+ temperature: 0.9,
716
+ emoji: "\u{1F3AD}"
717
+ },
718
+ concise: {
719
+ name: "Concise",
720
+ description: "Brief and to-the-point with no unnecessary words.",
721
+ systemPrompt: `You are a concise AI assistant that values brevity and clarity.
722
+ Provide the shortest possible response that fully answers the query.
723
+ Use bullet points where appropriate.
724
+ Eliminate unnecessary words, phrases, and preambles.
725
+ Focus only on the most essential information.`,
726
+ temperature: 0.5,
727
+ emoji: "\u{1F4CB}"
728
+ }
729
+ };
730
+ function getPersonalityConfig(personality) {
731
+ return personalityConfigs[personality] || personalityConfigs.default;
732
+ }
733
+
734
+ // server/flux.ts
735
+ import { z as z2 } from "zod";
736
+ var imageGenerationSchema = z2.object({
737
+ prompt: z2.string().min(1).max(1e3),
738
+ seed: z2.number().optional().default(0),
739
+ randomize_seed: z2.boolean().optional().default(true),
740
+ width: z2.number().min(256).max(1024).optional().default(512),
741
+ height: z2.number().min(256).max(1024).optional().default(512),
742
+ guidance_scale: z2.number().min(0).max(20).optional().default(7.5),
743
+ num_inference_steps: z2.number().min(1).max(50).optional().default(20)
744
+ });
745
+ async function generateImage(params) {
746
+ try {
747
+ const apiKey = process.env.REPLICATE_API_KEY;
748
+ if (!apiKey) {
749
+ throw new Error("REPLICATE_API_KEY is not set in environment variables");
750
+ }
751
+ const inputData = {
752
+ input: {
753
+ prompt: params.prompt,
754
+ width: params.width,
755
+ height: params.height,
756
+ seed: params.randomize_seed ? Math.floor(Math.random() * 1e6) : params.seed,
757
+ guidance_scale: params.guidance_scale,
758
+ num_inference_steps: params.num_inference_steps
759
+ }
760
+ };
761
+ const startResponse = await fetch(
762
+ "https://api.replicate.com/v1/models/black-forest-labs/flux-dev/predictions",
763
+ {
764
+ method: "POST",
765
+ headers: {
766
+ "Authorization": `Bearer ${apiKey}`,
767
+ "Content-Type": "application/json"
768
+ },
769
+ body: JSON.stringify(inputData)
770
+ }
771
+ );
772
+ if (!startResponse.ok) {
773
+ const errorData = await startResponse.json();
774
+ throw new Error(`Replicate API error: ${JSON.stringify(errorData)}`);
775
+ }
776
+ const prediction = await startResponse.json();
777
+ const predictionId = prediction.id;
778
+ let imageUrl = null;
779
+ let attempts = 0;
780
+ const maxAttempts = 30;
781
+ while (!imageUrl && attempts < maxAttempts) {
782
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
783
+ const statusResponse = await fetch(
784
+ `https://api.replicate.com/v1/predictions/${predictionId}`,
785
+ {
786
+ headers: {
787
+ "Authorization": `Bearer ${apiKey}`
788
+ }
789
+ }
790
+ );
791
+ if (!statusResponse.ok) {
792
+ const errorData = await statusResponse.json();
793
+ throw new Error(`Replicate API status error: ${JSON.stringify(errorData)}`);
794
+ }
795
+ const status = await statusResponse.json();
796
+ if (status.status === "succeeded") {
797
+ if (status.output && typeof status.output === "string") {
798
+ imageUrl = status.output;
799
+ } else if (Array.isArray(status.output) && status.output.length > 0) {
800
+ imageUrl = status.output[0];
801
+ }
802
+ } else if (status.status === "failed") {
803
+ throw new Error(`Image generation failed: ${status.error || "Unknown error"}`);
804
+ }
805
+ attempts++;
806
+ }
807
+ if (!imageUrl) {
808
+ throw new Error("Timed out waiting for image generation");
809
+ }
810
+ return imageUrl;
811
+ } catch (error) {
812
+ console.error("Error generating image:", error);
813
+ throw new Error(`Failed to generate image: ${error instanceof Error ? error.message : String(error)}`);
814
+ }
815
+ }
816
+ async function isFluxAvailable() {
817
+ try {
818
+ const apiKey = process.env.REPLICATE_API_KEY;
819
+ if (!apiKey) {
820
+ return false;
821
+ }
822
+ const response = await fetch(
823
+ "https://api.replicate.com/v1/models/black-forest-labs/flux-dev",
824
+ {
825
+ headers: {
826
+ "Authorization": `Bearer ${apiKey}`
827
+ }
828
+ }
829
+ );
830
+ return response.ok;
831
+ } catch (error) {
832
+ console.error("Error checking FLUX availability:", error);
833
+ return false;
834
+ }
835
+ }
836
+
837
+ // server/video.ts
838
+ import { InferenceClient as InferenceClient2 } from "@huggingface/inference";
839
+ import { z as z3 } from "zod";
840
+ var videoGenerationSchema = z3.object({
841
+ prompt: z3.string().min(1).max(1e3),
842
+ model: z3.enum(["Wan-AI/Wan2.1-T2V-14B"]).default("Wan-AI/Wan2.1-T2V-14B")
843
+ });
844
+ async function generateVideo(params) {
845
+ try {
846
+ const replicateApiKey = process.env.REPLICATE_API_KEY;
847
+ if (!replicateApiKey) {
848
+ throw new Error("REPLICATE_API_KEY is not set in environment variables");
849
+ }
850
+ const client = new InferenceClient2(replicateApiKey);
851
+ const result = await client.textToVideo({
852
+ provider: "replicate",
853
+ model: params.model,
854
+ inputs: params.prompt
855
+ });
856
+ if (!result) {
857
+ throw new Error("Failed to generate video: No result returned");
858
+ }
859
+ const videoBuffer = await result.arrayBuffer();
860
+ const videoBase64 = Buffer.from(videoBuffer).toString("base64");
861
+ const videoUrl = `data:video/mp4;base64,${videoBase64}`;
862
+ return videoUrl;
863
+ } catch (error) {
864
+ console.error("Error generating video:", error);
865
+ throw new Error(`Failed to generate video: ${error instanceof Error ? error.message : String(error)}`);
866
+ }
867
+ }
868
+ async function isVideoGenerationAvailable() {
869
+ try {
870
+ const replicateApiKey = process.env.REPLICATE_API_KEY;
871
+ if (!replicateApiKey) {
872
+ return false;
873
+ }
874
+ const client = new InferenceClient2(replicateApiKey);
875
+ return !!client;
876
+ } catch (error) {
877
+ console.error("Error checking video generation availability:", error);
878
+ return false;
879
+ }
880
+ }
881
+
882
+ // server/routes.ts
883
+ import OpenAI2 from "openai";
884
+ import { nanoid as nanoid2 } from "nanoid";
885
+ var currentModelStatus = {
886
+ model: "openai",
887
+ isOpenAIAvailable: true,
888
+ isQwenAvailable: true,
889
+ lastChecked: /* @__PURE__ */ new Date()
890
+ };
891
+ async function updateModelStatus() {
892
+ try {
893
+ const isOpenAIAvailable = await canUseOpenAI();
894
+ const isQwenAvailable = await canUseQwen();
895
+ let model = "unavailable";
896
+ if (isOpenAIAvailable) {
897
+ model = "openai";
898
+ } else if (isQwenAvailable) {
899
+ model = "qwen";
900
+ }
901
+ currentModelStatus = {
902
+ model,
903
+ isOpenAIAvailable,
904
+ isQwenAvailable,
905
+ lastChecked: /* @__PURE__ */ new Date()
906
+ };
907
+ console.log(`Updated model status: ${model} (OpenAI: ${isOpenAIAvailable}, Qwen: ${isQwenAvailable})`);
908
+ return currentModelStatus;
909
+ } catch (error) {
910
+ console.error("Error updating model status:", error);
911
+ return currentModelStatus;
912
+ }
913
+ }
914
+ updateModelStatus();
915
+ async function registerRoutes(app2) {
916
+ setupAuth(app2);
917
+ app2.get("/api/conversations", async (req, res) => {
918
+ try {
919
+ let conversations2;
920
+ if (req.isAuthenticated() && req.user) {
921
+ const userId = req.user.id;
922
+ conversations2 = await storage.getUserConversations(userId);
923
+ } else {
924
+ conversations2 = await storage.getConversations();
925
+ conversations2 = conversations2.filter((conv) => !conv.userId);
926
+ }
927
+ res.json(conversations2);
928
+ } catch (error) {
929
+ console.error("Error fetching conversations:", error);
930
+ res.status(500).json({ message: "Failed to fetch conversations." });
931
+ }
932
+ });
933
+ app2.post("/api/conversations", async (req, res) => {
934
+ try {
935
+ const conversationId = nanoid2();
936
+ let title = req.body.title;
937
+ if ((!title || title === "New Conversation") && req.body.firstMessage) {
938
+ try {
939
+ const openaiClient = new OpenAI2();
940
+ const response = await openaiClient.chat.completions.create({
941
+ model: "gpt-3.5-turbo",
942
+ messages: [\
943
+ {\
944
+ role: "system",\
945
+ content: "Generate a brief, descriptive title (3-5 words) for a conversation that starts with this message. Respond with just the title."\
946
+ },\
947
+ {\
948
+ role: "user",\
949
+ content: req.body.firstMessage\
950
+ }\
951
+ ],
952
+ max_tokens: 20,
953
+ temperature: 0.7
954
+ });
955
+ title = response.choices[0].message.content?.trim() || "New Conversation";
956
+ } catch (err) {
957
+ console.error("Error generating AI title:", err);
958
+ title = "New Conversation";
959
+ }
960
+ }
961
+ const conversationData = {
962
+ id: conversationId,
963
+ title,
964
+ personality: req.body.personality || "general"
965
+ };
966
+ if (req.isAuthenticated() && req.user) {
967
+ conversationData.userId = req.user.id;
968
+ }
969
+ const result = insertConversationSchema.safeParse(conversationData);
970
+ if (!result.success) {
971
+ return res.status(400).json({ message: "Invalid conversation data." });
972
+ }
973
+ const conversation = await storage.createConversation(result.data);
974
+ res.status(201).json(conversation);
975
+ } catch (error) {
976
+ console.error("Error creating conversation:", error);
977
+ res.status(500).json({ message: "Failed to create conversation." });
978
+ }
979
+ });
980
+ app2.post("/api/conversations/:id/generate-title", async (req, res) => {
981
+ try {
982
+ const { id } = req.params;
983
+ const messages2 = await storage.getMessages(id);
984
+ if (messages2.length < 2) {
985
+ return res.status(400).json({ message: "Need at least one exchange to generate a title" });
986
+ }
987
+ const contextMessages = messages2.slice(0, Math.min(4, messages2.length)).map((msg) => `${msg.role}: ${msg.content}`).join("\n");
988
+ let title;
989
+ try {
990
+ const openaiClient = new OpenAI2();
991
+ const response = await openaiClient.chat.completions.create({
992
+ model: "gpt-3.5-turbo",
993
+ messages: [\
994
+ {\
995
+ role: "system",\
996
+ content: "You are a helpful assistant that generates short, descriptive titles (max 6 words) for conversations based on their content. Respond with just the title."\
997
+ },\
998
+ {\
999
+ role: "user",\
1000
+ content: `Generate a short, descriptive title (maximum 6 words) for this conversation:\
1001
+ ${contextMessages}`\
1002
+ }\
1003
+ ],
1004
+ max_tokens: 20,
1005
+ temperature: 0.7
1006
+ });
1007
+ title = response.choices[0].message.content?.trim();
1008
+ if (!title) {
1009
+ title = `Chat ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`;
1010
+ }
1011
+ } catch (err) {
1012
+ console.error("Error generating AI title:", err);
1013
+ title = `Chat ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`;
1014
+ }
1015
+ const updatedConversation = await storage.updateConversationTitle(id, title);
1016
+ if (!updatedConversation) {
1017
+ return res.status(404).json({ message: "Conversation not found" });
1018
+ }
1019
+ res.json(updatedConversation);
1020
+ } catch (error) {
1021
+ console.error("Error generating title:", error);
1022
+ res.status(500).json({ message: "Failed to generate title." });
1023
+ }
1024
+ });
1025
+ app2.get("/api/conversations/:id/messages", async (req, res) => {
1026
+ try {
1027
+ const { id } = req.params;
1028
+ const conversation = await storage.getConversation(id);
1029
+ if (!conversation) {
1030
+ return res.status(404).json({ message: "Conversation not found." });
1031
+ }
1032
+ if (conversation.userId && req.isAuthenticated() && req.user) {
1033
+ if (conversation.userId !== req.user.id) {
1034
+ return res.status(403).json({ message: "You don't have permission to access this conversation." });
1035
+ }
1036
+ }
1037
+ const messages2 = await storage.getMessages(id);
1038
+ if (req.isAuthenticated() && req.user) {
1039
+ const userContext = {
1040
+ role: "system",
1041
+ content: req.user.systemContext || `Chat with ${req.user.username}`,
1042
+ conversationId: id,
1043
+ createdAt: /* @__PURE__ */ new Date()
1044
+ };
1045
+ messages2.unshift(userContext);
1046
+ }
1047
+ res.json(messages2);
1048
+ } catch (error) {
1049
+ console.error("Error fetching messages:", error);
1050
+ res.status(500).json({ message: "Failed to fetch messages." });
1051
+ }
1052
+ });
1053
+ app2.post("/api/chat", async (req, res) => {
1054
+ try {
1055
+ await updateModelStatus();
1056
+ if (currentModelStatus.model === "unavailable") {
1057
+ return res.status(503).json({
1058
+ message: "All AI models are currently unavailable. Please check your API keys."
1059
+ });
1060
+ }
1061
+ const result = conversationSchema.safeParse(req.body);
1062
+ if (!result.success) {
1063
+ return res.status(400).json({ message: "Invalid chat data format." });
1064
+ }
1065
+ const { messages: messages2 } = result.data;
1066
+ const conversationId = req.body.conversationId || "default";
1067
+ const conversation = await storage.getConversation(conversationId);
1068
+ if (!conversation && conversationId !== "default") {
1069
+ return res.status(404).json({ message: "Conversation not found." });
1070
+ }
1071
+ if (conversation && conversation.userId) {
1072
+ if (!req.isAuthenticated() || !req.user || conversation.userId !== req.user.id) {
1073
+ return res.status(403).json({ message: "You don't have permission to access this conversation." });
1074
+ }
1075
+ }
1076
+ const userMessage = messages2[messages2.length - 1];
1077
+ if (userMessage.role !== "user") {
1078
+ return res.status(400).json({ message: "Last message must be from the user." });
1079
+ }
1080
+ await storage.createMessage({
1081
+ content: userMessage.content,
1082
+ role: userMessage.role,
1083
+ conversationId
1084
+ });
1085
+ let userSystemContext = void 0;
1086
+ if (req.isAuthenticated() && req.user && req.user.systemContext) {
1087
+ userSystemContext = req.user.systemContext;
1088
+ console.log(
1089
+ "Including user system context in conversation:",
1090
+ userSystemContext ? "Yes" : "None available"
1091
+ );
1092
+ }
1093
+ const aiResponse = await generateChatResponse(messages2, userSystemContext);
1094
+ const savedMessage = await storage.createMessage({
1095
+ content: aiResponse,
1096
+ role: "assistant",
1097
+ conversationId
1098
+ });
1099
+ res.json({
1100
+ message: savedMessage,
1101
+ conversationId,
1102
+ modelInfo: {
1103
+ model: currentModelStatus.model,
1104
+ isFallback: currentModelStatus.model !== "openai"
1105
+ }
1106
+ });
1107
+ } catch (error) {
1108
+ console.error("Chat API error:", error);
1109
+ res.status(500).json({
1110
+ message: error.message || "Failed to process chat message."
1111
+ });
1112
+ }
1113
+ });
1114
+ app2.get("/api/model-status", async (_req, res) => {
1115
+ try {
1116
+ const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1e3);
1117
+ if (currentModelStatus.lastChecked < fiveMinutesAgo) {
1118
+ await updateModelStatus();
1119
+ }
1120
+ return res.json(currentModelStatus);
1121
+ } catch (error) {
1122
+ console.error("Error getting model status:", error);
1123
+ return res.status(500).json({ message: "Failed to get model status" });
1124
+ }
1125
+ });
1126
+ app2.delete("/api/conversations/:id", async (req, res) => {
1127
+ try {
1128
+ const { id } = req.params;
1129
+ if (id === "default") {
1130
+ return res.status(400).json({ message: "Cannot delete the default conversation" });
1131
+ }
1132
+ const conversation = await storage.getConversation(id);
1133
+ if (!conversation) {
1134
+ return res.status(404).json({ message: "Conversation not found" });
1135
+ }
1136
+ if (conversation.userId && req.isAuthenticated() && req.user) {
1137
+ if (conversation.userId !== req.user.id) {
1138
+ return res.status(403).json({ message: "You don't have permission to delete this conversation." });
1139
+ }
1140
+ }
1141
+ const success = await storage.deleteConversation(id);
1142
+ if (success) {
1143
+ res.status(200).json({ message: "Conversation deleted successfully" });
1144
+ } else {
1145
+ res.status(500).json({ message: "Failed to delete conversation" });
1146
+ }
1147
+ } catch (error) {
1148
+ console.error("Error deleting conversation:", error);
1149
+ res.status(500).json({ message: "Server error deleting conversation" });
1150
+ }
1151
+ });
1152
+ app2.patch("/api/conversations/:id/title", async (req, res) => {
1153
+ try {
1154
+ const { id } = req.params;
1155
+ const { title } = req.body;
1156
+ if (!title || typeof title !== "string" || title.trim().length === 0) {
1157
+ return res.status(400).json({ message: "Valid title is required" });
1158
+ }
1159
+ const conversation = await storage.getConversation(id);
1160
+ if (!conversation) {
1161
+ return res.status(404).json({ message: "Conversation not found" });
1162
+ }
1163
+ if (conversation.userId && req.isAuthenticated() && req.user) {
1164
+ if (conversation.userId !== req.user.id) {
1165
+ return res.status(403).json({ message: "You don't have permission to update this conversation." });
1166
+ }
1167
+ }
1168
+ const updatedConversation = await storage.createConversation({
1169
+ ...conversation,
1170
+ title: title.trim()
1171
+ });
1172
+ res.json(updatedConversation);
1173
+ } catch (error) {
1174
+ console.error("Error updating conversation title:", error);
1175
+ res.status(500).json({ message: "Failed to update conversation title" });
1176
+ }
1177
+ });
1178
+ app2.patch("/api/conversations/:id/personality", async (req, res) => {
1179
+ try {
1180
+ const { id } = req.params;
1181
+ const { personality } = req.body;
1182
+ const result = personalityTypeSchema.safeParse(personality);
1183
+ if (!result.success) {
1184
+ return res.status(400).json({
1185
+ message: "Invalid personality type",
1186
+ validOptions: personalityTypeSchema.options
1187
+ });
1188
+ }
1189
+ const conversation = await storage.getConversation(id);
1190
+ if (!conversation) {
1191
+ return res.status(404).json({ message: "Conversation not found" });
1192
+ }
1193
+ if (conversation.userId && req.isAuthenticated() && req.user) {
1194
+ if (conversation.userId !== req.user.id) {
1195
+ return res.status(403).json({ message: "You don't have permission to update this conversation." });
1196
+ }
1197
+ }
1198
+ const updatedConversation = await storage.updateConversationPersonality(id, result.data);
1199
+ const personalityConfig = getPersonalityConfig(result.data);
1200
+ res.json({
1201
+ ...updatedConversation,
1202
+ personalityConfig: {
1203
+ name: personalityConfig.name,
1204
+ description: personalityConfig.description,
1205
+ emoji: personalityConfig.emoji
1206
+ }
1207
+ });
1208
+ } catch (error) {
1209
+ console.error("Error updating conversation personality:", error);
1210
+ res.status(500).json({ message: "Failed to update conversation personality" });
1211
+ }
1212
+ });
1213
+ app2.get("/api/personalities", async (_req, res) => {
1214
+ try {
1215
+ const personalityTypes = personalityTypeSchema.options;
1216
+ const personalities = personalityTypes.map((type) => {
1217
+ const config = getPersonalityConfig(type);
1218
+ return {
1219
+ id: type,
1220
+ name: config.name,
1221
+ description: config.description,
1222
+ emoji: config.emoji
1223
+ };
1224
+ });
1225
+ res.json(personalities);
1226
+ } catch (error) {
1227
+ console.error("Error fetching personalities:", error);
1228
+ res.status(500).json({ message: "Failed to fetch personalities" });
1229
+ }
1230
+ });
1231
+ app2.post("/api/generate-image", async (req, res) => {
1232
+ try {
1233
+ const result = imageGenerationSchema.safeParse(req.body);
1234
+ if (!result.success) {
1235
+ return res.status(400).json({
1236
+ message: "Invalid image generation parameters",
1237
+ errors: result.error.format()
1238
+ });
1239
+ }
1240
+ const imageUrl = await generateImage(result.data);
1241
+ return res.json({
1242
+ success: true,
1243
+ imageUrl,
1244
+ params: result.data
1245
+ });
1246
+ } catch (error) {
1247
+ console.error("Error generating image:", error);
1248
+ return res.status(500).json({
1249
+ success: false,
1250
+ message: error.message || "Failed to generate image"
1251
+ });
1252
+ }
1253
+ });
1254
+ app2.get("/api/flux-status", async (_req, res) => {
1255
+ try {
1256
+ const isAvailable = await isFluxAvailable();
1257
+ return res.json({
1258
+ isAvailable,
1259
+ model: "FLUX.1-dev"
1260
+ });
1261
+ } catch (error) {
1262
+ console.error("Error checking FLUX availability:", error);
1263
+ return res.status(500).json({
1264
+ isAvailable: false,
1265
+ message: "Error checking FLUX availability"
1266
+ });
1267
+ }
1268
+ });
1269
+ app2.post("/api/generate-video", async (req, res) => {
1270
+ try {
1271
+ const result = videoGenerationSchema.safeParse(req.body);
1272
+ if (!result.success) {
1273
+ return res.status(400).json({
1274
+ message: "Invalid video generation parameters",
1275
+ errors: result.error.format()
1276
+ });
1277
+ }
1278
+ const videoUrl = await generateVideo(result.data);
1279
+ return res.json({
1280
+ success: true,
1281
+ videoUrl,
1282
+ params: result.data
1283
+ });
1284
+ } catch (error) {
1285
+ console.error("Error generating video:", error);
1286
+ return res.status(500).json({
1287
+ success: false,
1288
+ message: error.message || "Failed to generate video"
1289
+ });
1290
+ }
1291
+ });
1292
+ app2.get("/api/video-status", async (_req, res) => {
1293
+ try {
1294
+ const isAvailable = await isVideoGenerationAvailable();
1295
+ return res.json({
1296
+ isAvailable,
1297
+ model: "Wan-AI/Wan2.1-T2V-14B"
1298
+ });
1299
+ } catch (error) {
1300
+ console.error("Error checking video generation availability:", error);
1301
+ return res.status(500).json({
1302
+ isAvailable: false,
1303
+ message: "Error checking video generation availability"
1304
+ });
1305
+ }
1306
+ });
1307
+ app2.get("/api/health", (_req, res) => {
1308
+ return res.json({ status: "ok" });
1309
+ });
1310
+ const httpServer = createServer(app2);
1311
+ return httpServer;
1312
+ }
1313
+
1314
+ // server/vite.ts
1315
+ import express from "express";
1316
+ import fs from "fs";
1317
+ import path2 from "path";
1318
+ import { createServer as createViteServer, createLogger } from "vite";
1319
+
1320
+ // vite.config.ts
1321
+ import { defineConfig } from "vite";
1322
+ import react from "@vitejs/plugin-react";
1323
+ import path from "path";
1324
+ import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
1325
+ var vite_config_default = defineConfig({
1326
+ plugins: [\
1327
+ react(),\
1328
+ runtimeErrorOverlay(),\
1329
+ ...process.env.NODE_ENV !== "production" && process.env.REPL_ID !== void 0 ? [\
1330
+ await import("@replit/vite-plugin-cartographer").then(\
1331
+ (m) => m.cartographer()\
1332
+ )\
1333
+ ] : []\
1334
+ ],
1335
+ resolve: {
1336
+ alias: {
1337
+ "@": path.resolve(import.meta.dirname, "client", "src"),
1338
+ "@shared": path.resolve(import.meta.dirname, "shared"),
1339
+ "@assets": path.resolve(import.meta.dirname, "attached_assets")
1340
+ }
1341
+ },
1342
+ root: path.resolve(import.meta.dirname, "client"),
1343
+ build: {
1344
+ outDir: path.resolve(import.meta.dirname, "dist/public"),
1345
+ emptyOutDir: true
1346
+ }
1347
+ });
1348
+
1349
+ // server/vite.ts
1350
+ import { nanoid as nanoid3 } from "nanoid";
1351
+ var viteLogger = createLogger();
1352
+ function log(message, source = "express") {
1353
+ const formattedTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
1354
+ hour: "numeric",
1355
+ minute: "2-digit",
1356
+ second: "2-digit",
1357
+ hour12: true
1358
+ });
1359
+ console.log(`${formattedTime} [${source}] ${message}`);
1360
+ }
1361
+ async function setupVite(app2, server) {
1362
+ const serverOptions = {
1363
+ middlewareMode: true,
1364
+ hmr: { server },
1365
+ allowedHosts: true
1366
+ };
1367
+ const vite = await createViteServer({
1368
+ ...vite_config_default,
1369
+ configFile: false,
1370
+ customLogger: {
1371
+ ...viteLogger,
1372
+ error: (msg, options) => {
1373
+ viteLogger.error(msg, options);
1374
+ process.exit(1);
1375
+ }
1376
+ },
1377
+ server: serverOptions,
1378
+ appType: "custom"
1379
+ });
1380
+ app2.use(vite.middlewares);
1381
+ app2.use("*", async (req, res, next) => {
1382
+ const url = req.originalUrl;
1383
+ try {
1384
+ const clientTemplate = path2.resolve(
1385
+ import.meta.dirname,
1386
+ "..",
1387
+ "client",
1388
+ "index.html"
1389
+ );
1390
+ let template = await fs.promises.readFile(clientTemplate, "utf-8");
1391
+ template = template.replace(
1392
+ `src="/src/main.tsx"`,
1393
+ `src="/src/main.tsx?v=${nanoid3()}"`
1394
+ );
1395
+ const page = await vite.transformIndexHtml(url, template);
1396
+ res.status(200).set({ "Content-Type": "text/html" }).end(page);
1397
+ } catch (e) {
1398
+ vite.ssrFixStacktrace(e);
1399
+ next(e);
1400
+ }
1401
+ });
1402
+ }
1403
+ function serveStatic(app2) {
1404
+ const distPath = path2.resolve(import.meta.dirname, "public");
1405
+ if (!fs.existsSync(distPath)) {
1406
+ throw new Error(
1407
+ `Could not find the build directory: ${distPath}, make sure to build the client first`
1408
+ );
1409
+ }
1410
+ app2.use(express.static(distPath));
1411
+ app2.use("*", (_req, res) => {
1412
+ res.sendFile(path2.resolve(distPath, "index.html"));
1413
+ });
1414
+ }
1415
+
1416
+ // server/index.ts
1417
+ var app = express2();
1418
+ app.use(express2.json());
1419
+ app.use(express2.urlencoded({ extended: false }));
1420
+ app.use((req, res, next) => {
1421
+ const start = Date.now();
1422
+ const path3 = req.path;
1423
+ let capturedJsonResponse = void 0;
1424
+ const originalResJson = res.json;
1425
+ res.json = function(bodyJson, ...args) {
1426
+ capturedJsonResponse = bodyJson;
1427
+ return originalResJson.apply(res, [bodyJson, ...args]);
1428
+ };
1429
+ res.on("finish", () => {
1430
+ const duration = Date.now() - start;
1431
+ if (path3.startsWith("/api")) {
1432
+ let logLine = `${req.method} ${path3} ${res.statusCode} in ${duration}ms`;
1433
+ if (capturedJsonResponse) {
1434
+ logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
1435
+ }
1436
+ if (logLine.length > 80) {
1437
+ logLine = logLine.slice(0, 79) + "\u2026";
1438
+ }
1439
+ log(logLine);
1440
+ }
1441
+ });
1442
+ next();
1443
+ });
1444
+ (async () => {
1445
+ const server = await registerRoutes(app);
1446
+ app.use((err, _req, res, _next) => {
1447
+ const status = err.status || err.statusCode || 500;
1448
+ const message = err.message || "Internal Server Error";
1449
+ res.status(status).json({ message });
1450
+ throw err;
1451
+ });
1452
+ if (app.get("env") === "development") {
1453
+ await setupVite(app, server);
1454
+ } else {
1455
+ serveStatic(app);
1456
+ }
1457
+ const port = 5e3;
1458
+ server.listen({
1459
+ port,
1460
+ host: "0.0.0.0",
1461
+ reusePort: true
1462
+ }, () => {
1463
+ log(`serving on port ${port}`);
1464
+ });
1465
+ })();
1466
+
1467
+ ```
OpenAIChatAssistant/attached_assets/content-1746448603342.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 404
2
+
3
+ **File not found**
4
+
5
+ The site configured at this address does not
6
+ contain the requested file.
7
+
8
+
9
+ If this is your site, make sure that the filename case matches the URL
10
+ as well as any file permissions.
11
+
12
+ For root URLs (like `http://example.com/`) you must provide an
13
+ `index.html` file.
14
+
15
+
16
+ [Read the full documentation](https://help.github.com/pages/)
17
+ for more information about using **GitHub Pages**.
18
+
19
+
20
+ [GitHub Status](https://githubstatus.com/) —
21
+ [@githubstatus](https://twitter.com/githubstatus)
22
+
23
+ [![](<Base64-Image-Removed>)](https://bella288.github.io/)[![](<Base64-Image-Removed>)](https://bella288.github.io/)
OpenAIChatAssistant/attached_assets/screenshot-1746193704446.png ADDED
OpenAIChatAssistant/attached_assets/screenshot-1746193776733.png ADDED
OpenAIChatAssistant/client/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
6
+ </head>
7
+ <body>
8
+ <div id="root"></div>
9
+ <script type="module" src="/src/main.tsx"></script>
10
+ <!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
11
+ <script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
12
+ </body>
13
+ </html>
OpenAIChatAssistant/client/src/App.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Switch, Route } from "wouter";
2
+ import { queryClient } from "./lib/queryClient";
3
+ import { QueryClientProvider } from "@tanstack/react-query";
4
+ import { Toaster } from "@/components/ui/toaster";
5
+ import { TooltipProvider } from "@/components/ui/tooltip";
6
+ import NotFound from "@/pages/not-found";
7
+ import Home from "@/pages/Home";
8
+ import AuthPage from "@/pages/auth-page";
9
+ import ImageGenPage from "@/pages/ImageGenPage";
10
+ import VideoGenPage from "@/pages/VideoGenPage";
11
+ import { AuthProvider } from "@/hooks/use-auth";
12
+ import { ProtectedRoute } from "@/lib/protected-route";
13
+ import LogoutPage from "@/pages/logout-page";
14
+ import RegisterPage from "@/pages/register-page";
15
+
16
+ function Router() {
17
+ return (
18
+ <Switch>
19
+ <Route path="/auth" component={AuthPage} />
20
+ <Route path="/register" component={RegisterPage} />
21
+ <Route path="/logout" component={LogoutPage} />
22
+ <ProtectedRoute path="/" component={Home} />
23
+ <ProtectedRoute path="/image-generator" component={ImageGenPage} />
24
+ <ProtectedRoute path="/video-generator" component={VideoGenPage} />
25
+ <Route component={NotFound} />
26
+ </Switch>
27
+ );
28
+ }
29
+
30
+ function App() {
31
+ return (
32
+ <QueryClientProvider client={queryClient}>
33
+ <AuthProvider>
34
+ <TooltipProvider>
35
+ <Toaster />
36
+ <Router />
37
+ </TooltipProvider>
38
+ </AuthProvider>
39
+ </QueryClientProvider>
40
+ );
41
+ }
42
+
43
+ export default App;
OpenAIChatAssistant/client/src/components/ChatHistory.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { ChatHistoryProps } from '@/lib/types';
3
+ import TypingIndicator from './TypingIndicator';
4
+ import { useScrollToBottom } from '@/lib/hooks';
5
+ import { AlertTriangle } from 'lucide-react';
6
+
7
+ const ChatHistory: React.FC<ChatHistoryProps> = ({
8
+ messages,
9
+ isLoading,
10
+ currentModel = 'openai'
11
+ }) => {
12
+ const scrollRef = useScrollToBottom([messages, isLoading]);
13
+
14
+ // Check if we're in fallback mode by looking at the model or fallback indicator in messages
15
+ const isFallbackMode = currentModel === 'qwen' ||
16
+ messages.some(message =>
17
+ message.role === 'assistant' &&
18
+ message.content.includes('fallback mode')
19
+ );
20
+
21
+ return (
22
+ <div
23
+ ref={scrollRef}
24
+ className="chat-container overflow-y-auto pb-4 px-2"
25
+ style={{ height: 'calc(100vh - 180px)' }}
26
+ >
27
+ {/* Fallback mode indicator */}
28
+ {isFallbackMode && (
29
+ <div className="bg-amber-50 border-l-4 border-amber-400 p-4 mb-4 rounded-md">
30
+ <div className="flex items-center">
31
+ <div className="flex-shrink-0">
32
+ <AlertTriangle className="h-5 w-5 text-amber-400" />
33
+ </div>
34
+ <div className="ml-3">
35
+ <p className="text-sm text-amber-700">
36
+ <strong>Qwen Fallback Mode Active:</strong> The OpenAI API is currently unavailable.
37
+ Responses are being generated by the Qwen model instead.
38
+ </p>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ )}
43
+
44
+ {messages.map((message, index) => {
45
+ // Check if this is a fallback message directly from the content
46
+ const isMessageFallback = message.role === 'assistant' &&
47
+ (message.content.includes('fallback mode') ||
48
+ message.content.includes('Qwen model'));
49
+
50
+ // Determine if this message appears to be a fallback response
51
+ const isAssistantFallbackMessage = message.role === 'assistant' &&
52
+ (currentModel === 'qwen' || isMessageFallback);
53
+
54
+ // Clean up fallback message for display
55
+ let displayContent = isAssistantFallbackMessage
56
+ ? message.content.replace(/\n\n\(Note: I'm currently operating in fallback mode.*\)$/, '')
57
+ : message.content;
58
+
59
+ // Remove any thinking process sections for Qwen responses
60
+ if (isAssistantFallbackMessage) {
61
+ // Remove <think> tags and their content
62
+ displayContent = displayContent.replace(/<think>[\s\S]*?<\/think>/g, '');
63
+
64
+ // Remove any other XML-like tags
65
+ displayContent = displayContent.replace(/<[^>]*>/g, '');
66
+
67
+ // Clean up any excessive whitespace
68
+ displayContent = displayContent.replace(/^\s+|\s+$/g, '');
69
+ displayContent = displayContent.replace(/\n{3,}/g, '\n\n');
70
+ }
71
+
72
+ return (
73
+ <div
74
+ key={index}
75
+ className={`flex items-start ${message.role === 'user' ? 'justify-end' : ''} mb-4`}
76
+ >
77
+ {message.role !== 'user' && (
78
+ <div className="flex-shrink-0 mr-3">
79
+ <div className={`h-8 w-8 rounded-full ${
80
+ isAssistantFallbackMessage ? 'bg-amber-500' : 'bg-primary'
81
+ } flex items-center justify-center text-white`}>
82
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
83
+ <path d="M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" />
84
+ <path d="M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" />
85
+ </svg>
86
+ </div>
87
+ </div>
88
+ )}
89
+
90
+ <div
91
+ className={`${
92
+ message.role === 'user'
93
+ ? 'bg-primary text-white'
94
+ : isAssistantFallbackMessage
95
+ ? 'bg-amber-50 border border-amber-200 text-gray-800'
96
+ : 'bg-white text-gray-800'
97
+ } rounded-lg p-4 shadow-sm max-w-[85%]`}
98
+ >
99
+ <p className="whitespace-pre-wrap">{displayContent}</p>
100
+
101
+ {isAssistantFallbackMessage && isMessageFallback && (
102
+ <p className="mt-2 text-xs text-amber-600 italic">
103
+ (This response was generated using the Qwen fallback model)
104
+ </p>
105
+ )}
106
+ </div>
107
+
108
+ {message.role === 'user' && (
109
+ <div className="flex-shrink-0 ml-3">
110
+ <div className="h-8 w-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-600">
111
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
112
+ <path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
113
+ </svg>
114
+ </div>
115
+ </div>
116
+ )}
117
+ </div>
118
+ );
119
+ })}
120
+
121
+ <TypingIndicator isVisible={isLoading} />
122
+ </div>
123
+ );
124
+ };
125
+
126
+ export default ChatHistory;
OpenAIChatAssistant/client/src/components/ChatInputForm.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { ChatInputFormProps } from '@/lib/types';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Input } from '@/components/ui/input';
5
+
6
+ const ChatInputForm: React.FC<ChatInputFormProps> = ({ onSendMessage, isLoading }) => {
7
+ const [input, setInput] = useState('');
8
+ const inputRef = useRef<HTMLInputElement>(null);
9
+
10
+ // Focus input on component mount
11
+ useEffect(() => {
12
+ if (inputRef.current) {
13
+ inputRef.current.focus();
14
+ }
15
+ }, []);
16
+
17
+ const handleSubmit = (e: React.FormEvent) => {
18
+ e.preventDefault();
19
+
20
+ const message = input.trim();
21
+ if (!message || isLoading) return;
22
+
23
+ onSendMessage(message);
24
+ setInput('');
25
+ };
26
+
27
+ const handleClear = () => {
28
+ setInput('');
29
+ if (inputRef.current) {
30
+ inputRef.current.focus();
31
+ }
32
+ };
33
+
34
+ return (
35
+ <form onSubmit={handleSubmit} className="flex items-center space-x-2">
36
+ <div className="relative flex-1">
37
+ <Input
38
+ ref={inputRef}
39
+ type="text"
40
+ value={input}
41
+ onChange={(e) => setInput(e.target.value)}
42
+ placeholder="Type your message here..."
43
+ className="w-full py-3 px-4 bg-gray-100 rounded-full focus:outline-none focus:ring-2 focus:ring-primary focus:bg-white"
44
+ disabled={isLoading}
45
+ />
46
+ {input && (
47
+ <button
48
+ type="button"
49
+ onClick={handleClear}
50
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
51
+ aria-label="Clear input"
52
+ >
53
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
54
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
55
+ </svg>
56
+ </button>
57
+ )}
58
+ </div>
59
+ <Button
60
+ type="submit"
61
+ disabled={isLoading || !input.trim()}
62
+ className="bg-primary text-white p-3 rounded-full hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-colors duration-200"
63
+ >
64
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
65
+ <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
66
+ </svg>
67
+ </Button>
68
+ </form>
69
+ );
70
+ };
71
+
72
+ export default ChatInputForm;
OpenAIChatAssistant/client/src/components/ConnectionStatus.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { ConnectionStatusProps } from '@/lib/types';
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
5
+
6
+ const ConnectionStatus: React.FC<ConnectionStatusProps> = ({ isConnected, currentModel = 'openai' }) => {
7
+ // Get model status details
8
+ const getModelBadge = () => {
9
+ switch (currentModel) {
10
+ case 'openai':
11
+ return (
12
+ <Badge variant="outline" className="ml-2 bg-blue-50 text-blue-800 border-blue-300">
13
+ OpenAI
14
+ </Badge>
15
+ );
16
+ case 'qwen':
17
+ return (
18
+ <Badge variant="outline" className="ml-2 bg-amber-50 text-amber-800 border-amber-300">
19
+ Qwen (Fallback)
20
+ </Badge>
21
+ );
22
+ case 'unavailable':
23
+ return (
24
+ <Badge variant="outline" className="ml-2 bg-red-50 text-red-800 border-red-300">
25
+ No AI Available
26
+ </Badge>
27
+ );
28
+ default:
29
+ return null;
30
+ }
31
+ };
32
+
33
+ return (
34
+ <div className="flex items-center">
35
+ <span
36
+ className={`inline-block h-2 w-2 rounded-full mr-2 ${
37
+ isConnected ? 'bg-green-500' : 'bg-red-500'
38
+ }`}
39
+ ></span>
40
+ <span className="text-sm text-gray-600 mr-2">
41
+ {isConnected ? 'Connected' : 'Disconnected'}
42
+ </span>
43
+
44
+ <TooltipProvider>
45
+ <Tooltip>
46
+ <TooltipTrigger asChild>
47
+ {getModelBadge()}
48
+ </TooltipTrigger>
49
+ <TooltipContent>
50
+ <p>
51
+ {currentModel === 'openai'
52
+ ? 'Using OpenAI GPT-4o model'
53
+ : currentModel === 'qwen'
54
+ ? 'Using Qwen fallback model due to OpenAI unavailability'
55
+ : 'All AI models are currently unavailable'}
56
+ </p>
57
+ </TooltipContent>
58
+ </Tooltip>
59
+ </TooltipProvider>
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export default ConnectionStatus;
OpenAIChatAssistant/client/src/components/ConversationSidebar.tsx ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { PlusCircle, MessageSquare, Trash2, Edit2, Save, X, User, LogOut } from 'lucide-react';
3
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Input } from '@/components/ui/input';
6
+ import { apiRequest } from '@/lib/queryClient';
7
+ import { Conversation } from '@/lib/types';
8
+ import { useAuth } from '@/hooks/use-auth';
9
+ import { useLocation } from 'wouter';
10
+
11
+ interface ConversationSidebarProps {
12
+ isOpen: boolean;
13
+ onClose: () => void;
14
+ selectedConversationId: string;
15
+ onSelectConversation: (conversationId: string) => void;
16
+ onNewConversation: () => void;
17
+ }
18
+
19
+ const ConversationSidebar: React.FC<ConversationSidebarProps> = ({
20
+ isOpen,
21
+ onClose,
22
+ selectedConversationId,
23
+ onSelectConversation,
24
+ onNewConversation
25
+ }) => {
26
+ const [conversations, setConversations] = useState<Conversation[]>([]);
27
+ const [isLoading, setIsLoading] = useState(false);
28
+ const [editingId, setEditingId] = useState<string | null>(null);
29
+ const [editingTitle, setEditingTitle] = useState('');
30
+ const { user, logoutMutation } = useAuth();
31
+ const [, setLocation] = useLocation();
32
+
33
+ const isSignedIn = !!user;
34
+
35
+ // Fetch conversations
36
+ useEffect(() => {
37
+ const fetchConversations = async () => {
38
+ setIsLoading(true);
39
+ try {
40
+ const response = await fetch('/api/conversations');
41
+ if (response.ok) {
42
+ const data = await response.json();
43
+ setConversations(data);
44
+ } else {
45
+ console.error('Failed to fetch conversations');
46
+ }
47
+ } catch (error) {
48
+ console.error('Error fetching conversations:', error);
49
+ } finally {
50
+ setIsLoading(false);
51
+ }
52
+ };
53
+
54
+ fetchConversations();
55
+
56
+ // Set up interval to refresh conversations (every 30 seconds)
57
+ const interval = setInterval(fetchConversations, 30000);
58
+ return () => clearInterval(interval);
59
+ }, [isSignedIn]);
60
+
61
+ // Navigate to auth page
62
+ const handleSignIn = () => {
63
+ setLocation('/auth');
64
+ };
65
+
66
+ // Sign out
67
+ const handleSignOut = () => {
68
+ setLocation('/logout');
69
+ };
70
+
71
+ // Start editing a conversation title
72
+ const handleEditStart = (conversation: Conversation) => {
73
+ setEditingId(conversation.id);
74
+ setEditingTitle(conversation.title);
75
+ };
76
+
77
+ // Cancel editing
78
+ const handleEditCancel = () => {
79
+ setEditingId(null);
80
+ setEditingTitle('');
81
+ };
82
+
83
+ // Save edited title
84
+ const handleEditSave = async (conversationId: string) => {
85
+ try {
86
+ const response = await apiRequest('PATCH', `/api/conversations/${conversationId}/title`, {
87
+ title: editingTitle
88
+ });
89
+
90
+ if (response.ok) {
91
+ const updatedConversation = await response.json();
92
+ setConversations(conversations.map(conv =>
93
+ conv.id === conversationId ? updatedConversation : conv
94
+ ));
95
+ setEditingId(null);
96
+ } else {
97
+ console.error('Failed to update conversation title');
98
+ }
99
+ } catch (error) {
100
+ console.error('Error updating conversation title:', error);
101
+ }
102
+ };
103
+
104
+ // Delete a conversation
105
+ const handleDelete = async (conversationId: string) => {
106
+ // Confirm delete
107
+ if (!window.confirm('Are you sure you want to delete this conversation?')) {
108
+ return;
109
+ }
110
+
111
+ try {
112
+ const response = await apiRequest('DELETE', `/api/conversations/${conversationId}`);
113
+
114
+ if (response.ok) {
115
+ setConversations(conversations.filter(conv => conv.id !== conversationId));
116
+
117
+ // If we deleted the selected conversation, switch to a new one
118
+ if (conversationId === selectedConversationId) {
119
+ const nextConv = conversations.find(conv => conv.id !== conversationId);
120
+ if (nextConv) {
121
+ onSelectConversation(nextConv.id);
122
+ } else {
123
+ onNewConversation();
124
+ }
125
+ }
126
+ } else {
127
+ console.error('Failed to delete conversation');
128
+ }
129
+ } catch (error) {
130
+ console.error('Error deleting conversation:', error);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <aside
136
+ className={`fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 shadow-md transform ${
137
+ isOpen ? 'translate-x-0' : '-translate-x-full'
138
+ } transition-transform duration-300 ease-in-out z-10 flex flex-col`}
139
+ >
140
+ <div className="flex items-center justify-between p-4 border-b border-gray-200">
141
+ <h2 className="text-lg font-semibold">Conversations</h2>
142
+ <button
143
+ onClick={onClose}
144
+ className="p-1 rounded-full hover:bg-gray-100"
145
+ aria-label="Close sidebar"
146
+ >
147
+ <X className="h-5 w-5 text-gray-500" />
148
+ </button>
149
+ </div>
150
+
151
+ <div className="p-4 border-b border-gray-200">
152
+ <Button
153
+ onClick={onNewConversation}
154
+ className="w-full flex items-center justify-center"
155
+ variant="outline"
156
+ >
157
+ <PlusCircle className="mr-2 h-4 w-4" />
158
+ New Chat
159
+ </Button>
160
+ </div>
161
+
162
+ {/* Sign in/out section */}
163
+ <div className="p-4 border-b border-gray-200">
164
+ {isSignedIn ? (
165
+ <Button
166
+ onClick={handleSignOut}
167
+ variant="ghost"
168
+ className="w-full flex items-center justify-center text-red-500 hover:text-red-700 hover:bg-red-50"
169
+ >
170
+ <LogOut className="mr-2 h-4 w-4" />
171
+ Sign Out
172
+ </Button>
173
+ ) : (
174
+ <Button
175
+ onClick={handleSignIn}
176
+ variant="default"
177
+ className="w-full flex items-center justify-center"
178
+ >
179
+ <User className="mr-2 h-4 w-4" />
180
+ Sign In to Save Chats
181
+ </Button>
182
+ )}
183
+ {!isSignedIn && (
184
+ <p className="text-xs text-gray-500 mt-2 text-center">
185
+ Create an account to save your conversations
186
+ </p>
187
+ )}
188
+ </div>
189
+
190
+ {/* Conversations list */}
191
+ <div className="flex-1 overflow-y-auto p-2">
192
+ {isLoading ? (
193
+ <div className="flex items-center justify-center h-20">
194
+ <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary"></div>
195
+ </div>
196
+ ) : (
197
+ conversations.length === 0 ? (
198
+ isSignedIn ? (
199
+ <div className="text-center text-gray-500 py-8">
200
+ <MessageSquare className="mx-auto h-12 w-12 text-gray-300 mb-2" />
201
+ <p>No conversations yet</p>
202
+ <p className="text-sm">Start a new chat to get started</p>
203
+ </div>
204
+ ) : (
205
+ <div className="text-center text-gray-500 py-8">
206
+ <p className="text-sm">Sign in to view your saved conversations</p>
207
+ </div>
208
+ )
209
+ ) : (
210
+ <ul className="space-y-1">
211
+ {conversations.map(conversation => (
212
+ <li key={conversation.id} className="relative">
213
+ {editingId === conversation.id ? (
214
+ <div className="flex items-center p-2 rounded-md bg-gray-100">
215
+ <Input
216
+ value={editingTitle}
217
+ onChange={(e) => setEditingTitle(e.target.value)}
218
+ className="flex-1 mr-1"
219
+ autoFocus
220
+ />
221
+ <div className="flex">
222
+ <Button
223
+ onClick={() => handleEditSave(conversation.id)}
224
+ size="sm"
225
+ variant="ghost"
226
+ className="p-1 h-8 w-8"
227
+ >
228
+ <Save className="h-4 w-4 text-green-500" />
229
+ </Button>
230
+ <Button
231
+ onClick={handleEditCancel}
232
+ size="sm"
233
+ variant="ghost"
234
+ className="p-1 h-8 w-8"
235
+ >
236
+ <X className="h-4 w-4 text-red-500" />
237
+ </Button>
238
+ </div>
239
+ </div>
240
+ ) : (
241
+ <div
242
+ className={`flex items-center p-2 rounded-md cursor-pointer group ${
243
+ conversation.id === selectedConversationId
244
+ ? 'bg-primary text-white'
245
+ : 'hover:bg-gray-100'
246
+ }`}
247
+ onClick={() => onSelectConversation(conversation.id)}
248
+ >
249
+ <MessageSquare className={`h-4 w-4 mr-2 ${
250
+ conversation.id === selectedConversationId ? 'text-white' : 'text-gray-500'
251
+ }`} />
252
+ <span className="flex-1 truncate">{conversation.title}</span>
253
+
254
+ <div className={`flex space-x-1 ${
255
+ conversation.id === selectedConversationId ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
256
+ } transition-opacity`}>
257
+ <TooltipProvider>
258
+ <Tooltip>
259
+ <TooltipTrigger asChild>
260
+ <Button
261
+ onClick={(e) => {
262
+ e.stopPropagation();
263
+ handleEditStart(conversation);
264
+ }}
265
+ size="sm"
266
+ variant="ghost"
267
+ className={`p-1 h-6 w-6 ${
268
+ conversation.id === selectedConversationId ? 'text-white hover:bg-primary-dark' : ''
269
+ }`}
270
+ >
271
+ <Edit2 className="h-3 w-3" />
272
+ </Button>
273
+ </TooltipTrigger>
274
+ <TooltipContent>
275
+ <p>Edit title</p>
276
+ </TooltipContent>
277
+ </Tooltip>
278
+ </TooltipProvider>
279
+
280
+ <TooltipProvider>
281
+ <Tooltip>
282
+ <TooltipTrigger asChild>
283
+ <Button
284
+ onClick={(e) => {
285
+ e.stopPropagation();
286
+ handleDelete(conversation.id);
287
+ }}
288
+ size="sm"
289
+ variant="ghost"
290
+ className={`p-1 h-6 w-6 ${
291
+ conversation.id === selectedConversationId ? 'text-white hover:bg-primary-dark' : ''
292
+ }`}
293
+ >
294
+ <Trash2 className="h-3 w-3" />
295
+ </Button>
296
+ </TooltipTrigger>
297
+ <TooltipContent>
298
+ <p>Delete conversation</p>
299
+ </TooltipContent>
300
+ </Tooltip>
301
+ </TooltipProvider>
302
+ </div>
303
+ </div>
304
+ )}
305
+ </li>
306
+ ))}
307
+ </ul>
308
+ )
309
+ )}
310
+ </div>
311
+ </aside>
312
+ );
313
+ };
314
+
315
+ export default ConversationSidebar;
OpenAIChatAssistant/client/src/components/ImageGenerator.tsx ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { useForm } from 'react-hook-form';
3
+ import { zodResolver } from '@hookform/resolvers/zod';
4
+ import { z } from 'zod';
5
+ import { Button } from './ui/button';
6
+ import {
7
+ Form,
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from './ui/form';
15
+ import { Input } from './ui/input';
16
+ import { Slider } from './ui/slider';
17
+ import { Switch } from './ui/switch';
18
+ import { cn } from '@/lib/utils';
19
+ import { Card } from './ui/card';
20
+ import { Loader2 } from 'lucide-react';
21
+
22
+ // Define form schema
23
+ const formSchema = z.object({
24
+ prompt: z.string().min(1, {
25
+ message: 'Prompt is required',
26
+ }).max(1000, {
27
+ message: 'Prompt must be less than 1000 characters',
28
+ }),
29
+ width: z.number().min(256).max(1024).default(512),
30
+ height: z.number().min(256).max(1024).default(512),
31
+ seed: z.number().default(0),
32
+ randomize_seed: z.boolean().default(true),
33
+ guidance_scale: z.number().min(0).max(20).default(7.5),
34
+ num_inference_steps: z.number().min(1).max(50).default(20),
35
+ });
36
+
37
+ type FormValues = z.infer<typeof formSchema>;
38
+
39
+ export default function ImageGenerator() {
40
+ const [isLoading, setIsLoading] = useState(false);
41
+ const [error, setError] = useState<string | null>(null);
42
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
43
+
44
+ // Default form values
45
+ const defaultValues: FormValues = {
46
+ prompt: '',
47
+ width: 512,
48
+ height: 512,
49
+ seed: 0,
50
+ randomize_seed: true,
51
+ guidance_scale: 7.5,
52
+ num_inference_steps: 20,
53
+ };
54
+
55
+ // Initialize form
56
+ const form = useForm<FormValues>({
57
+ resolver: zodResolver(formSchema),
58
+ defaultValues,
59
+ });
60
+
61
+ // Handle form submission
62
+ const onSubmit = async (data: FormValues) => {
63
+ setIsLoading(true);
64
+ setError(null);
65
+
66
+ try {
67
+ const response = await fetch('/api/generate-image', {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ },
72
+ body: JSON.stringify(data),
73
+ });
74
+
75
+ if (!response.ok) {
76
+ const errorData = await response.json();
77
+ throw new Error(errorData.message || 'Failed to generate image');
78
+ }
79
+
80
+ const result = await response.json();
81
+ setImageUrl(result.imageUrl);
82
+ } catch (err: any) {
83
+ setError(err.message || 'An error occurred while generating the image');
84
+ console.error('Error generating image:', err);
85
+ } finally {
86
+ setIsLoading(false);
87
+ }
88
+ };
89
+
90
+ return (
91
+ <div className="space-y-6">
92
+ <div className="flex justify-between items-center">
93
+ <h2 className="text-3xl font-bold">Image Generator</h2>
94
+ {isLoading && <div className="flex items-center"><Loader2 className="mr-2 h-4 w-4 animate-spin" /> Generating...</div>}
95
+ </div>
96
+
97
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
98
+ <div>
99
+ <Form {...form}>
100
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
101
+ <FormField
102
+ control={form.control}
103
+ name="prompt"
104
+ render={({ field }) => (
105
+ <FormItem>
106
+ <FormLabel>Prompt</FormLabel>
107
+ <FormControl>
108
+ <Input placeholder="Enter your prompt..." {...field} />
109
+ </FormControl>
110
+ <FormDescription>
111
+ Describe the image you want to generate
112
+ </FormDescription>
113
+ <FormMessage />
114
+ </FormItem>
115
+ )}
116
+ />
117
+
118
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
119
+ <FormField
120
+ control={form.control}
121
+ name="width"
122
+ render={({ field }) => (
123
+ <FormItem>
124
+ <FormLabel>Width: {field.value}px</FormLabel>
125
+ <FormControl>
126
+ <Slider
127
+ min={256}
128
+ max={1024}
129
+ step={64}
130
+ defaultValue={[field.value]}
131
+ onValueChange={(value) => field.onChange(value[0])}
132
+ />
133
+ </FormControl>
134
+ <FormMessage />
135
+ </FormItem>
136
+ )}
137
+ />
138
+
139
+ <FormField
140
+ control={form.control}
141
+ name="height"
142
+ render={({ field }) => (
143
+ <FormItem>
144
+ <FormLabel>Height: {field.value}px</FormLabel>
145
+ <FormControl>
146
+ <Slider
147
+ min={256}
148
+ max={1024}
149
+ step={64}
150
+ defaultValue={[field.value]}
151
+ onValueChange={(value) => field.onChange(value[0])}
152
+ />
153
+ </FormControl>
154
+ <FormMessage />
155
+ </FormItem>
156
+ )}
157
+ />
158
+ </div>
159
+
160
+ <FormField
161
+ control={form.control}
162
+ name="randomize_seed"
163
+ render={({ field }) => (
164
+ <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
165
+ <div className="space-y-0.5">
166
+ <FormLabel className="text-base">Randomize Seed</FormLabel>
167
+ <FormDescription>
168
+ Use a random seed for each generation
169
+ </FormDescription>
170
+ </div>
171
+ <FormControl>
172
+ <Switch
173
+ checked={field.value}
174
+ onCheckedChange={field.onChange}
175
+ />
176
+ </FormControl>
177
+ </FormItem>
178
+ )}
179
+ />
180
+
181
+ {!form.watch("randomize_seed") && (
182
+ <FormField
183
+ control={form.control}
184
+ name="seed"
185
+ render={({ field }) => (
186
+ <FormItem>
187
+ <FormLabel>Seed: {field.value}</FormLabel>
188
+ <FormControl>
189
+ <Input
190
+ type="number"
191
+ {...field}
192
+ onChange={(e) => field.onChange(parseInt(e.target.value) || 0)}
193
+ />
194
+ </FormControl>
195
+ <FormDescription>
196
+ Specific seed for reproducible results
197
+ </FormDescription>
198
+ <FormMessage />
199
+ </FormItem>
200
+ )}
201
+ />
202
+ )}
203
+
204
+ <FormField
205
+ control={form.control}
206
+ name="guidance_scale"
207
+ render={({ field }) => (
208
+ <FormItem>
209
+ <FormLabel>Guidance Scale: {field.value}</FormLabel>
210
+ <FormControl>
211
+ <Slider
212
+ min={0}
213
+ max={20}
214
+ step={0.1}
215
+ defaultValue={[field.value]}
216
+ onValueChange={(value) => field.onChange(value[0])}
217
+ />
218
+ </FormControl>
219
+ <FormDescription>
220
+ How closely to follow the prompt (higher = more faithful)
221
+ </FormDescription>
222
+ <FormMessage />
223
+ </FormItem>
224
+ )}
225
+ />
226
+
227
+ <FormField
228
+ control={form.control}
229
+ name="num_inference_steps"
230
+ render={({ field }) => (
231
+ <FormItem>
232
+ <FormLabel>Inference Steps: {field.value}</FormLabel>
233
+ <FormControl>
234
+ <Slider
235
+ min={1}
236
+ max={50}
237
+ step={1}
238
+ defaultValue={[field.value]}
239
+ onValueChange={(value) => field.onChange(value[0])}
240
+ />
241
+ </FormControl>
242
+ <FormDescription>
243
+ Number of denoising steps (higher = better quality but slower)
244
+ </FormDescription>
245
+ <FormMessage />
246
+ </FormItem>
247
+ )}
248
+ />
249
+
250
+ {error && (
251
+ <div className="bg-red-50 text-red-500 p-3 rounded-md text-sm">
252
+ {error}
253
+ </div>
254
+ )}
255
+
256
+ <Button
257
+ type="submit"
258
+ className="w-full"
259
+ disabled={isLoading}
260
+ >
261
+ {isLoading ? (
262
+ <>
263
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
264
+ Generating...
265
+ </>
266
+ ) : (
267
+ 'Generate Image'
268
+ )}
269
+ </Button>
270
+ </form>
271
+ </Form>
272
+ </div>
273
+
274
+ <div className={cn("flex flex-col items-center justify-center",
275
+ imageUrl ? "bg-gray-50 dark:bg-gray-900" : "bg-gray-100 dark:bg-gray-800")}>
276
+ {imageUrl ? (
277
+ <div className="relative w-full">
278
+ <img
279
+ src={imageUrl}
280
+ alt="Generated"
281
+ className="rounded-md object-contain max-h-[600px] mx-auto"
282
+ />
283
+ <div className="mt-4 flex justify-center">
284
+ <Button
285
+ variant="outline"
286
+ onClick={() => window.open(imageUrl, '_blank')}
287
+ className="mr-2"
288
+ >
289
+ Open in New Tab
290
+ </Button>
291
+ <Button
292
+ variant="outline"
293
+ onClick={() => {
294
+ const a = document.createElement('a');
295
+ a.href = imageUrl;
296
+ a.download = 'generated-image.png';
297
+ document.body.appendChild(a);
298
+ a.click();
299
+ document.body.removeChild(a);
300
+ }}
301
+ >
302
+ Download
303
+ </Button>
304
+ </div>
305
+ </div>
306
+ ) : (
307
+ <Card className="w-full h-full flex items-center justify-center p-8 border-dashed">
308
+ <div className="text-center">
309
+ <p className="text-muted-foreground">
310
+ Your generated image will appear here
311
+ </p>
312
+ </div>
313
+ </Card>
314
+ )}
315
+ </div>
316
+ </div>
317
+ </div>
318
+ );
319
+ }
OpenAIChatAssistant/client/src/components/TypingIndicator.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { TypingIndicatorProps } from '@/lib/types';
3
+
4
+ const TypingIndicator: React.FC<TypingIndicatorProps> = ({ isVisible }) => {
5
+ if (!isVisible) return null;
6
+
7
+ return (
8
+ <div className="flex items-start mb-4">
9
+ <div className="flex-shrink-0 mr-3">
10
+ <div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center text-white">
11
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
12
+ <path d="M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" />
13
+ <path d="M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" />
14
+ </svg>
15
+ </div>
16
+ </div>
17
+ <div className="bg-white rounded-lg shadow-sm">
18
+ <div className="flex items-center p-2">
19
+ <div className="flex space-x-1">
20
+ <span className="h-2 w-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '0s' }}></span>
21
+ <span className="h-2 w-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></span>
22
+ <span className="h-2 w-2 bg-primary rounded-full animate-bounce" style={{ animationDelay: '0.4s' }}></span>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export default TypingIndicator;
OpenAIChatAssistant/client/src/components/UserSettingsModal.tsx ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect } from "react";
2
+ import { useForm } from "react-hook-form";
3
+ import { zodResolver } from "@hookform/resolvers/zod";
4
+ import { z } from "zod";
5
+ import { useMutation } from "@tanstack/react-query";
6
+ import { apiRequest, queryClient } from "@/lib/queryClient";
7
+ import { useAuth } from "@/hooks/use-auth";
8
+ import { useToast } from "@/hooks/use-toast";
9
+ import { updateUserProfileSchema } from "@shared/schema";
10
+
11
+ import {
12
+ Dialog,
13
+ DialogContent,
14
+ DialogDescription,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ DialogFooter,
18
+ } from "@/components/ui/dialog";
19
+ import {
20
+ Form,
21
+ FormControl,
22
+ FormDescription,
23
+ FormField,
24
+ FormItem,
25
+ FormLabel,
26
+ FormMessage,
27
+ } from "@/components/ui/form";
28
+ import { Input } from "@/components/ui/input";
29
+ import { Textarea } from "@/components/ui/textarea";
30
+ import { Button } from "@/components/ui/button";
31
+ import { Loader2 } from "lucide-react";
32
+
33
+ // Create a form schema
34
+ const profileFormSchema = z.object({
35
+ fullName: z.string().optional(),
36
+ location: z.string().optional(),
37
+ interests: z.array(z.string()).optional(),
38
+ interestsInput: z.string().optional(), // For input field value only, not submitted
39
+ profession: z.string().optional(),
40
+ pets: z.string().optional(),
41
+ systemContext: z.string().optional(),
42
+ });
43
+
44
+ type ProfileFormValues = z.infer<typeof profileFormSchema>;
45
+
46
+ interface UserSettingsModalProps {
47
+ isOpen: boolean;
48
+ onClose: () => void;
49
+ }
50
+
51
+ export default function UserSettingsModal({
52
+ isOpen,
53
+ onClose,
54
+ }: UserSettingsModalProps) {
55
+ const { user } = useAuth();
56
+ const { toast } = useToast();
57
+
58
+ // Create form with default values
59
+ const form = useForm<ProfileFormValues>({
60
+ resolver: zodResolver(profileFormSchema),
61
+ defaultValues: {
62
+ fullName: "",
63
+ location: "",
64
+ interests: [],
65
+ interestsInput: "",
66
+ profession: "",
67
+ pets: "",
68
+ systemContext: "",
69
+ },
70
+ });
71
+
72
+ // Update form when user data changes
73
+ useEffect(() => {
74
+ if (user) {
75
+ // Convert interests array to comma-separated string for display
76
+ const interestsString = user.interests?.join(", ") || "";
77
+
78
+ form.reset({
79
+ fullName: user.fullName || "",
80
+ location: user.location || "",
81
+ interests: user.interests || [],
82
+ interestsInput: interestsString,
83
+ profession: user.profession || "",
84
+ pets: user.pets || "",
85
+ systemContext: user.systemContext || "",
86
+ });
87
+ }
88
+ }, [user, form]);
89
+
90
+ const updateProfileMutation = useMutation({
91
+ mutationFn: async (data: ProfileFormValues) => {
92
+ const res = await apiRequest("PATCH", "/api/user/profile", data);
93
+ if (!res.ok) {
94
+ const errorData = await res.json();
95
+ throw new Error(errorData.message || "Failed to update profile");
96
+ }
97
+ return await res.json();
98
+ },
99
+ onSuccess: (updatedUser) => {
100
+ queryClient.setQueryData(["/api/user"], updatedUser);
101
+ toast({
102
+ title: "Profile updated",
103
+ description: "Your profile has been updated successfully.",
104
+ });
105
+ onClose();
106
+ },
107
+ onError: (error: Error) => {
108
+ toast({
109
+ title: "Update failed",
110
+ description: error.message,
111
+ variant: "destructive",
112
+ });
113
+ },
114
+ });
115
+
116
+ const onSubmit = async (data: ProfileFormValues) => {
117
+ // Create a copy of the data object without interestsInput
118
+ const { interestsInput, ...submitData } = data;
119
+
120
+ // Submit data without the temporary interestsInput field
121
+ await updateProfileMutation.mutateAsync(submitData);
122
+ };
123
+
124
+ // Convert string to array for interests field if needed
125
+ const handleInterestsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
126
+ const value = e.target.value;
127
+ // Just store the input value as is, don't process it yet
128
+ form.setValue("interestsInput", value);
129
+
130
+ // Process for the actual interests field that gets submitted
131
+ const interestsArray = value
132
+ .split(",")
133
+ .map((item) => item.trim())
134
+ .filter((item) => item !== "");
135
+ form.setValue("interests", interestsArray);
136
+ };
137
+
138
+ return (
139
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
140
+ <DialogContent className="sm:max-w-[525px] max-h-[90vh] overflow-y-auto">
141
+ <DialogHeader>
142
+ <DialogTitle>User Settings</DialogTitle>
143
+ <DialogDescription>
144
+ Update your profile information and AI assistant preferences
145
+ </DialogDescription>
146
+ </DialogHeader>
147
+
148
+ <Form {...form}>
149
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
150
+ <div className="space-y-4">
151
+ {/* Profile Information Section */}
152
+ <div className="border-b pb-2">
153
+ <h3 className="text-lg font-medium">Profile Information</h3>
154
+ </div>
155
+
156
+ <FormField
157
+ control={form.control}
158
+ name="fullName"
159
+ render={({ field }) => (
160
+ <FormItem>
161
+ <FormLabel>Full Name</FormLabel>
162
+ <FormControl>
163
+ <Input placeholder="Your full name" {...field} value={field.value || ""} />
164
+ </FormControl>
165
+ <FormMessage />
166
+ </FormItem>
167
+ )}
168
+ />
169
+
170
+ <FormField
171
+ control={form.control}
172
+ name="location"
173
+ render={({ field }) => (
174
+ <FormItem>
175
+ <FormLabel>Location</FormLabel>
176
+ <FormControl>
177
+ <Input placeholder="Your location" {...field} value={field.value || ""} />
178
+ </FormControl>
179
+ <FormMessage />
180
+ </FormItem>
181
+ )}
182
+ />
183
+
184
+ <FormField
185
+ control={form.control}
186
+ name="profession"
187
+ render={({ field }) => (
188
+ <FormItem>
189
+ <FormLabel>Profession</FormLabel>
190
+ <FormControl>
191
+ <Input placeholder="Your profession" {...field} value={field.value || ""} />
192
+ </FormControl>
193
+ <FormMessage />
194
+ </FormItem>
195
+ )}
196
+ />
197
+
198
+ <FormField
199
+ control={form.control}
200
+ name="interestsInput"
201
+ render={({ field }) => (
202
+ <FormItem>
203
+ <FormLabel>Interests</FormLabel>
204
+ <FormControl>
205
+ <Input
206
+ placeholder="Interests (comma-separated)"
207
+ {...field}
208
+ onChange={handleInterestsChange}
209
+ />
210
+ </FormControl>
211
+ <FormDescription>
212
+ Enter your interests separated by commas
213
+ </FormDescription>
214
+ <FormMessage />
215
+ </FormItem>
216
+ )}
217
+ />
218
+
219
+ <FormField
220
+ control={form.control}
221
+ name="pets"
222
+ render={({ field }) => (
223
+ <FormItem>
224
+ <FormLabel>Pets</FormLabel>
225
+ <FormControl>
226
+ <Input placeholder="Your pets" {...field} value={field.value || ""} />
227
+ </FormControl>
228
+ <FormMessage />
229
+ </FormItem>
230
+ )}
231
+ />
232
+
233
+ {/* AI Assistant Preferences Section */}
234
+ <div className="border-b pb-2 pt-4">
235
+ <h3 className="text-lg font-medium">AI Assistant Preferences</h3>
236
+ </div>
237
+
238
+ <FormField
239
+ control={form.control}
240
+ name="additionalInfo"
241
+ render={({ field }) => (
242
+ <FormItem>
243
+ <FormLabel>Additional Information</FormLabel>
244
+ <FormControl>
245
+ <Textarea
246
+ placeholder="Add any additional information about yourself that you'd like the AI to know"
247
+ className="min-h-[100px]"
248
+ {...field}
249
+ value={field.value || ""}
250
+ />
251
+ </FormControl>
252
+ <FormDescription>
253
+ This information will be included in your AI context
254
+ </FormDescription>
255
+ <FormMessage />
256
+ </FormItem>
257
+ )}
258
+ />
259
+
260
+ <div className="space-y-4">
261
+ <div className="flex justify-between items-center">
262
+ <FormLabel className="text-base">System Context</FormLabel>
263
+ <Button
264
+ type="button"
265
+ variant="outline"
266
+ size="sm"
267
+ onClick={() => {
268
+ // Generate structured context from profile fields
269
+ const fullName = form.getValues("fullName");
270
+ const location = form.getValues("location");
271
+ const interests = form.getValues("interests");
272
+ const profession = form.getValues("profession");
273
+ const pets = form.getValues("pets");
274
+ const additionalInfo = form.getValues("additionalInfo");
275
+
276
+ // Format profile information in a structured way
277
+ let profileInfo = "";
278
+ if (fullName) profileInfo += `name: ${fullName}\n`;
279
+ if (location) profileInfo += `location: ${location}\n`;
280
+ if (interests && interests.length > 0) profileInfo += `interests: ${interests.join(", ")}\n`;
281
+ if (profession) profileInfo += `profession: ${profession}\n`;
282
+ if (pets) profileInfo += `pets: ${pets}\n`;
283
+ if (additionalInfo) profileInfo += `additional_info: ${additionalInfo}\n`;
284
+
285
+ // Get existing context
286
+ const currentContext = form.getValues("systemContext") || "";
287
+
288
+ // Set the new structured context
289
+ form.setValue("systemContext", profileInfo + "\n" + currentContext);
290
+ }}
291
+ >
292
+ Include Profile Info
293
+ </Button>
294
+ </div>
295
+
296
+ <FormField
297
+ control={form.control}
298
+ name="systemContext"
299
+ render={({ field }) => (
300
+ <FormItem>
301
+ <FormControl>
302
+ <Textarea
303
+ placeholder="Add custom context for the AI assistant to understand your requirements better"
304
+ className="min-h-[150px]"
305
+ {...field}
306
+ value={field.value || ""}
307
+ />
308
+ </FormControl>
309
+ <FormDescription>
310
+ This context will be provided to the AI assistant for all your conversations.
311
+ Use key-value pairs like "name: Your Name" for best results.
312
+ </FormDescription>
313
+ <FormMessage />
314
+ </FormItem>
315
+ )}
316
+ />
317
+ </div>
318
+ </div>
319
+
320
+ <DialogFooter>
321
+ <Button
322
+ type="button"
323
+ variant="outline"
324
+ onClick={onClose}
325
+ disabled={updateProfileMutation.isPending}
326
+ >
327
+ Cancel
328
+ </Button>
329
+ <Button
330
+ type="submit"
331
+ disabled={updateProfileMutation.isPending}
332
+ >
333
+ {updateProfileMutation.isPending ? (
334
+ <>
335
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
336
+ Saving...
337
+ </>
338
+ ) : (
339
+ "Save Changes"
340
+ )}
341
+ </Button>
342
+ </DialogFooter>
343
+ </form>
344
+ </Form>
345
+ </DialogContent>
346
+ </Dialog>
347
+ );
348
+ }
OpenAIChatAssistant/client/src/components/VideoGenerator.tsx ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { useForm } from 'react-hook-form';
3
+ import { zodResolver } from '@hookform/resolvers/zod';
4
+ import { z } from 'zod';
5
+ import { Button } from './ui/button';
6
+ import {
7
+ Form,
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from './ui/form';
15
+ import { Input } from './ui/input';
16
+ import { cn } from '@/lib/utils';
17
+ import { Card } from './ui/card';
18
+ import { Loader2 } from 'lucide-react';
19
+
20
+ // Define form schema
21
+ const formSchema = z.object({
22
+ prompt: z.string().min(1, {
23
+ message: 'Prompt is required',
24
+ }).max(1000, {
25
+ message: 'Prompt must be less than 1000 characters',
26
+ }),
27
+ model: z.enum(["Wan-AI/Wan2.1-T2V-14B"]).default("Wan-AI/Wan2.1-T2V-14B"),
28
+ });
29
+
30
+ type FormValues = z.infer<typeof formSchema>;
31
+
32
+ export default function VideoGenerator() {
33
+ const [isLoading, setIsLoading] = useState(false);
34
+ const [error, setError] = useState<string | null>(null);
35
+ const [videoUrl, setVideoUrl] = useState<string | null>(null);
36
+
37
+ // Default form values
38
+ const defaultValues: FormValues = {
39
+ prompt: '',
40
+ model: "Wan-AI/Wan2.1-T2V-14B",
41
+ };
42
+
43
+ // Initialize form
44
+ const form = useForm<FormValues>({
45
+ resolver: zodResolver(formSchema),
46
+ defaultValues,
47
+ });
48
+
49
+ // Handle form submission
50
+ const onSubmit = async (data: FormValues) => {
51
+ setIsLoading(true);
52
+ setError(null);
53
+
54
+ try {
55
+ const response = await fetch('/api/generate-video', {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ },
60
+ body: JSON.stringify(data),
61
+ });
62
+
63
+ if (!response.ok) {
64
+ const errorData = await response.json();
65
+ throw new Error(errorData.message || 'Failed to generate video');
66
+ }
67
+
68
+ const result = await response.json();
69
+ setVideoUrl(result.videoUrl);
70
+ } catch (err: any) {
71
+ setError(err.message || 'An error occurred while generating the video');
72
+ console.error('Error generating video:', err);
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+
78
+ return (
79
+ <div className="w-full space-y-8">
80
+ <Card className="p-6">
81
+ <Form {...form}>
82
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
83
+ <FormField
84
+ control={form.control}
85
+ name="prompt"
86
+ render={({ field }) => (
87
+ <FormItem>
88
+ <FormLabel>Prompt</FormLabel>
89
+ <FormControl>
90
+ <Input
91
+ placeholder="A young man walking on the street"
92
+ {...field}
93
+ />
94
+ </FormControl>
95
+ <FormDescription>
96
+ Describe the video you want to generate.
97
+ </FormDescription>
98
+ <FormMessage />
99
+ </FormItem>
100
+ )}
101
+ />
102
+
103
+ <Button
104
+ type="submit"
105
+ className="w-full"
106
+ disabled={isLoading}
107
+ >
108
+ {isLoading ? (
109
+ <>
110
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
111
+ Generating video...
112
+ </>
113
+ ) : 'Generate Video'}
114
+ </Button>
115
+ </form>
116
+ </Form>
117
+ </Card>
118
+
119
+ {error && (
120
+ <div className="p-4 text-sm border border-red-200 bg-red-50 text-red-800 rounded-md">
121
+ {error}
122
+ </div>
123
+ )}
124
+
125
+ <div className={cn("flex flex-col items-center justify-center",
126
+ videoUrl ? "bg-gray-50 dark:bg-gray-900" : "bg-gray-100 dark:bg-gray-800")}>
127
+ {videoUrl ? (
128
+ <div className="relative w-full">
129
+ <video
130
+ src={videoUrl}
131
+ controls
132
+ className="rounded-md object-contain max-h-[600px] mx-auto"
133
+ />
134
+ <div className="mt-4 flex justify-center">
135
+ <Button
136
+ variant="outline"
137
+ onClick={() => window.open(videoUrl, '_blank')}
138
+ className="mr-2"
139
+ >
140
+ Open in New Tab
141
+ </Button>
142
+ <Button
143
+ variant="outline"
144
+ onClick={() => {
145
+ const a = document.createElement('a');
146
+ a.href = videoUrl;
147
+ a.download = 'generated-video.mp4';
148
+ document.body.appendChild(a);
149
+ a.click();
150
+ document.body.removeChild(a);
151
+ }}
152
+ >
153
+ Download
154
+ </Button>
155
+ </div>
156
+ </div>
157
+ ) : (
158
+ <Card className="w-full h-full flex items-center justify-center p-8 border-dashed">
159
+ <div className="text-center">
160
+ <p className="text-muted-foreground">
161
+ Your generated video will appear here
162
+ </p>
163
+ </div>
164
+ </Card>
165
+ )}
166
+ </div>
167
+ </div>
168
+ );
169
+ }
OpenAIChatAssistant/client/src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item
14
+ ref={ref}
15
+ className={cn("border-b", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ AccordionItem.displayName = "AccordionItem"
20
+
21
+ const AccordionTrigger = React.forwardRef<
22
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
+ >(({ className, children, ...props }, ref) => (
25
+ <AccordionPrimitive.Header className="flex">
26
+ <AccordionPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
36
+ </AccordionPrimitive.Trigger>
37
+ </AccordionPrimitive.Header>
38
+ ))
39
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40
+
41
+ const AccordionContent = React.forwardRef<
42
+ React.ElementRef<typeof AccordionPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <AccordionPrimitive.Content
46
+ ref={ref}
47
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
48
+ {...props}
49
+ >
50
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
51
+ </AccordionPrimitive.Content>
52
+ ))
53
+
54
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
55
+
56
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
OpenAIChatAssistant/client/src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button"
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ))
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ))
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45
+
46
+ const AlertDialogHeader = ({
47
+ className,
48
+ ...props
49
+ }: React.HTMLAttributes<HTMLDivElement>) => (
50
+ <div
51
+ className={cn(
52
+ "flex flex-col space-y-2 text-center sm:text-left",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ )
58
+ AlertDialogHeader.displayName = "AlertDialogHeader"
59
+
60
+ const AlertDialogFooter = ({
61
+ className,
62
+ ...props
63
+ }: React.HTMLAttributes<HTMLDivElement>) => (
64
+ <div
65
+ className={cn(
66
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ AlertDialogFooter.displayName = "AlertDialogFooter"
73
+
74
+ const AlertDialogTitle = React.forwardRef<
75
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
76
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
77
+ >(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Title
79
+ ref={ref}
80
+ className={cn("text-lg font-semibold", className)}
81
+ {...props}
82
+ />
83
+ ))
84
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85
+
86
+ const AlertDialogDescription = React.forwardRef<
87
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
88
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
89
+ >(({ className, ...props }, ref) => (
90
+ <AlertDialogPrimitive.Description
91
+ ref={ref}
92
+ className={cn("text-sm text-muted-foreground", className)}
93
+ {...props}
94
+ />
95
+ ))
96
+ AlertDialogDescription.displayName =
97
+ AlertDialogPrimitive.Description.displayName
98
+
99
+ const AlertDialogAction = React.forwardRef<
100
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
101
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
102
+ >(({ className, ...props }, ref) => (
103
+ <AlertDialogPrimitive.Action
104
+ ref={ref}
105
+ className={cn(buttonVariants(), className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110
+
111
+ const AlertDialogCancel = React.forwardRef<
112
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
113
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
114
+ >(({ className, ...props }, ref) => (
115
+ <AlertDialogPrimitive.Cancel
116
+ ref={ref}
117
+ className={cn(
118
+ buttonVariants({ variant: "outline" }),
119
+ "mt-2 sm:mt-0",
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ ))
125
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogPortal,
130
+ AlertDialogOverlay,
131
+ AlertDialogTrigger,
132
+ AlertDialogContent,
133
+ AlertDialogHeader,
134
+ AlertDialogFooter,
135
+ AlertDialogTitle,
136
+ AlertDialogDescription,
137
+ AlertDialogAction,
138
+ AlertDialogCancel,
139
+ }
OpenAIChatAssistant/client/src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
OpenAIChatAssistant/client/src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
OpenAIChatAssistant/client/src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
OpenAIChatAssistant/client/src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
OpenAIChatAssistant/client/src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
OpenAIChatAssistant/client/src/components/ui/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
OpenAIChatAssistant/client/src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { ChevronLeft, ChevronRight } from "lucide-react"
3
+ import { DayPicker } from "react-day-picker"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { buttonVariants } from "@/components/ui/button"
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ ...props
15
+ }: CalendarProps) {
16
+ return (
17
+ <DayPicker
18
+ showOutsideDays={showOutsideDays}
19
+ className={cn("p-3", className)}
20
+ classNames={{
21
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
22
+ month: "space-y-4",
23
+ caption: "flex justify-center pt-1 relative items-center",
24
+ caption_label: "text-sm font-medium",
25
+ nav: "space-x-1 flex items-center",
26
+ nav_button: cn(
27
+ buttonVariants({ variant: "outline" }),
28
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
29
+ ),
30
+ nav_button_previous: "absolute left-1",
31
+ nav_button_next: "absolute right-1",
32
+ table: "w-full border-collapse space-y-1",
33
+ head_row: "flex",
34
+ head_cell:
35
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
36
+ row: "flex w-full mt-2",
37
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
38
+ day: cn(
39
+ buttonVariants({ variant: "ghost" }),
40
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
41
+ ),
42
+ day_range_end: "day-range-end",
43
+ day_selected:
44
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45
+ day_today: "bg-accent text-accent-foreground",
46
+ day_outside:
47
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
48
+ day_disabled: "text-muted-foreground opacity-50",
49
+ day_range_middle:
50
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ components={{
55
+ IconLeft: ({ className, ...props }) => (
56
+ <ChevronLeft className={cn("h-4 w-4", className)} {...props} />
57
+ ),
58
+ IconRight: ({ className, ...props }) => (
59
+ <ChevronRight className={cn("h-4 w-4", className)} {...props} />
60
+ ),
61
+ }}
62
+ {...props}
63
+ />
64
+ )
65
+ }
66
+ Calendar.displayName = "Calendar"
67
+
68
+ export { Calendar }
OpenAIChatAssistant/client/src/components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
OpenAIChatAssistant/client/src/components/ui/carousel.tsx ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import useEmblaCarousel, {
3
+ type UseEmblaCarouselType,
4
+ } from "embla-carousel-react"
5
+ import { ArrowLeft, ArrowRight } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1]
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
12
+ type CarouselOptions = UseCarouselParameters[0]
13
+ type CarouselPlugin = UseCarouselParameters[1]
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions
17
+ plugins?: CarouselPlugin
18
+ orientation?: "horizontal" | "vertical"
19
+ setApi?: (api: CarouselApi) => void
20
+ }
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
24
+ api: ReturnType<typeof useEmblaCarousel>[1]
25
+ scrollPrev: () => void
26
+ scrollNext: () => void
27
+ canScrollPrev: boolean
28
+ canScrollNext: boolean
29
+ } & CarouselProps
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext)
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />")
38
+ }
39
+
40
+ return context
41
+ }
42
+
43
+ const Carousel = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
46
+ >(
47
+ (
48
+ {
49
+ orientation = "horizontal",
50
+ opts,
51
+ setApi,
52
+ plugins,
53
+ className,
54
+ children,
55
+ ...props
56
+ },
57
+ ref
58
+ ) => {
59
+ const [carouselRef, api] = useEmblaCarousel(
60
+ {
61
+ ...opts,
62
+ axis: orientation === "horizontal" ? "x" : "y",
63
+ },
64
+ plugins
65
+ )
66
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
67
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
68
+
69
+ const onSelect = React.useCallback((api: CarouselApi) => {
70
+ if (!api) {
71
+ return
72
+ }
73
+
74
+ setCanScrollPrev(api.canScrollPrev())
75
+ setCanScrollNext(api.canScrollNext())
76
+ }, [])
77
+
78
+ const scrollPrev = React.useCallback(() => {
79
+ api?.scrollPrev()
80
+ }, [api])
81
+
82
+ const scrollNext = React.useCallback(() => {
83
+ api?.scrollNext()
84
+ }, [api])
85
+
86
+ const handleKeyDown = React.useCallback(
87
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
88
+ if (event.key === "ArrowLeft") {
89
+ event.preventDefault()
90
+ scrollPrev()
91
+ } else if (event.key === "ArrowRight") {
92
+ event.preventDefault()
93
+ scrollNext()
94
+ }
95
+ },
96
+ [scrollPrev, scrollNext]
97
+ )
98
+
99
+ React.useEffect(() => {
100
+ if (!api || !setApi) {
101
+ return
102
+ }
103
+
104
+ setApi(api)
105
+ }, [api, setApi])
106
+
107
+ React.useEffect(() => {
108
+ if (!api) {
109
+ return
110
+ }
111
+
112
+ onSelect(api)
113
+ api.on("reInit", onSelect)
114
+ api.on("select", onSelect)
115
+
116
+ return () => {
117
+ api?.off("select", onSelect)
118
+ }
119
+ }, [api, onSelect])
120
+
121
+ return (
122
+ <CarouselContext.Provider
123
+ value={{
124
+ carouselRef,
125
+ api: api,
126
+ opts,
127
+ orientation:
128
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
129
+ scrollPrev,
130
+ scrollNext,
131
+ canScrollPrev,
132
+ canScrollNext,
133
+ }}
134
+ >
135
+ <div
136
+ ref={ref}
137
+ onKeyDownCapture={handleKeyDown}
138
+ className={cn("relative", className)}
139
+ role="region"
140
+ aria-roledescription="carousel"
141
+ {...props}
142
+ >
143
+ {children}
144
+ </div>
145
+ </CarouselContext.Provider>
146
+ )
147
+ }
148
+ )
149
+ Carousel.displayName = "Carousel"
150
+
151
+ const CarouselContent = React.forwardRef<
152
+ HTMLDivElement,
153
+ React.HTMLAttributes<HTMLDivElement>
154
+ >(({ className, ...props }, ref) => {
155
+ const { carouselRef, orientation } = useCarousel()
156
+
157
+ return (
158
+ <div ref={carouselRef} className="overflow-hidden">
159
+ <div
160
+ ref={ref}
161
+ className={cn(
162
+ "flex",
163
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
164
+ className
165
+ )}
166
+ {...props}
167
+ />
168
+ </div>
169
+ )
170
+ })
171
+ CarouselContent.displayName = "CarouselContent"
172
+
173
+ const CarouselItem = React.forwardRef<
174
+ HTMLDivElement,
175
+ React.HTMLAttributes<HTMLDivElement>
176
+ >(({ className, ...props }, ref) => {
177
+ const { orientation } = useCarousel()
178
+
179
+ return (
180
+ <div
181
+ ref={ref}
182
+ role="group"
183
+ aria-roledescription="slide"
184
+ className={cn(
185
+ "min-w-0 shrink-0 grow-0 basis-full",
186
+ orientation === "horizontal" ? "pl-4" : "pt-4",
187
+ className
188
+ )}
189
+ {...props}
190
+ />
191
+ )
192
+ })
193
+ CarouselItem.displayName = "CarouselItem"
194
+
195
+ const CarouselPrevious = React.forwardRef<
196
+ HTMLButtonElement,
197
+ React.ComponentProps<typeof Button>
198
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
199
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
200
+
201
+ return (
202
+ <Button
203
+ ref={ref}
204
+ variant={variant}
205
+ size={size}
206
+ className={cn(
207
+ "absolute h-8 w-8 rounded-full",
208
+ orientation === "horizontal"
209
+ ? "-left-12 top-1/2 -translate-y-1/2"
210
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
211
+ className
212
+ )}
213
+ disabled={!canScrollPrev}
214
+ onClick={scrollPrev}
215
+ {...props}
216
+ >
217
+ <ArrowLeft className="h-4 w-4" />
218
+ <span className="sr-only">Previous slide</span>
219
+ </Button>
220
+ )
221
+ })
222
+ CarouselPrevious.displayName = "CarouselPrevious"
223
+
224
+ const CarouselNext = React.forwardRef<
225
+ HTMLButtonElement,
226
+ React.ComponentProps<typeof Button>
227
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
228
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
229
+
230
+ return (
231
+ <Button
232
+ ref={ref}
233
+ variant={variant}
234
+ size={size}
235
+ className={cn(
236
+ "absolute h-8 w-8 rounded-full",
237
+ orientation === "horizontal"
238
+ ? "-right-12 top-1/2 -translate-y-1/2"
239
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
240
+ className
241
+ )}
242
+ disabled={!canScrollNext}
243
+ onClick={scrollNext}
244
+ {...props}
245
+ >
246
+ <ArrowRight className="h-4 w-4" />
247
+ <span className="sr-only">Next slide</span>
248
+ </Button>
249
+ )
250
+ })
251
+ CarouselNext.displayName = "CarouselNext"
252
+
253
+ export {
254
+ type CarouselApi,
255
+ Carousel,
256
+ CarouselContent,
257
+ CarouselItem,
258
+ CarouselPrevious,
259
+ CarouselNext,
260
+ }
OpenAIChatAssistant/client/src/components/ui/chart.tsx ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<
42
+ typeof RechartsPrimitive.ResponsiveContainer
43
+ >["children"]
44
+ }
45
+ >(({ id, className, children, config, ...props }, ref) => {
46
+ const uniqueId = React.useId()
47
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48
+
49
+ return (
50
+ <ChartContext.Provider value={{ config }}>
51
+ <div
52
+ data-chart={chartId}
53
+ ref={ref}
54
+ className={cn(
55
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <ChartStyle id={chartId} config={config} />
61
+ <RechartsPrimitive.ResponsiveContainer>
62
+ {children}
63
+ </RechartsPrimitive.ResponsiveContainer>
64
+ </div>
65
+ </ChartContext.Provider>
66
+ )
67
+ })
68
+ ChartContainer.displayName = "Chart"
69
+
70
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
+ const colorConfig = Object.entries(config).filter(
72
+ ([, config]) => config.theme || config.color
73
+ )
74
+
75
+ if (!colorConfig.length) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <style
81
+ dangerouslySetInnerHTML={{
82
+ __html: Object.entries(THEMES)
83
+ .map(
84
+ ([theme, prefix]) => `
85
+ ${prefix} [data-chart=${id}] {
86
+ ${colorConfig
87
+ .map(([key, itemConfig]) => {
88
+ const color =
89
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
+ itemConfig.color
91
+ return color ? ` --color-${key}: ${color};` : null
92
+ })
93
+ .join("\n")}
94
+ }
95
+ `
96
+ )
97
+ .join("\n"),
98
+ }}
99
+ />
100
+ )
101
+ }
102
+
103
+ const ChartTooltip = RechartsPrimitive.Tooltip
104
+
105
+ const ChartTooltipContent = React.forwardRef<
106
+ HTMLDivElement,
107
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
108
+ React.ComponentProps<"div"> & {
109
+ hideLabel?: boolean
110
+ hideIndicator?: boolean
111
+ indicator?: "line" | "dot" | "dashed"
112
+ nameKey?: string
113
+ labelKey?: string
114
+ }
115
+ >(
116
+ (
117
+ {
118
+ active,
119
+ payload,
120
+ className,
121
+ indicator = "dot",
122
+ hideLabel = false,
123
+ hideIndicator = false,
124
+ label,
125
+ labelFormatter,
126
+ labelClassName,
127
+ formatter,
128
+ color,
129
+ nameKey,
130
+ labelKey,
131
+ },
132
+ ref
133
+ ) => {
134
+ const { config } = useChart()
135
+
136
+ const tooltipLabel = React.useMemo(() => {
137
+ if (hideLabel || !payload?.length) {
138
+ return null
139
+ }
140
+
141
+ const [item] = payload
142
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`
143
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
144
+ const value =
145
+ !labelKey && typeof label === "string"
146
+ ? config[label as keyof typeof config]?.label || label
147
+ : itemConfig?.label
148
+
149
+ if (labelFormatter) {
150
+ return (
151
+ <div className={cn("font-medium", labelClassName)}>
152
+ {labelFormatter(value, payload)}
153
+ </div>
154
+ )
155
+ }
156
+
157
+ if (!value) {
158
+ return null
159
+ }
160
+
161
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
162
+ }, [
163
+ label,
164
+ labelFormatter,
165
+ payload,
166
+ hideLabel,
167
+ labelClassName,
168
+ config,
169
+ labelKey,
170
+ ])
171
+
172
+ if (!active || !payload?.length) {
173
+ return null
174
+ }
175
+
176
+ const nestLabel = payload.length === 1 && indicator !== "dot"
177
+
178
+ return (
179
+ <div
180
+ ref={ref}
181
+ className={cn(
182
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
183
+ className
184
+ )}
185
+ >
186
+ {!nestLabel ? tooltipLabel : null}
187
+ <div className="grid gap-1.5">
188
+ {payload.map((item, index) => {
189
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
190
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
191
+ const indicatorColor = color || item.payload.fill || item.color
192
+
193
+ return (
194
+ <div
195
+ key={item.dataKey}
196
+ className={cn(
197
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
198
+ indicator === "dot" && "items-center"
199
+ )}
200
+ >
201
+ {formatter && item?.value !== undefined && item.name ? (
202
+ formatter(item.value, item.name, item, index, item.payload)
203
+ ) : (
204
+ <>
205
+ {itemConfig?.icon ? (
206
+ <itemConfig.icon />
207
+ ) : (
208
+ !hideIndicator && (
209
+ <div
210
+ className={cn(
211
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
212
+ {
213
+ "h-2.5 w-2.5": indicator === "dot",
214
+ "w-1": indicator === "line",
215
+ "w-0 border-[1.5px] border-dashed bg-transparent":
216
+ indicator === "dashed",
217
+ "my-0.5": nestLabel && indicator === "dashed",
218
+ }
219
+ )}
220
+ style={
221
+ {
222
+ "--color-bg": indicatorColor,
223
+ "--color-border": indicatorColor,
224
+ } as React.CSSProperties
225
+ }
226
+ />
227
+ )
228
+ )}
229
+ <div
230
+ className={cn(
231
+ "flex flex-1 justify-between leading-none",
232
+ nestLabel ? "items-end" : "items-center"
233
+ )}
234
+ >
235
+ <div className="grid gap-1.5">
236
+ {nestLabel ? tooltipLabel : null}
237
+ <span className="text-muted-foreground">
238
+ {itemConfig?.label || item.name}
239
+ </span>
240
+ </div>
241
+ {item.value && (
242
+ <span className="font-mono font-medium tabular-nums text-foreground">
243
+ {item.value.toLocaleString()}
244
+ </span>
245
+ )}
246
+ </div>
247
+ </>
248
+ )}
249
+ </div>
250
+ )
251
+ })}
252
+ </div>
253
+ </div>
254
+ )
255
+ }
256
+ )
257
+ ChartTooltipContent.displayName = "ChartTooltip"
258
+
259
+ const ChartLegend = RechartsPrimitive.Legend
260
+
261
+ const ChartLegendContent = React.forwardRef<
262
+ HTMLDivElement,
263
+ React.ComponentProps<"div"> &
264
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
265
+ hideIcon?: boolean
266
+ nameKey?: string
267
+ }
268
+ >(
269
+ (
270
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
271
+ ref
272
+ ) => {
273
+ const { config } = useChart()
274
+
275
+ if (!payload?.length) {
276
+ return null
277
+ }
278
+
279
+ return (
280
+ <div
281
+ ref={ref}
282
+ className={cn(
283
+ "flex items-center justify-center gap-4",
284
+ verticalAlign === "top" ? "pb-3" : "pt-3",
285
+ className
286
+ )}
287
+ >
288
+ {payload.map((item) => {
289
+ const key = `${nameKey || item.dataKey || "value"}`
290
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
291
+
292
+ return (
293
+ <div
294
+ key={item.value}
295
+ className={cn(
296
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
297
+ )}
298
+ >
299
+ {itemConfig?.icon && !hideIcon ? (
300
+ <itemConfig.icon />
301
+ ) : (
302
+ <div
303
+ className="h-2 w-2 shrink-0 rounded-[2px]"
304
+ style={{
305
+ backgroundColor: item.color,
306
+ }}
307
+ />
308
+ )}
309
+ {itemConfig?.label}
310
+ </div>
311
+ )
312
+ })}
313
+ </div>
314
+ )
315
+ }
316
+ )
317
+ ChartLegendContent.displayName = "ChartLegend"
318
+
319
+ // Helper to extract item config from a payload.
320
+ function getPayloadConfigFromPayload(
321
+ config: ChartConfig,
322
+ payload: unknown,
323
+ key: string
324
+ ) {
325
+ if (typeof payload !== "object" || payload === null) {
326
+ return undefined
327
+ }
328
+
329
+ const payloadPayload =
330
+ "payload" in payload &&
331
+ typeof payload.payload === "object" &&
332
+ payload.payload !== null
333
+ ? payload.payload
334
+ : undefined
335
+
336
+ let configLabelKey: string = key
337
+
338
+ if (
339
+ key in payload &&
340
+ typeof payload[key as keyof typeof payload] === "string"
341
+ ) {
342
+ configLabelKey = payload[key as keyof typeof payload] as string
343
+ } else if (
344
+ payloadPayload &&
345
+ key in payloadPayload &&
346
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
347
+ ) {
348
+ configLabelKey = payloadPayload[
349
+ key as keyof typeof payloadPayload
350
+ ] as string
351
+ }
352
+
353
+ return configLabelKey in config
354
+ ? config[configLabelKey]
355
+ : config[key as keyof typeof config]
356
+ }
357
+
358
+ export {
359
+ ChartContainer,
360
+ ChartTooltip,
361
+ ChartTooltipContent,
362
+ ChartLegend,
363
+ ChartLegendContent,
364
+ ChartStyle,
365
+ }
OpenAIChatAssistant/client/src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator
20
+ className={cn("flex items-center justify-center text-current")}
21
+ >
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ))
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
+
28
+ export { Checkbox }
OpenAIChatAssistant/client/src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
OpenAIChatAssistant/client/src/components/ui/command.tsx ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { type DialogProps } from "@radix-ui/react-dialog"
3
+ import { Command as CommandPrimitive } from "cmdk"
4
+ import { Search } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
8
+
9
+ const Command = React.forwardRef<
10
+ React.ElementRef<typeof CommandPrimitive>,
11
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
12
+ >(({ className, ...props }, ref) => (
13
+ <CommandPrimitive
14
+ ref={ref}
15
+ className={cn(
16
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ ))
22
+ Command.displayName = CommandPrimitive.displayName
23
+
24
+ const CommandDialog = ({ children, ...props }: DialogProps) => {
25
+ return (
26
+ <Dialog {...props}>
27
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
28
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
29
+ {children}
30
+ </Command>
31
+ </DialogContent>
32
+ </Dialog>
33
+ )
34
+ }
35
+
36
+ const CommandInput = React.forwardRef<
37
+ React.ElementRef<typeof CommandPrimitive.Input>,
38
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
39
+ >(({ className, ...props }, ref) => (
40
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
41
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
42
+ <CommandPrimitive.Input
43
+ ref={ref}
44
+ className={cn(
45
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </div>
51
+ ))
52
+
53
+ CommandInput.displayName = CommandPrimitive.Input.displayName
54
+
55
+ const CommandList = React.forwardRef<
56
+ React.ElementRef<typeof CommandPrimitive.List>,
57
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
58
+ >(({ className, ...props }, ref) => (
59
+ <CommandPrimitive.List
60
+ ref={ref}
61
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
62
+ {...props}
63
+ />
64
+ ))
65
+
66
+ CommandList.displayName = CommandPrimitive.List.displayName
67
+
68
+ const CommandEmpty = React.forwardRef<
69
+ React.ElementRef<typeof CommandPrimitive.Empty>,
70
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
71
+ >((props, ref) => (
72
+ <CommandPrimitive.Empty
73
+ ref={ref}
74
+ className="py-6 text-center text-sm"
75
+ {...props}
76
+ />
77
+ ))
78
+
79
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
80
+
81
+ const CommandGroup = React.forwardRef<
82
+ React.ElementRef<typeof CommandPrimitive.Group>,
83
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
84
+ >(({ className, ...props }, ref) => (
85
+ <CommandPrimitive.Group
86
+ ref={ref}
87
+ className={cn(
88
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ ))
94
+
95
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
96
+
97
+ const CommandSeparator = React.forwardRef<
98
+ React.ElementRef<typeof CommandPrimitive.Separator>,
99
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
100
+ >(({ className, ...props }, ref) => (
101
+ <CommandPrimitive.Separator
102
+ ref={ref}
103
+ className={cn("-mx-1 h-px bg-border", className)}
104
+ {...props}
105
+ />
106
+ ))
107
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
108
+
109
+ const CommandItem = React.forwardRef<
110
+ React.ElementRef<typeof CommandPrimitive.Item>,
111
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
112
+ >(({ className, ...props }, ref) => (
113
+ <CommandPrimitive.Item
114
+ ref={ref}
115
+ className={cn(
116
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
117
+ className
118
+ )}
119
+ {...props}
120
+ />
121
+ ))
122
+
123
+ CommandItem.displayName = CommandPrimitive.Item.displayName
124
+
125
+ const CommandShortcut = ({
126
+ className,
127
+ ...props
128
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
129
+ return (
130
+ <span
131
+ className={cn(
132
+ "ml-auto text-xs tracking-widest text-muted-foreground",
133
+ className
134
+ )}
135
+ {...props}
136
+ />
137
+ )
138
+ }
139
+ CommandShortcut.displayName = "CommandShortcut"
140
+
141
+ export {
142
+ Command,
143
+ CommandDialog,
144
+ CommandInput,
145
+ CommandList,
146
+ CommandEmpty,
147
+ CommandGroup,
148
+ CommandItem,
149
+ CommandShortcut,
150
+ CommandSeparator,
151
+ }
OpenAIChatAssistant/client/src/components/ui/context-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <ContextMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </ContextMenuPrimitive.SubTrigger>
37
+ ))
38
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
39
+
40
+ const ContextMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <ContextMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
54
+
55
+ const ContextMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58
+ >(({ className, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Portal>
60
+ <ContextMenuPrimitive.Content
61
+ ref={ref}
62
+ className={cn(
63
+ "z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ </ContextMenuPrimitive.Portal>
69
+ ))
70
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
71
+
72
+ const ContextMenuItem = React.forwardRef<
73
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
74
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75
+ inset?: boolean
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <ContextMenuPrimitive.Item
79
+ ref={ref}
80
+ className={cn(
81
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ inset && "pl-8",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ ))
88
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
89
+
90
+ const ContextMenuCheckboxItem = React.forwardRef<
91
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93
+ >(({ className, children, checked, ...props }, ref) => (
94
+ <ContextMenuPrimitive.CheckboxItem
95
+ ref={ref}
96
+ className={cn(
97
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
98
+ className
99
+ )}
100
+ checked={checked}
101
+ {...props}
102
+ >
103
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104
+ <ContextMenuPrimitive.ItemIndicator>
105
+ <Check className="h-4 w-4" />
106
+ </ContextMenuPrimitive.ItemIndicator>
107
+ </span>
108
+ {children}
109
+ </ContextMenuPrimitive.CheckboxItem>
110
+ ))
111
+ ContextMenuCheckboxItem.displayName =
112
+ ContextMenuPrimitive.CheckboxItem.displayName
113
+
114
+ const ContextMenuRadioItem = React.forwardRef<
115
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
116
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <ContextMenuPrimitive.RadioItem
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <ContextMenuPrimitive.ItemIndicator>
128
+ <Circle className="h-2 w-2 fill-current" />
129
+ </ContextMenuPrimitive.ItemIndicator>
130
+ </span>
131
+ {children}
132
+ </ContextMenuPrimitive.RadioItem>
133
+ ))
134
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
135
+
136
+ const ContextMenuLabel = React.forwardRef<
137
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
138
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
139
+ inset?: boolean
140
+ }
141
+ >(({ className, inset, ...props }, ref) => (
142
+ <ContextMenuPrimitive.Label
143
+ ref={ref}
144
+ className={cn(
145
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
146
+ inset && "pl-8",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ ))
152
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
153
+
154
+ const ContextMenuSeparator = React.forwardRef<
155
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
156
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
157
+ >(({ className, ...props }, ref) => (
158
+ <ContextMenuPrimitive.Separator
159
+ ref={ref}
160
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
161
+ {...props}
162
+ />
163
+ ))
164
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
165
+
166
+ const ContextMenuShortcut = ({
167
+ className,
168
+ ...props
169
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
170
+ return (
171
+ <span
172
+ className={cn(
173
+ "ml-auto text-xs tracking-widest text-muted-foreground",
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
181
+
182
+ export {
183
+ ContextMenu,
184
+ ContextMenuTrigger,
185
+ ContextMenuContent,
186
+ ContextMenuItem,
187
+ ContextMenuCheckboxItem,
188
+ ContextMenuRadioItem,
189
+ ContextMenuLabel,
190
+ ContextMenuSeparator,
191
+ ContextMenuShortcut,
192
+ ContextMenuGroup,
193
+ ContextMenuPortal,
194
+ ContextMenuSub,
195
+ ContextMenuSubContent,
196
+ ContextMenuSubTrigger,
197
+ ContextMenuRadioGroup,
198
+ }
OpenAIChatAssistant/client/src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
3
+ import { X } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Dialog = DialogPrimitive.Root
8
+
9
+ const DialogTrigger = DialogPrimitive.Trigger
10
+
11
+ const DialogPortal = DialogPrimitive.Portal
12
+
13
+ const DialogClose = DialogPrimitive.Close
14
+
15
+ const DialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <DialogPrimitive.Overlay
20
+ ref={ref}
21
+ className={cn(
22
+ "fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29
+
30
+ const DialogContent = React.forwardRef<
31
+ React.ElementRef<typeof DialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
33
+ >(({ className, children, ...props }, ref) => (
34
+ <DialogPortal>
35
+ <DialogOverlay />
36
+ <DialogPrimitive.Content
37
+ ref={ref}
38
+ className={cn(
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg max-h-[85vh] translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 overflow-y-auto data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
46
+ <X className="h-4 w-4" />
47
+ <span className="sr-only">Close</span>
48
+ </DialogPrimitive.Close>
49
+ </DialogPrimitive.Content>
50
+ </DialogPortal>
51
+ ))
52
+ DialogContent.displayName = DialogPrimitive.Content.displayName
53
+
54
+ const DialogHeader = ({
55
+ className,
56
+ ...props
57
+ }: React.HTMLAttributes<HTMLDivElement>) => (
58
+ <div
59
+ className={cn(
60
+ "flex flex-col space-y-1.5 text-center sm:text-left",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ DialogHeader.displayName = "DialogHeader"
67
+
68
+ const DialogFooter = ({
69
+ className,
70
+ ...props
71
+ }: React.HTMLAttributes<HTMLDivElement>) => (
72
+ <div
73
+ className={cn(
74
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ DialogFooter.displayName = "DialogFooter"
81
+
82
+ const DialogTitle = React.forwardRef<
83
+ React.ElementRef<typeof DialogPrimitive.Title>,
84
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
85
+ >(({ className, ...props }, ref) => (
86
+ <DialogPrimitive.Title
87
+ ref={ref}
88
+ className={cn(
89
+ "text-lg font-semibold leading-none tracking-tight",
90
+ className
91
+ )}
92
+ {...props}
93
+ />
94
+ ))
95
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
96
+
97
+ const DialogDescription = React.forwardRef<
98
+ React.ElementRef<typeof DialogPrimitive.Description>,
99
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
100
+ >(({ className, ...props }, ref) => (
101
+ <DialogPrimitive.Description
102
+ ref={ref}
103
+ className={cn("text-sm text-muted-foreground", className)}
104
+ {...props}
105
+ />
106
+ ))
107
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
108
+
109
+ export {
110
+ Dialog,
111
+ DialogPortal,
112
+ DialogOverlay,
113
+ DialogClose,
114
+ DialogTrigger,
115
+ DialogContent,
116
+ DialogHeader,
117
+ DialogFooter,
118
+ DialogTitle,
119
+ DialogDescription,
120
+ }
OpenAIChatAssistant/client/src/components/ui/drawer.tsx ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Drawer as DrawerPrimitive } from "vaul"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Drawer = ({
9
+ shouldScaleBackground = true,
10
+ ...props
11
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
12
+ <DrawerPrimitive.Root
13
+ shouldScaleBackground={shouldScaleBackground}
14
+ {...props}
15
+ />
16
+ )
17
+ Drawer.displayName = "Drawer"
18
+
19
+ const DrawerTrigger = DrawerPrimitive.Trigger
20
+
21
+ const DrawerPortal = DrawerPrimitive.Portal
22
+
23
+ const DrawerClose = DrawerPrimitive.Close
24
+
25
+ const DrawerOverlay = React.forwardRef<
26
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
27
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
28
+ >(({ className, ...props }, ref) => (
29
+ <DrawerPrimitive.Overlay
30
+ ref={ref}
31
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
32
+ {...props}
33
+ />
34
+ ))
35
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
36
+
37
+ const DrawerContent = React.forwardRef<
38
+ React.ElementRef<typeof DrawerPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
40
+ >(({ className, children, ...props }, ref) => (
41
+ <DrawerPortal>
42
+ <DrawerOverlay />
43
+ <DrawerPrimitive.Content
44
+ ref={ref}
45
+ className={cn(
46
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
47
+ className
48
+ )}
49
+ {...props}
50
+ >
51
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
52
+ {children}
53
+ </DrawerPrimitive.Content>
54
+ </DrawerPortal>
55
+ ))
56
+ DrawerContent.displayName = "DrawerContent"
57
+
58
+ const DrawerHeader = ({
59
+ className,
60
+ ...props
61
+ }: React.HTMLAttributes<HTMLDivElement>) => (
62
+ <div
63
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
64
+ {...props}
65
+ />
66
+ )
67
+ DrawerHeader.displayName = "DrawerHeader"
68
+
69
+ const DrawerFooter = ({
70
+ className,
71
+ ...props
72
+ }: React.HTMLAttributes<HTMLDivElement>) => (
73
+ <div
74
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
75
+ {...props}
76
+ />
77
+ )
78
+ DrawerFooter.displayName = "DrawerFooter"
79
+
80
+ const DrawerTitle = React.forwardRef<
81
+ React.ElementRef<typeof DrawerPrimitive.Title>,
82
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
83
+ >(({ className, ...props }, ref) => (
84
+ <DrawerPrimitive.Title
85
+ ref={ref}
86
+ className={cn(
87
+ "text-lg font-semibold leading-none tracking-tight",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ))
93
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName
94
+
95
+ const DrawerDescription = React.forwardRef<
96
+ React.ElementRef<typeof DrawerPrimitive.Description>,
97
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
98
+ >(({ className, ...props }, ref) => (
99
+ <DrawerPrimitive.Description
100
+ ref={ref}
101
+ className={cn("text-sm text-muted-foreground", className)}
102
+ {...props}
103
+ />
104
+ ))
105
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName
106
+
107
+ export {
108
+ Drawer,
109
+ DrawerPortal,
110
+ DrawerOverlay,
111
+ DrawerTrigger,
112
+ DrawerClose,
113
+ DrawerContent,
114
+ DrawerHeader,
115
+ DrawerFooter,
116
+ DrawerTitle,
117
+ DrawerDescription,
118
+ }
OpenAIChatAssistant/client/src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <DropdownMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto" />
36
+ </DropdownMenuPrimitive.SubTrigger>
37
+ ))
38
+ DropdownMenuSubTrigger.displayName =
39
+ DropdownMenuPrimitive.SubTrigger.displayName
40
+
41
+ const DropdownMenuSubContent = React.forwardRef<
42
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
43
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
44
+ >(({ className, ...props }, ref) => (
45
+ <DropdownMenuPrimitive.SubContent
46
+ ref={ref}
47
+ className={cn(
48
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ ))
54
+ DropdownMenuSubContent.displayName =
55
+ DropdownMenuPrimitive.SubContent.displayName
56
+
57
+ const DropdownMenuContent = React.forwardRef<
58
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
59
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
60
+ >(({ className, sideOffset = 4, ...props }, ref) => (
61
+ <DropdownMenuPrimitive.Portal>
62
+ <DropdownMenuPrimitive.Content
63
+ ref={ref}
64
+ sideOffset={sideOffset}
65
+ className={cn(
66
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ </DropdownMenuPrimitive.Portal>
72
+ ))
73
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74
+
75
+ const DropdownMenuItem = React.forwardRef<
76
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
77
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
78
+ inset?: boolean
79
+ }
80
+ >(({ className, inset, ...props }, ref) => (
81
+ <DropdownMenuPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
85
+ inset && "pl-8",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ ))
91
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92
+
93
+ const DropdownMenuCheckboxItem = React.forwardRef<
94
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
95
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
96
+ >(({ className, children, checked, ...props }, ref) => (
97
+ <DropdownMenuPrimitive.CheckboxItem
98
+ ref={ref}
99
+ className={cn(
100
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
101
+ className
102
+ )}
103
+ checked={checked}
104
+ {...props}
105
+ >
106
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107
+ <DropdownMenuPrimitive.ItemIndicator>
108
+ <Check className="h-4 w-4" />
109
+ </DropdownMenuPrimitive.ItemIndicator>
110
+ </span>
111
+ {children}
112
+ </DropdownMenuPrimitive.CheckboxItem>
113
+ ))
114
+ DropdownMenuCheckboxItem.displayName =
115
+ DropdownMenuPrimitive.CheckboxItem.displayName
116
+
117
+ const DropdownMenuRadioItem = React.forwardRef<
118
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120
+ >(({ className, children, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ ref={ref}
123
+ className={cn(
124
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125
+ className
126
+ )}
127
+ {...props}
128
+ >
129
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130
+ <DropdownMenuPrimitive.ItemIndicator>
131
+ <Circle className="h-2 w-2 fill-current" />
132
+ </DropdownMenuPrimitive.ItemIndicator>
133
+ </span>
134
+ {children}
135
+ </DropdownMenuPrimitive.RadioItem>
136
+ ))
137
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138
+
139
+ const DropdownMenuLabel = React.forwardRef<
140
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
141
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
142
+ inset?: boolean
143
+ }
144
+ >(({ className, inset, ...props }, ref) => (
145
+ <DropdownMenuPrimitive.Label
146
+ ref={ref}
147
+ className={cn(
148
+ "px-2 py-1.5 text-sm font-semibold",
149
+ inset && "pl-8",
150
+ className
151
+ )}
152
+ {...props}
153
+ />
154
+ ))
155
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156
+
157
+ const DropdownMenuSeparator = React.forwardRef<
158
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
159
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
160
+ >(({ className, ...props }, ref) => (
161
+ <DropdownMenuPrimitive.Separator
162
+ ref={ref}
163
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
164
+ {...props}
165
+ />
166
+ ))
167
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168
+
169
+ const DropdownMenuShortcut = ({
170
+ className,
171
+ ...props
172
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
173
+ return (
174
+ <span
175
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181
+
182
+ export {
183
+ DropdownMenu,
184
+ DropdownMenuTrigger,
185
+ DropdownMenuContent,
186
+ DropdownMenuItem,
187
+ DropdownMenuCheckboxItem,
188
+ DropdownMenuRadioItem,
189
+ DropdownMenuLabel,
190
+ DropdownMenuSeparator,
191
+ DropdownMenuShortcut,
192
+ DropdownMenuGroup,
193
+ DropdownMenuPortal,
194
+ DropdownMenuSub,
195
+ DropdownMenuSubContent,
196
+ DropdownMenuSubTrigger,
197
+ DropdownMenuRadioGroup,
198
+ }
OpenAIChatAssistant/client/src/components/ui/form.tsx ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import {
5
+ Controller,
6
+ ControllerProps,
7
+ FieldPath,
8
+ FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from "react-hook-form"
12
+
13
+ import { cn } from "@/lib/utils"
14
+ import { Label } from "@/components/ui/label"
15
+
16
+ const Form = FormProvider
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
21
+ > = {
22
+ name: TName
23
+ }
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
26
+ {} as FormFieldContextValue
27
+ )
28
+
29
+ const FormField = <
30
+ TFieldValues extends FieldValues = FieldValues,
31
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
32
+ >({
33
+ ...props
34
+ }: ControllerProps<TFieldValues, TName>) => {
35
+ return (
36
+ <FormFieldContext.Provider value={{ name: props.name }}>
37
+ <Controller {...props} />
38
+ </FormFieldContext.Provider>
39
+ )
40
+ }
41
+
42
+ const useFormField = () => {
43
+ const fieldContext = React.useContext(FormFieldContext)
44
+ const itemContext = React.useContext(FormItemContext)
45
+ const { getFieldState, formState } = useFormContext()
46
+
47
+ const fieldState = getFieldState(fieldContext.name, formState)
48
+
49
+ if (!fieldContext) {
50
+ throw new Error("useFormField should be used within <FormField>")
51
+ }
52
+
53
+ const { id } = itemContext
54
+
55
+ return {
56
+ id,
57
+ name: fieldContext.name,
58
+ formItemId: `${id}-form-item`,
59
+ formDescriptionId: `${id}-form-item-description`,
60
+ formMessageId: `${id}-form-item-message`,
61
+ ...fieldState,
62
+ }
63
+ }
64
+
65
+ type FormItemContextValue = {
66
+ id: string
67
+ }
68
+
69
+ const FormItemContext = React.createContext<FormItemContextValue>(
70
+ {} as FormItemContextValue
71
+ )
72
+
73
+ const FormItem = React.forwardRef<
74
+ HTMLDivElement,
75
+ React.HTMLAttributes<HTMLDivElement>
76
+ >(({ className, ...props }, ref) => {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
82
+ </FormItemContext.Provider>
83
+ )
84
+ })
85
+ FormItem.displayName = "FormItem"
86
+
87
+ const FormLabel = React.forwardRef<
88
+ React.ElementRef<typeof LabelPrimitive.Root>,
89
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
90
+ >(({ className, ...props }, ref) => {
91
+ const { error, formItemId } = useFormField()
92
+
93
+ return (
94
+ <Label
95
+ ref={ref}
96
+ className={cn(error && "text-destructive", className)}
97
+ htmlFor={formItemId}
98
+ {...props}
99
+ />
100
+ )
101
+ })
102
+ FormLabel.displayName = "FormLabel"
103
+
104
+ const FormControl = React.forwardRef<
105
+ React.ElementRef<typeof Slot>,
106
+ React.ComponentPropsWithoutRef<typeof Slot>
107
+ >(({ ...props }, ref) => {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ ref={ref}
113
+ id={formItemId}
114
+ aria-describedby={
115
+ !error
116
+ ? `${formDescriptionId}`
117
+ : `${formDescriptionId} ${formMessageId}`
118
+ }
119
+ aria-invalid={!!error}
120
+ {...props}
121
+ />
122
+ )
123
+ })
124
+ FormControl.displayName = "FormControl"
125
+
126
+ const FormDescription = React.forwardRef<
127
+ HTMLParagraphElement,
128
+ React.HTMLAttributes<HTMLParagraphElement>
129
+ >(({ className, ...props }, ref) => {
130
+ const { formDescriptionId } = useFormField()
131
+
132
+ return (
133
+ <p
134
+ ref={ref}
135
+ id={formDescriptionId}
136
+ className={cn("text-sm text-muted-foreground", className)}
137
+ {...props}
138
+ />
139
+ )
140
+ })
141
+ FormDescription.displayName = "FormDescription"
142
+
143
+ const FormMessage = React.forwardRef<
144
+ HTMLParagraphElement,
145
+ React.HTMLAttributes<HTMLParagraphElement>
146
+ >(({ className, children, ...props }, ref) => {
147
+ const { error, formMessageId } = useFormField()
148
+ const body = error ? String(error?.message) : children
149
+
150
+ if (!body) {
151
+ return null
152
+ }
153
+
154
+ return (
155
+ <p
156
+ ref={ref}
157
+ id={formMessageId}
158
+ className={cn("text-sm font-medium text-destructive", className)}
159
+ {...props}
160
+ >
161
+ {body}
162
+ </p>
163
+ )
164
+ })
165
+ FormMessage.displayName = "FormMessage"
166
+
167
+ export {
168
+ useFormField,
169
+ Form,
170
+ FormItem,
171
+ FormLabel,
172
+ FormControl,
173
+ FormDescription,
174
+ FormMessage,
175
+ FormField,
176
+ }
OpenAIChatAssistant/client/src/components/ui/hover-card.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const HoverCard = HoverCardPrimitive.Root
9
+
10
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
11
+
12
+ const HoverCardContent = React.forwardRef<
13
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <HoverCardPrimitive.Content
17
+ ref={ref}
18
+ align={align}
19
+ sideOffset={sideOffset}
20
+ className={cn(
21
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]",
22
+ className
23
+ )}
24
+ {...props}
25
+ />
26
+ ))
27
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28
+
29
+ export { HoverCard, HoverCardTrigger, HoverCardContent }