noumanjavaid commited on
Commit
65c09e3
·
verified ·
1 Parent(s): 19385bc

Upload 23 files

Browse files
Files changed (23) hide show
  1. .dockerignore +44 -0
  2. .env +2 -0
  3. .env.docker.template +12 -0
  4. .env.local +2 -0
  5. .gitignore +24 -0
  6. .vercelignore +7 -0
  7. App.tsx +82 -0
  8. Dockerfile +13 -2
  9. LICENSE +202 -0
  10. README.md +66 -6
  11. docker-compose.yml +23 -0
  12. docker-deploy.bat +23 -0
  13. docker-deploy.sh +24 -0
  14. drizzle.config.ts +10 -0
  15. index.css +1342 -0
  16. index.html +45 -0
  17. index.tsx +33 -0
  18. metadata.json +8 -0
  19. package.json +44 -0
  20. server.cjs +37 -0
  21. tsconfig.json +29 -0
  22. vercel.json +1 -0
  23. vite.config.ts +28 -0
.dockerignore ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Version control
2
+ .git
3
+ .gitignore
4
+ .github
5
+
6
+ # Environment variables
7
+ .env
8
+ .env.local
9
+ .env.*
10
+ .env.docker
11
+ .env.docker.template
12
+
13
+ # Node.js
14
+ node_modules
15
+ npm-debug.log
16
+ yarn-debug.log
17
+ yarn-error.log
18
+
19
+ # Build outputs
20
+ dist
21
+
22
+ # Editor directories and files
23
+ .vscode
24
+ .idea
25
+ *.suo
26
+ *.ntvs*
27
+ *.njsproj
28
+ *.sln
29
+ *.sw?
30
+
31
+ # OS specific
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # Vercel
36
+ .vercel
37
+ .vercelignore
38
+
39
+ # Trae
40
+ .trae
41
+
42
+ # Misc
43
+ README.md
44
+ LICENSE
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ GEMINI_API_KEY='AIzaSyBykzJhB4HiulHdEK248Sb1xwcTBmhlwIM'
2
+ DATABASE_URL='postgresql://neondb_owner:npg_C68EwdfxDvru@ep-purple-snow-ad7h5sk9-pooler.c-2.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require'
.env.docker.template ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables for Docker deployment
2
+ # Rename this file to .env.docker and fill in your values
3
+
4
+ # API Keys
5
+ GEMINI_API_KEY=your_api_key_here
6
+
7
+ # Database
8
+ DATABASE_URL=your_database_url_here
9
+
10
+ # Application settings
11
+ NODE_ENV=production
12
+ PORT=3001
.env.local ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ GEMINI_API_KEY='AIzaSyBykzJhB4HiulHdEK248Sb1xwcTBmhlwIM'
2
+ DATABASE_URL='postgresql://neondb_owner:npg_C68EwdfxDvru@ep-purple-snow-ad7h5sk9-pooler.c-2.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require'
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
.vercelignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ build
3
+ dist
4
+ .git
5
+ .trae
6
+ .log
7
+ .figma
App.tsx ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Copyright 2024 Google LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ import ControlTray from './components/console/control-tray/ControlTray';
22
+ import ErrorScreen from './components/demo/ErrorSreen';
23
+ import KeynoteCompanion from './components/demo/keynote-companion/KeynoteCompanion';
24
+ import Header from './components/Header';
25
+ import UserSettings from './components/UserSettings';
26
+ import AuthWrapper from './components/AuthWrapper';
27
+ import { LiveAPIProvider } from './contexts/LiveAPIContext';
28
+ import { AuthProvider } from './contexts/AuthContext';
29
+ import { DatabaseProvider } from './contexts/DatabaseContext';
30
+ import { useUI } from './lib/state';
31
+
32
+ const API_KEY = process.env.GEMINI_API_KEY as string;
33
+ if (!API_KEY) {
34
+ const errorElement = document.getElementById('root');
35
+ if(errorElement) {
36
+ errorElement.innerHTML = `<div style="font-family: sans-serif; padding: 2rem; text-align: center;"><h1>Configuration Error</h1><p>Missing required environment variable: GEMINI_API_KEY</p><p>Please ask the application administrator to configure this.</p></div>`;
37
+ }
38
+ throw new Error(
39
+ 'Missing required environment variable: GEMINI_API_KEY'
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Main application component that provides a streaming interface for Live API.
45
+ * Manages video streaming state and provides controls for webcam/screen capture.
46
+ */
47
+ function AppContent() {
48
+ const { showUserConfig } = useUI();
49
+ return (
50
+ <div className="App">
51
+ <LiveAPIProvider apiKey={API_KEY}>
52
+ <ErrorScreen />
53
+ <Header />
54
+
55
+ {showUserConfig && <UserSettings />}
56
+ <div className="streaming-console">
57
+ <main>
58
+ <div className="main-app-area">
59
+ <KeynoteCompanion />
60
+ </div>
61
+
62
+ <ControlTray></ControlTray>
63
+ </main>
64
+ </div>
65
+ </LiveAPIProvider>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ function App() {
71
+ return (
72
+ <DatabaseProvider>
73
+ <AuthProvider>
74
+ <AuthWrapper>
75
+ <AppContent />
76
+ </AuthWrapper>
77
+ </AuthProvider>
78
+ </DatabaseProvider>
79
+ );
80
+ }
81
+
82
+ export default App;
Dockerfile CHANGED
@@ -1,11 +1,15 @@
1
  # Build stage
2
  FROM node:18-alpine AS build
3
 
 
 
 
 
4
  WORKDIR /app
5
 
6
  # Copy package files and install dependencies
7
  COPY package*.json ./
8
- RUN npm ci
9
 
10
  # Copy source code
11
  COPY . .
@@ -18,12 +22,15 @@ FROM node:18-alpine
18
 
19
  WORKDIR /app
20
 
 
 
 
21
  # Create a non-root user
22
  RUN addgroup -S appgroup && adduser -S appuser -G appgroup
23
 
24
  # Copy only production dependencies
25
  COPY package*.json ./
26
- RUN npm ci --only=production
27
 
28
  # Copy built application from build stage
29
  COPY --from=build /app/dist ./dist
@@ -43,6 +50,10 @@ RUN mkdir -p /app/config
43
  # Expose the port
44
  EXPOSE 3001
45
 
 
 
 
 
46
  # Switch to non-root user
47
  USER appuser
48
 
 
1
  # Build stage
2
  FROM node:18-alpine AS build
3
 
4
+ # Set build arguments
5
+ ARG NODE_ENV=production
6
+ ENV NODE_ENV=${NODE_ENV}
7
+
8
  WORKDIR /app
9
 
10
  # Copy package files and install dependencies
11
  COPY package*.json ./
12
+ RUN npm ci --no-audit --no-fund
13
 
14
  # Copy source code
15
  COPY . .
 
22
 
23
  WORKDIR /app
24
 
25
+ # Install wget for health check and other utilities
26
+ RUN apk --no-cache add wget curl
27
+
28
  # Create a non-root user
29
  RUN addgroup -S appgroup && adduser -S appuser -G appgroup
30
 
31
  # Copy only production dependencies
32
  COPY package*.json ./
33
+ RUN npm ci --only=production --no-audit --no-fund
34
 
35
  # Copy built application from build stage
36
  COPY --from=build /app/dist ./dist
 
50
  # Expose the port
51
  EXPOSE 3001
52
 
53
+ # Add health check
54
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
55
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
56
+
57
  # Switch to non-root user
58
  USER appuser
59
 
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
README.md CHANGED
@@ -1,10 +1,70 @@
1
  ---
2
- title: New React
3
- emoji: 💻
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: docker
7
- pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Final Alif Implementation
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ app_port: 3001
8
  ---
9
 
10
+ # Final Alif Implementation
11
+
12
+ A Node.js application with React frontend.
13
+
14
+ ## Docker Deployment
15
+
16
+ ### Using Docker
17
+
18
+ 1. Build the Docker image:
19
+ ```bash
20
+ docker build -t final-alif-implementation .
21
+ ```
22
+
23
+ 2. Run the container:
24
+ ```bash
25
+ docker run -p 3001:3001 \
26
+ -e GEMINI_API_KEY=your_api_key \
27
+ -e DATABASE_URL=your_database_url \
28
+ final-alif-implementation
29
+ ```
30
+
31
+ ### Using Docker Compose
32
+
33
+ 1. Create a `.env.docker` file from the template:
34
+ ```bash
35
+ # On Windows
36
+ copy .env.docker.template .env.docker
37
+
38
+ # On Linux/Mac
39
+ cp .env.docker.template .env.docker
40
+ ```
41
+
42
+ 2. Edit the `.env.docker` file with your environment variables
43
+
44
+ 3. Run with Docker Compose:
45
+ ```bash
46
+ docker-compose up -d
47
+ ```
48
+
49
+ Or use the provided deployment scripts:
50
+ ```bash
51
+ # On Windows
52
+ .\docker-deploy.bat
53
+
54
+ # On Linux/Mac
55
+ ./docker-deploy.sh
56
+ ```
57
+
58
+ 4. Access the application at http://localhost:3001
59
+
60
+ ## Development
61
+
62
+ 1. Install dependencies:
63
+ ```bash
64
+ npm install
65
+ ```
66
+
67
+ 2. Run the development server:
68
+ ```bash
69
+ npm run dev
70
+ ```
docker-compose.yml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ app:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ ports:
9
+ - "3001:3001"
10
+ environment:
11
+ - NODE_ENV=production
12
+ - PORT=3001
13
+ # Environment variables from file
14
+ env_file:
15
+ - .env.docker
16
+ restart: unless-stopped
17
+ # For production, consider adding healthcheck
18
+ healthcheck:
19
+ test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
20
+ interval: 30s
21
+ timeout: 10s
22
+ retries: 3
23
+ start_period: 40s
docker-deploy.bat ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ REM Docker deployment script for Final Alif Implementation
3
+
4
+ REM Check if .env.docker exists
5
+ IF NOT EXIST .env.docker (
6
+ echo Creating .env.docker file...
7
+ echo # Add your environment variables below > .env.docker
8
+ echo GEMINI_API_KEY=your_api_key >> .env.docker
9
+ echo DATABASE_URL=your_database_url >> .env.docker
10
+ echo .env.docker created. Please edit it with your actual environment variables.
11
+ exit /b 1
12
+ )
13
+
14
+ REM Build the Docker image
15
+ echo Building Docker image...
16
+ docker build -t final-alif-implementation .
17
+
18
+ REM Run with Docker Compose
19
+ echo Starting application with Docker Compose...
20
+ docker-compose up -d
21
+
22
+ echo Application deployed! Access it at http://localhost:3001
23
+ echo Check logs with: docker-compose logs -f
docker-deploy.sh ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Docker deployment script for Final Alif Implementation
4
+
5
+ # Check if .env.docker exists
6
+ if [ ! -f ".env.docker" ]; then
7
+ echo "Creating .env.docker file..."
8
+ echo "# Add your environment variables below" > .env.docker
9
+ echo "GEMINI_API_KEY=your_api_key" >> .env.docker
10
+ echo "DATABASE_URL=your_database_url" >> .env.docker
11
+ echo ".env.docker created. Please edit it with your actual environment variables."
12
+ exit 1
13
+ fi
14
+
15
+ # Build the Docker image
16
+ echo "Building Docker image..."
17
+ docker build -t final-alif-implementation .
18
+
19
+ # Run with Docker Compose
20
+ echo "Starting application with Docker Compose..."
21
+ docker-compose up -d
22
+
23
+ echo "Application deployed! Access it at http://localhost:3001"
24
+ echo "Check logs with: docker-compose logs -f"
drizzle.config.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "drizzle-kit";
2
+
3
+ export default {
4
+ schema: "./lib/db.ts",
5
+ out: "./drizzle",
6
+ dialect: "postgresql",
7
+ dbCredentials: {
8
+ connectionString: process.env.DATABASE_URL!,
9
+ },
10
+ } satisfies Config;
index.css ADDED
@@ -0,0 +1,1342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ margin: 0;
3
+ font-family:
4
+ 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6
+ sans-serif;
7
+ -webkit-font-smoothing: antialiased;
8
+ -moz-osx-font-smoothing: grayscale;
9
+ }
10
+
11
+ code {
12
+ font-family:
13
+ source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
14
+ }
15
+
16
+ :root {
17
+ --font-family: 'Nunito', sans-serif;
18
+
19
+ /* Theme Colors */
20
+ --bg-main: #fffaf0; /* Floral White */
21
+ --bg-secondary: #ffffff;
22
+ --text-primary: #5d4037; /* Brownish */
23
+ --text-secondary: #8d6e63;
24
+ --border-color: #d7ccc8;
25
+ --shadow-color: rgba(93, 64, 55, 0.1);
26
+
27
+ --accent-blue: #81d4fa;
28
+ --accent-green: #a5d6a7;
29
+ --accent-red: #ef9a9a;
30
+ --accent-yellow: #fff59d;
31
+ --accent-purple: #ce93d8;
32
+
33
+ --button-primary-bg: #4db6ac; /* Teal */
34
+ --button-primary-text: white;
35
+ --button-primary-hover: #26a69a;
36
+ --button-secondary-bg: #f5f5f5;
37
+ --button-secondary-text: var(--text-primary);
38
+ --button-secondary-hover: #eeeeee;
39
+
40
+
41
+ --mic-active: #ff7043; /* Deep Orange */
42
+ --mic-inactive: #bcaaa4;
43
+
44
+ /* Original variables (some might be unused now) */
45
+ --text: var(--text-primary);
46
+ --gray-200: #e0e0e0;
47
+ --gray-300: #bdbdbd;
48
+ --gray-500: #9e9e9e;
49
+ --gray-600: #757575;
50
+ --gray-700: #616161;
51
+ --gray-800: #424242;
52
+ --gray-900: #212121;
53
+ --gray-1000: #000000;
54
+ --border-stroke: var(--border-color);
55
+ --background: var(--bg-main);
56
+ --color: var(--text-primary);
57
+ scrollbar-color: var(--gray-500) var(--bg-main);
58
+ scrollbar-width: thin;
59
+
60
+ --card-border-radius: 24px;
61
+ --breakpoint-md: 768px;
62
+ }
63
+
64
+ * {
65
+ margin: 0;
66
+ padding: 0;
67
+ box-sizing: border-box;
68
+ }
69
+
70
+ body {
71
+ font-family: var(--font-family);
72
+ background: var(--bg-main);
73
+ color: var(--text-primary);
74
+ }
75
+
76
+ h1,
77
+ h2,
78
+ h3,
79
+ h4,
80
+ h5,
81
+ h6 {
82
+ font-weight: 700;
83
+ color: var(--text-primary);
84
+ }
85
+
86
+ li {
87
+ list-style: none;
88
+ }
89
+
90
+ input,
91
+ textarea {
92
+ font-family: var(--font-family);
93
+ background: var(--bg-secondary);
94
+ color: var(--text-primary);
95
+ border: 1px solid var(--border-color);
96
+ outline: none;
97
+ font-size: 16px;
98
+ resize: none;
99
+ -webkit-user-select: text;
100
+ user-select: text;
101
+ border-radius: 12px;
102
+ padding: 12px;
103
+ }
104
+
105
+ input::placeholder,
106
+ textarea::placeholder {
107
+ user-select: none;
108
+ color: var(--text-secondary);
109
+ }
110
+
111
+ select {
112
+ font-family: inherit;
113
+ padding: 10px;
114
+ border: 1px solid var(--border-color);
115
+ background: var(--bg-secondary);
116
+ color: var(--text-primary);
117
+ border-radius: 12px;
118
+ font-size: 16px;
119
+ cursor: pointer;
120
+ accent-color: var(--button-primary-bg);
121
+ }
122
+
123
+ select:focus-visible {
124
+ outline: 2px solid var(--button-primary-bg);
125
+ }
126
+
127
+ button {
128
+ font-family: var(--font-family);
129
+ background: none;
130
+ color: var(--text-primary);
131
+ border: none;
132
+ font-size: 16px;
133
+ font-weight: 700;
134
+ cursor: pointer;
135
+ user-select: none;
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 5px;
139
+ border-radius: 12px;
140
+ padding: 10px 15px;
141
+ transition: all 0.2s ease-in-out;
142
+ }
143
+
144
+ button.primary {
145
+ background: var(--button-primary-bg);
146
+ color: var(--button-primary-text);
147
+ }
148
+ button.primary:hover {
149
+ background: var(--button-primary-hover);
150
+ }
151
+ button.icon {
152
+ font-size: 1.2em;
153
+ }
154
+
155
+ .button {
156
+ background: var(--button-secondary-bg);
157
+ color: var(--button-secondary-text);
158
+ display: inline-flex;
159
+ padding: 10px;
160
+ border-radius: 12px;
161
+ gap: 4px;
162
+ align-items: center;
163
+ justify-content: center;
164
+ font-weight: 700;
165
+ }
166
+ .button:hover {
167
+ background: var(--button-secondary-hover);
168
+ }
169
+
170
+
171
+ .button .icon {
172
+ font-size: 1.2em;
173
+ }
174
+
175
+ button:focus {
176
+ outline: none;
177
+ }
178
+
179
+ button:focus-visible {
180
+ box-shadow: 0 0 0 3px var(--accent-blue);
181
+ }
182
+
183
+ button[disabled] {
184
+ opacity: 0.5;
185
+ cursor: not-allowed;
186
+ }
187
+
188
+ button .icon {
189
+ display: block;
190
+ }
191
+
192
+ .icon {
193
+ font-family: 'Material Symbols Outlined';
194
+ font-weight: 300;
195
+ line-height: 1;
196
+ }
197
+
198
+ .hidden {
199
+ display: none;
200
+ }
201
+
202
+ header {
203
+ padding: 20px;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ position: fixed;
208
+ width: 100%;
209
+ z-index: 999;
210
+ }
211
+
212
+ .streaming-console {
213
+ background: transparent;
214
+ color: var(--text-secondary);
215
+ display: flex;
216
+ height: 100vh;
217
+ width: 100vw;
218
+ }
219
+
220
+ .streaming-console main {
221
+ position: relative;
222
+ display: flex;
223
+ flex-direction: column;
224
+ align-items: center;
225
+ justify-content: center;
226
+ flex-grow: 1;
227
+ gap: 1rem;
228
+ max-width: 100%;
229
+ overflow: hidden;
230
+ }
231
+ .streaming-console .main-app-area {
232
+ display: flex;
233
+ flex: 1;
234
+ align-items: center;
235
+ justify-content: center;
236
+ position: relative;
237
+ width: 100%;
238
+ }
239
+
240
+ .action-button {
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ background: var(--bg-secondary);
245
+ color: var(--mic-inactive);
246
+ font-size: 1.5rem;
247
+ cursor: pointer;
248
+ transition: all 0.2s ease-in-out;
249
+ width: 64px;
250
+ height: 64px;
251
+ border-radius: 50%;
252
+ border: 2px solid var(--border-color);
253
+ user-select: none;
254
+ box-shadow: 0 4px 10px var(--shadow-color);
255
+ }
256
+ .action-button:hover {
257
+ transform: translateY(-2px);
258
+ box-shadow: 0 6px 15px var(--shadow-color);
259
+ }
260
+ .action-button:focus {
261
+ border: 2px solid var(--button-primary-bg);
262
+ outline: none;
263
+ }
264
+ .action-button.connected {
265
+ background: var(--mic-active);
266
+ color: white;
267
+ border-color: transparent;
268
+ }
269
+
270
+ .mic-button {
271
+ position: relative;
272
+ background-color: var(--mic-active);
273
+ z-index: 1;
274
+ color: white;
275
+ width: 80px;
276
+ height: 80px;
277
+ font-size: 2rem;
278
+ border-radius: 50%;
279
+ }
280
+ .mic-button.bot-speaking {
281
+ background-color: var(--mic-inactive);
282
+ }
283
+
284
+ .mic-button:focus {
285
+ border: none;
286
+ outline: 3px solid var(--accent-red);
287
+ }
288
+ .mic-button:hover {
289
+ background-color: #ff8a65;
290
+ }
291
+ .mic-button.bot-speaking:hover {
292
+ background-color: var(--mic-inactive);
293
+ }
294
+
295
+ .connect-button {
296
+ font-size: 1.5rem;
297
+ padding: 1rem 2rem;
298
+ border-radius: 99px;
299
+ background-color: var(--button-primary-bg);
300
+ color: var(--button-primary-text);
301
+ }
302
+ .connect-button:hover {
303
+ background-color: var(--button-primary-hover);
304
+ }
305
+
306
+
307
+ .control-tray {
308
+ position: absolute;
309
+ bottom: 0;
310
+ left: 50%;
311
+ transform: translate(-50%, 0);
312
+ display: inline-flex;
313
+ justify-content: center;
314
+ align-items: center;
315
+ gap: 1.5rem;
316
+ padding: 1rem;
317
+ margin-bottom: 2rem;
318
+ background-color: rgba(255, 255, 255, 0.7);
319
+ backdrop-filter: blur(10px);
320
+ border-radius: 99px;
321
+ box-shadow: 0 4px 20px var(--shadow-color);
322
+ z-index: 12;
323
+ transition: all 0.3s ease-in-out;
324
+ }
325
+
326
+ .control-tray.connected {
327
+ width: 80%;
328
+ max-width: 900px;
329
+ gap: 1rem;
330
+ padding: 1rem 1.5rem;
331
+ border-radius: 3rem;
332
+ align-items: center;
333
+ }
334
+
335
+ .caption-display {
336
+ flex-grow: 1;
337
+ text-align: center;
338
+ overflow: hidden;
339
+ }
340
+
341
+ .caption-display p {
342
+ font-size: 1.5rem;
343
+ font-weight: 700;
344
+ color: var(--text-primary);
345
+ margin: 0;
346
+ opacity: 0;
347
+ transform: translateY(10px);
348
+ transition: opacity 0.3s ease-out, transform 0.3s ease-out;
349
+ white-space: normal;
350
+ line-height: 1.3;
351
+ }
352
+
353
+ .caption-display p.visible {
354
+ opacity: 1;
355
+ transform: translateY(0);
356
+ }
357
+
358
+ .end-session-button {
359
+ background-color: var(--accent-red);
360
+ color: white;
361
+ padding: 0.8rem 1.5rem;
362
+ border-radius: 99px;
363
+ flex-shrink: 0;
364
+ }
365
+
366
+ .end-session-button:hover {
367
+ background-color: #d32f2f;
368
+ }
369
+
370
+
371
+ .control-tray .disabled .action-button,
372
+ .control-tray .action-button.disabled {
373
+ background: #f5f5f5;
374
+ border: 2px solid #e0e0e0;
375
+ color: #bdbdbd;
376
+ box-shadow: none;
377
+ }
378
+
379
+ .actions-nav {
380
+ background: transparent;
381
+ border: none;
382
+ display: inline-flex;
383
+ align-items: center;
384
+ transition: all 0.6s ease-in;
385
+ }
386
+
387
+ /* Modal */
388
+ .modalShroud {
389
+ position: fixed;
390
+ inset: 0;
391
+ display: flex;
392
+ justify-content: center;
393
+ align-items: center;
394
+ z-index: 9999;
395
+ background: rgba(93, 64, 55, 0.3);
396
+ backdrop-filter: blur(5px);
397
+ }
398
+
399
+ .modalShroud .modal {
400
+ background: var(--bg-secondary);
401
+ border: 1px solid var(--border-color);
402
+ border-radius: var(--card-border-radius);
403
+ padding: 40px;
404
+ position: relative;
405
+ width: 100%;
406
+ box-shadow: 0 10px 30px var(--shadow-color);
407
+ }
408
+ @media (min-width: 768px) {
409
+ .modalShroud .modal {
410
+ max-width: 500px;
411
+ }
412
+ }
413
+ .modalShroud .modal {
414
+ max-height: 80vh;
415
+ overflow: auto;
416
+ margin: 20px;
417
+ }
418
+
419
+ .modalClose {
420
+ position: absolute;
421
+ top: 15px;
422
+ right: 15px;
423
+ color: var(--text-secondary);
424
+ font-size: 30px;
425
+ border-radius: 50%;
426
+ width: 40px;
427
+ height: 40px;
428
+ justify-content: center;
429
+ padding: 0;
430
+ }
431
+ .modalClose:hover {
432
+ background-color: #f5f5f5;
433
+ color: var(--text-primary);
434
+ }
435
+
436
+ /* UserSettings */
437
+ .userSettings {
438
+ max-width: 480px;
439
+ display: flex;
440
+ flex-direction: column;
441
+ gap: 15px;
442
+ text-align: center;
443
+ }
444
+ .userSettings h2 {
445
+ font-size: 24px;
446
+ margin-bottom: 0;
447
+ }
448
+
449
+ .userSettings p {
450
+ font-size: 16px;
451
+ line-height: 1.6;
452
+ color: var(--text-secondary);
453
+ }
454
+ @media (min-width: 768px) {
455
+ .userSettings p {
456
+ font-size: 18px;
457
+ }
458
+ }
459
+
460
+ .userSettings form {
461
+ margin-top: 10px;
462
+ padding-top: 10px;
463
+ text-align: left;
464
+ }
465
+ .userSettings form .divider {
466
+ height: 1px;
467
+ background-color: var(--border-color);
468
+ margin: 20px 0;
469
+ }
470
+ form > div p {
471
+ font-weight: 700;
472
+ color: var(--text-primary);
473
+ font-size: 14px;
474
+ }
475
+
476
+ .userSettingsButton {
477
+ display: flex;
478
+ align-items: center;
479
+ gap: 8px;
480
+ color: var(--text-secondary);
481
+ transition: all 0.3s;
482
+ font-size: 16px;
483
+ background-color: var(--bg-secondary);
484
+ padding: 8px 16px;
485
+ border-radius: 99px;
486
+ box-shadow: 0 2px 8px var(--shadow-color);
487
+ }
488
+ @media (min-width: 768px) {
489
+ .userSettingsButton {
490
+ font-size: 18px;
491
+ }
492
+ }
493
+
494
+ .userSettingsButton .user-name {
495
+ display: block;
496
+ }
497
+
498
+ .userSettingsButton .icon {
499
+ font-size: 24px;
500
+ }
501
+
502
+ .sticky-header {
503
+ position: -webkit-sticky; /* Safari */
504
+ position: sticky;
505
+ top: 0;
506
+ background-color: var(--bg-main);
507
+ border-bottom: 1px solid var(--border-color);
508
+ box-shadow: 0 2px 4px var(--shadow-color);
509
+ }
510
+
511
+ .userSettingsButton:hover {
512
+ color: var(--text-primary);
513
+ box-shadow: 0 4px 12px var(--shadow-color);
514
+ }
515
+
516
+ /* Form */
517
+ form {
518
+ display: flex;
519
+ flex-direction: column;
520
+ gap: 20px;
521
+ }
522
+
523
+ form .largeInput {
524
+ font-size: 16px;
525
+ border-bottom: 1px solid var(--border-color);
526
+ border-radius: 0;
527
+ background: transparent;
528
+ border-left: none;
529
+ border-right: none;
530
+ border-top: none;
531
+ }
532
+ @media (min-width: 768px) {
533
+ form .largeInput {
534
+ font-size: 28px;
535
+ }
536
+ }
537
+
538
+ form > div,
539
+ form label {
540
+ display: flex;
541
+ flex-direction: column;
542
+ gap: 10px;
543
+ user-select: none;
544
+ }
545
+
546
+ form input,
547
+ form textarea {
548
+ font-size: 14px;
549
+ line-height: 1.4;
550
+ }
551
+ @media (min-width: 768px) {
552
+ form input,
553
+ form textarea {
554
+ font-size: 18px;
555
+ }
556
+ }
557
+
558
+ form input:focus,
559
+ form textarea:focus {
560
+ border-color: var(--button-primary-bg);
561
+ box-shadow: 0 0 0 3px rgba(77, 182, 172, 0.3);
562
+ background: white;
563
+ }
564
+
565
+ form textarea {
566
+ resize: none;
567
+ }
568
+
569
+ form button.primary {
570
+ margin-top: 10px;
571
+ padding: 15px;
572
+ font-size: 18px;
573
+ }
574
+
575
+ /* agenst */
576
+ .roomInfo {
577
+ position: relative;
578
+ }
579
+
580
+ .roomInfo h1 {
581
+ font-size: 22px;
582
+ font-weight: 700;
583
+ display: flex;
584
+ align-items: center;
585
+ opacity: 0.8;
586
+ transition: all 0.2s;
587
+ text-align: left;
588
+ color: var(--text-primary);
589
+ }
590
+ @media (min-width: 768px) {
591
+ .roomInfo h1 {
592
+ font-size: 28px;
593
+ }
594
+ }
595
+
596
+ .roomInfo h1:hover,
597
+ .roomInfo h1.active {
598
+ opacity: 1;
599
+ }
600
+
601
+ .roomList {
602
+ display: flex;
603
+ flex-direction: column;
604
+ gap: 10px;
605
+ position: absolute;
606
+ background: var(--bg-secondary);
607
+ border: 1px solid var(--border-color);
608
+ padding: 20px;
609
+ border-radius: var(--card-border-radius);
610
+ width: 300px;
611
+ margin-top: 10px;
612
+ opacity: 0;
613
+ pointer-events: none;
614
+ transition: all 0.1s;
615
+ user-select: none;
616
+ max-height: 70vh;
617
+ overflow: auto;
618
+ box-shadow: 0 5px 20px var(--shadow-color);
619
+ }
620
+
621
+ .roomList.active {
622
+ opacity: 1;
623
+ pointer-events: auto;
624
+ }
625
+
626
+ .roomList h3 {
627
+ font-size: 16px;
628
+ font-weight: 700;
629
+ margin-bottom: 10px;
630
+ color: var(--text-secondary);
631
+ }
632
+
633
+ .roomList ul {
634
+ display: flex;
635
+ flex-direction: column;
636
+ gap: 5px;
637
+ border-bottom: 1px solid var(--border-color);
638
+ padding-bottom: 15px;
639
+ margin-bottom: 5px;
640
+ }
641
+
642
+ .roomList li {
643
+ font-size: 20px;
644
+ }
645
+
646
+ .roomList li button {
647
+ color: var(--text-secondary);
648
+ transition: all 0.2s;
649
+ width: 100%;
650
+ justify-content: flex-start;
651
+ padding: 10px;
652
+ }
653
+
654
+ .roomList li.active button,
655
+ .roomList li:hover button {
656
+ color: var(--text-primary);
657
+ background-color: var(--bg-main);
658
+ }
659
+
660
+ .error-screen {
661
+ display: flex;
662
+ flex-direction: column;
663
+ align-items: center;
664
+ justify-content: center;
665
+ height: 100dvh;
666
+ width: 100%;
667
+ background: var(--bg-main);
668
+ color: var(--text-primary);
669
+ gap: 48px;
670
+ position: absolute;
671
+ top: 50%;
672
+ left: 50%;
673
+ transform: translate(-50%, -50%);
674
+ z-index: 99991;
675
+ }
676
+
677
+ .error-screen .error-message-container,
678
+ .error-screen .error-raw-message-container {
679
+ width: 100%;
680
+ text-align: center;
681
+ max-width: 650px;
682
+ padding-left: 0.5rem;
683
+ padding-right: 0.5rem;
684
+ }
685
+
686
+ .error-screen .close-button {
687
+ color: var(--text-primary);
688
+ font-size: 24px;
689
+ }
690
+
691
+ /* Remove unused styles for simplicity */
692
+ .agentPreview,
693
+ .editAgent,
694
+ .voicePicker,
695
+ .colorPicker {
696
+ display: none;
697
+ }
698
+
699
+ .keynote-companion {
700
+ align-items: center;
701
+ display: flex;
702
+ height: 100%;
703
+ justify-content: center;
704
+ position: relative;
705
+ width: 100%;
706
+ z-index: 1;
707
+ }
708
+
709
+ .keynote-companion .basic-face-container {
710
+ position: relative;
711
+ z-index: 5;
712
+ transition: all 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
713
+ }
714
+
715
+ .keynote-companion .basic-face {
716
+ box-shadow: 0px 10px 30px var(--shadow-color);
717
+ transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
718
+ }
719
+
720
+ /* "Thinking" animation for the face */
721
+ .basic-face-container.generating .basic-face {
722
+ animation: pulse-shadow 2s infinite;
723
+ }
724
+
725
+ @keyframes pulse-shadow {
726
+ 0%,
727
+ 100% {
728
+ box-shadow: 0px 10px 30px var(--shadow-color);
729
+ }
730
+ 50% {
731
+ box-shadow: 0px 15px 45px var(--accent-blue);
732
+ }
733
+ }
734
+
735
+ /* When images are visible, move face to the side */
736
+ .keynote-companion.images-visible .basic-face-container {
737
+ position: absolute;
738
+ bottom: 20px;
739
+ left: 20px;
740
+ transform: scale(0.6);
741
+ transform-origin: bottom left;
742
+ }
743
+
744
+ /* AuthWrapper Styles */
745
+ .auth-loading-container {
746
+ display: flex;
747
+ align-items: center;
748
+ justify-content: center;
749
+ min-height: 100vh;
750
+ background: linear-gradient(135deg, #dbeafe 0%, #e0e7ff 100%);
751
+ }
752
+
753
+ .auth-loading-content {
754
+ text-align: center;
755
+ }
756
+
757
+ .auth-spinner {
758
+ width: 48px;
759
+ height: 48px;
760
+ border: 2px solid transparent;
761
+ border-bottom: 2px solid #2563eb;
762
+ border-radius: 50%;
763
+ animation: spin 1s linear infinite;
764
+ margin: 0 auto 16px;
765
+ }
766
+
767
+ .auth-loading-text {
768
+ font-size: 18px;
769
+ color: #374151;
770
+ font-weight: 500;
771
+ }
772
+
773
+ .auth-signin-container {
774
+ display: flex;
775
+ align-items: center;
776
+ justify-content: center;
777
+ min-height: 100vh;
778
+ background: linear-gradient(135deg, #dbeafe 0%, #e0e7ff 50%, #ede9fe 100%);
779
+ }
780
+
781
+ .auth-signin-card {
782
+ background: white;
783
+ padding: 40px;
784
+ border-radius: 16px;
785
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
786
+ text-align: center;
787
+ max-width: 448px;
788
+ width: 100%;
789
+ margin: 16px;
790
+ border: 1px solid #f3f4f6;
791
+ }
792
+
793
+ .auth-logo-container {
794
+ margin-bottom: 32px;
795
+ }
796
+
797
+ .auth-logo {
798
+ width: 64px;
799
+ height: 64px;
800
+ background: linear-gradient(135deg, #2563eb 0%, #4f46e5 100%);
801
+ border-radius: 50%;
802
+ display: flex;
803
+ align-items: center;
804
+ justify-content: center;
805
+ margin: 0 auto 16px;
806
+ }
807
+
808
+ .auth-logo svg {
809
+ width: 32px;
810
+ height: 32px;
811
+ color: white;
812
+ }
813
+
814
+ .auth-title {
815
+ font-size: 30px;
816
+ font-weight: bold;
817
+ color: #111827;
818
+ margin-bottom: 8px;
819
+ }
820
+
821
+ .auth-subtitle {
822
+ color: #6b7280;
823
+ font-size: 18px;
824
+ }
825
+
826
+ .auth-signin-content {
827
+ margin-bottom: 24px;
828
+ }
829
+
830
+ .auth-signin-text {
831
+ color: #374151;
832
+ margin-bottom: 16px;
833
+ }
834
+
835
+ .auth-error {
836
+ background: #fef2f2;
837
+ border: 1px solid #fecaca;
838
+ color: #dc2626;
839
+ padding: 12px 16px;
840
+ border-radius: 8px;
841
+ margin-bottom: 16px;
842
+ font-size: 14px;
843
+ }
844
+
845
+ .auth-warning {
846
+ background: #fffbeb;
847
+ border: 1px solid #fed7aa;
848
+ color: #d97706;
849
+ padding: 12px 16px;
850
+ border-radius: 8px;
851
+ margin-bottom: 16px;
852
+ font-size: 14px;
853
+ }
854
+
855
+ .auth-google-button {
856
+ width: 100%;
857
+ background: white;
858
+ border: 2px solid #d1d5db;
859
+ color: #374151;
860
+ font-weight: 600;
861
+ padding: 12px 24px;
862
+ border-radius: 12px;
863
+ cursor: pointer;
864
+ transition: all 0.2s;
865
+ display: flex;
866
+ align-items: center;
867
+ justify-content: center;
868
+ gap: 12px;
869
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
870
+ }
871
+
872
+ .auth-google-button:hover {
873
+ background: #f9fafb;
874
+ border-color: #9ca3af;
875
+ }
876
+
877
+ .auth-google-button:focus {
878
+ outline: none;
879
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
880
+ }
881
+
882
+ .auth-google-button:disabled {
883
+ opacity: 0.5;
884
+ cursor: not-allowed;
885
+ }
886
+
887
+ .auth-button-spinner {
888
+ width: 20px;
889
+ height: 20px;
890
+ border: 2px solid transparent;
891
+ border-bottom: 2px solid #4b5563;
892
+ border-radius: 50%;
893
+ animation: spin 1s linear infinite;
894
+ }
895
+
896
+ .auth-google-icon {
897
+ width: 20px;
898
+ height: 20px;
899
+ }
900
+
901
+ .auth-terms {
902
+ margin-top: 24px;
903
+ font-size: 12px;
904
+ color: #6b7280;
905
+ }
906
+
907
+ .auth-header {
908
+ background: white;
909
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
910
+ border-bottom: 1px solid #e5e7eb;
911
+ }
912
+
913
+ .auth-header-content {
914
+ max-width: 1280px;
915
+ margin: 0 auto;
916
+ padding: 0 16px;
917
+ }
918
+
919
+ .auth-header-inner {
920
+ display: flex;
921
+ justify-content: space-between;
922
+ align-items: center;
923
+ padding: 16px 0;
924
+ }
925
+
926
+ .auth-brand {
927
+ display: flex;
928
+ align-items: center;
929
+ gap: 12px;
930
+ }
931
+
932
+ .auth-brand-logo {
933
+ width: 40px;
934
+ height: 40px;
935
+ background: linear-gradient(135deg, #2563eb 0%, #4f46e5 100%);
936
+ border-radius: 8px;
937
+ display: flex;
938
+ align-items: center;
939
+ justify-content: center;
940
+ }
941
+
942
+ .auth-brand-logo svg {
943
+ width: 24px;
944
+ height: 24px;
945
+ color: white;
946
+ }
947
+
948
+ .auth-brand-text {
949
+ font-size: 20px;
950
+ font-weight: bold;
951
+ color: #111827;
952
+ }
953
+
954
+ .auth-user-section {
955
+ display: flex;
956
+ align-items: center;
957
+ gap: 16px;
958
+ }
959
+
960
+ .auth-warning-indicator {
961
+ display: flex;
962
+ align-items: center;
963
+ gap: 8px;
964
+ color: #d97706;
965
+ }
966
+
967
+ .auth-warning-indicator svg {
968
+ width: 16px;
969
+ height: 16px;
970
+ }
971
+
972
+ .auth-warning-text {
973
+ font-size: 14px;
974
+ font-weight: 500;
975
+ }
976
+
977
+ .auth-user-info {
978
+ display: flex;
979
+ align-items: center;
980
+ gap: 12px;
981
+ background: #f9fafb;
982
+ border-radius: 8px;
983
+ padding: 8px 12px;
984
+ }
985
+
986
+ .auth-user-avatar {
987
+ width: 32px;
988
+ height: 32px;
989
+ border-radius: 50%;
990
+ border: 2px solid white;
991
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
992
+ }
993
+
994
+ .auth-user-details {
995
+ text-align: left;
996
+ }
997
+
998
+ .auth-user-name {
999
+ font-size: 14px;
1000
+ font-weight: 500;
1001
+ color: #111827;
1002
+ }
1003
+
1004
+ .auth-user-email {
1005
+ font-size: 12px;
1006
+ color: #6b7280;
1007
+ }
1008
+
1009
+ .auth-logout-button {
1010
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
1011
+ color: white;
1012
+ font-weight: 500;
1013
+ padding: 8px 16px;
1014
+ border-radius: 8px;
1015
+ font-size: 14px;
1016
+ cursor: pointer;
1017
+ border: none;
1018
+ transition: all 0.2s;
1019
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
1020
+ }
1021
+
1022
+ .auth-logout-button:hover {
1023
+ background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
1024
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
1025
+ }
1026
+
1027
+ .auth-logout-button:focus {
1028
+ outline: none;
1029
+ box-shadow: 0 0 0 2px #ef4444, 0 0 0 4px rgba(239, 68, 68, 0.1);
1030
+ }
1031
+
1032
+ .auth-main-container {
1033
+ min-height: 100vh;
1034
+ background: #f9fafb;
1035
+ }
1036
+
1037
+ .auth-content {
1038
+ flex: 1;
1039
+ }
1040
+
1041
+ @keyframes spin {
1042
+ from {
1043
+ transform: rotate(0deg);
1044
+ }
1045
+ to {
1046
+ transform: rotate(360deg);
1047
+ }
1048
+ }
1049
+
1050
+ @media (min-width: 640px) {
1051
+ .auth-header-content {
1052
+ padding: 0 24px;
1053
+ }
1054
+ }
1055
+
1056
+ @media (min-width: 1024px) {
1057
+ .auth-header-content {
1058
+ padding: 0 32px;
1059
+ }
1060
+ }
1061
+
1062
+ /* Clean up unnecessary old styles that are not in the provided files */
1063
+ .control-tray .connection-container,
1064
+ .top,
1065
+ .roomDescription,
1066
+ .createButton,
1067
+ .deleteRoomButton,
1068
+ .roomList .newRoomButton,
1069
+ .counter-container,
1070
+ .streaming-console .function-call,
1071
+ .actions-nav > *,
1072
+ .mic-button:before,
1073
+ .connect-toggle:not(.connected) {
1074
+ display: none !important;
1075
+ }
1076
+
1077
+ .actions-nav {
1078
+ padding: 0;
1079
+ gap: 0;
1080
+ }
1081
+
1082
+ .control-tray {
1083
+ padding: 1rem;
1084
+ }
1085
+
1086
+ /* Loading Indicator */
1087
+ .loading-indicator {
1088
+ position: absolute;
1089
+ top: 50%;
1090
+ left: 50%;
1091
+ transform: translate(-50%, -50%);
1092
+ z-index: 10;
1093
+ display: flex;
1094
+ align-items: center;
1095
+ justify-content: center;
1096
+ }
1097
+
1098
+ .loading-indicator.large {
1099
+ display: flex;
1100
+ flex-direction: column;
1101
+ gap: 20px;
1102
+ align-items: center;
1103
+ color: var(--text-secondary);
1104
+ font-size: 1.2rem;
1105
+ font-weight: 700;
1106
+ }
1107
+
1108
+ .loading-indicator.large .spinner {
1109
+ width: 80px;
1110
+ height: 80px;
1111
+ border-width: 6px;
1112
+ }
1113
+
1114
+ .spinner {
1115
+ width: 60px;
1116
+ height: 60px;
1117
+ border: 5px solid rgba(93, 64, 55, 0.2);
1118
+ border-top-color: var(--text-primary);
1119
+ border-radius: 50%;
1120
+ animation: spin 1s linear infinite;
1121
+ }
1122
+
1123
+ @keyframes spin {
1124
+ to {
1125
+ transform: rotate(360deg);
1126
+ }
1127
+ }
1128
+
1129
+ /* Image Collage Styles */
1130
+ .image-collage-container {
1131
+ position: absolute;
1132
+ top: 0;
1133
+ left: 0;
1134
+ width: 100%;
1135
+ height: 100%;
1136
+ z-index: 2; /* Sits above the slideshow but below UI elements */
1137
+ display: grid;
1138
+ grid-template-columns: repeat(10, 1fr);
1139
+ grid-template-rows: repeat(10, 1fr);
1140
+ gap: 8px;
1141
+ background-color: var(--bg-main);
1142
+ overflow: hidden;
1143
+ }
1144
+
1145
+ .collage-item {
1146
+ overflow: hidden;
1147
+ border-radius: 12px;
1148
+ box-shadow: 0 4px 15px var(--shadow-color);
1149
+ transition: transform 0.3s ease-in-out;
1150
+ animation: fade-in 0.5s ease-out forwards;
1151
+ opacity: 0;
1152
+ }
1153
+
1154
+ @keyframes fade-in {
1155
+ to { opacity: 1; }
1156
+ }
1157
+
1158
+
1159
+ .collage-item:hover {
1160
+ transform: scale(1.05) !important;
1161
+ z-index: 10;
1162
+ }
1163
+
1164
+ .collage-item img {
1165
+ width: 100%;
1166
+ height: 100%;
1167
+ object-fit: cover;
1168
+ }
1169
+
1170
+ /* Quiz Styles */
1171
+ .quiz-container {
1172
+ position: absolute;
1173
+ top: 50%;
1174
+ left: 50%;
1175
+ transform: translate(-50%, -50%);
1176
+ width: 90%;
1177
+ max-width: 700px;
1178
+ background: rgba(255, 255, 255, 0.85);
1179
+ backdrop-filter: blur(10px);
1180
+ border-radius: var(--card-border-radius);
1181
+ padding: 2rem;
1182
+ box-shadow: 0 8px 32px 0 var(--shadow-color);
1183
+ border: 1px solid rgba(255, 255, 255, 0.18);
1184
+ z-index: 10;
1185
+ }
1186
+
1187
+ .quiz-header {
1188
+ display: flex;
1189
+ justify-content: space-between;
1190
+ align-items: center;
1191
+ margin-bottom: 1.5rem;
1192
+ border-bottom: 2px solid var(--border-color);
1193
+ padding-bottom: 1rem;
1194
+ }
1195
+
1196
+ .quiz-header h2 {
1197
+ font-size: 2rem;
1198
+ color: var(--text-primary);
1199
+ margin: 0;
1200
+ }
1201
+
1202
+ .quiz-score {
1203
+ font-size: 1.25rem;
1204
+ font-weight: 700;
1205
+ color: var(--text-secondary);
1206
+ background: var(--bg-main);
1207
+ padding: 0.5rem 1rem;
1208
+ border-radius: 99px;
1209
+ }
1210
+
1211
+ .quiz-score span {
1212
+ color: var(--text-primary);
1213
+ }
1214
+
1215
+ .quiz-card {
1216
+ text-align: center;
1217
+ }
1218
+
1219
+ .quiz-question {
1220
+ font-size: 1.75rem;
1221
+ font-weight: 700;
1222
+ color: var(--text-primary);
1223
+ line-height: 1.4;
1224
+ margin-bottom: 2rem;
1225
+ }
1226
+
1227
+ .quiz-options {
1228
+ display: flex;
1229
+ flex-direction: column;
1230
+ gap: 1rem;
1231
+ }
1232
+
1233
+ .quiz-options li {
1234
+ background-color: var(--bg-secondary);
1235
+ border: 2px solid var(--border-color);
1236
+ border-radius: 12px;
1237
+ padding: 1rem;
1238
+ font-size: 1.25rem;
1239
+ color: var(--text-secondary);
1240
+ font-weight: 700;
1241
+ transition: all 0.2s ease-in-out;
1242
+ }
1243
+
1244
+ .quiz-option-button {
1245
+ background-color: var(--bg-secondary);
1246
+ border: 2px solid var(--border-color);
1247
+ border-radius: 12px;
1248
+ padding: 1rem;
1249
+ font-size: 1.25rem;
1250
+ color: var(--text-primary);
1251
+ font-weight: 700;
1252
+ transition: all 0.2s ease-in-out;
1253
+ width: 100%;
1254
+ text-align: left;
1255
+ justify-content: flex-start;
1256
+ }
1257
+
1258
+ .quiz-option-button:not(:disabled):hover {
1259
+ border-color: var(--button-primary-bg);
1260
+ color: var(--text-primary);
1261
+ transform: translateY(-2px);
1262
+ box-shadow: 0 4px 10px var(--shadow-color);
1263
+ }
1264
+
1265
+ .quiz-option-button:disabled {
1266
+ cursor: not-allowed;
1267
+ opacity: 0.7;
1268
+ }
1269
+
1270
+ .quiz-option-button.selected.incorrect {
1271
+ background-color: #ffebee; /* Light red */
1272
+ border-color: var(--accent-red);
1273
+ color: #c62828; /* Dark red */
1274
+ opacity: 1;
1275
+ }
1276
+
1277
+ .quiz-option-button.correct {
1278
+ background-color: #e8f5e9; /* Light green */
1279
+ border-color: var(--accent-green);
1280
+ color: #2e7d32; /* Dark green */
1281
+ opacity: 1;
1282
+ }
1283
+
1284
+ .quiz-option-button.correct::after {
1285
+ content: '✔';
1286
+ margin-left: auto;
1287
+ font-weight: bold;
1288
+ color: #2e7d32;
1289
+ }
1290
+ .quiz-option-button.selected.incorrect::after {
1291
+ content: '✖';
1292
+ margin-left: auto;
1293
+ font-weight: bold;
1294
+ color: #c62828;
1295
+ }
1296
+
1297
+ /* End Screen Styles */
1298
+ .end-screen-container {
1299
+ position: relative;
1300
+ z-index: 10;
1301
+ background: rgba(255, 255, 255, 0.85);
1302
+ backdrop-filter: blur(10px);
1303
+ border-radius: var(--card-border-radius);
1304
+ padding: 2.5rem;
1305
+ box-shadow: 0 8px 32px 0 var(--shadow-color);
1306
+ text-align: center;
1307
+ display: flex;
1308
+ flex-direction: column;
1309
+ gap: 1rem;
1310
+ max-width: 500px;
1311
+ }
1312
+
1313
+ .end-screen-container h2 {
1314
+ font-size: 2.5rem;
1315
+ color: var(--text-primary);
1316
+ margin: 0;
1317
+ }
1318
+
1319
+ .end-screen-container p {
1320
+ font-size: 1.25rem;
1321
+ color: var(--text-secondary);
1322
+ margin-bottom: 1rem;
1323
+ }
1324
+
1325
+ .end-screen-buttons {
1326
+ display: flex;
1327
+ flex-direction: column;
1328
+ gap: 1rem;
1329
+ justify-content: center;
1330
+ }
1331
+
1332
+ @media (min-width: 768px) {
1333
+ .end-screen-buttons {
1334
+ flex-direction: row;
1335
+ }
1336
+ }
1337
+
1338
+
1339
+ .end-screen-buttons .button {
1340
+ font-size: 1.1rem;
1341
+ padding: 0.8rem 1.5rem;
1342
+ }
index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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" />
6
+ <meta name="theme-color" content="#FFFBEF" />
7
+ <meta name="description" content="Fun learning chatbots for kids" />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap"
12
+ rel="stylesheet"
13
+ />
14
+ <link
15
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=block"
16
+ rel="stylesheet"
17
+ />
18
+ <link rel="stylesheet" href="index.css" />
19
+ <title>Learning Friends</title>
20
+ <script type="importmap">
21
+ {
22
+ "imports": {
23
+ "react/": "https://esm.sh/react@^19.1.0/",
24
+ "react": "https://esm.sh/react@^19.1.0",
25
+ "react-dom/": "https://esm.sh/react-dom@^19.1.0/",
26
+ "@google/genai": "https://esm.sh/@google/genai@^1.4.0",
27
+ "eventemitter3": "https://esm.sh/eventemitter3@^5.0.1",
28
+ "lodash": "https://esm.sh/lodash@^4.17.21",
29
+ "vite": "https://esm.sh/vite@^6.3.5",
30
+ "classnames": "https://esm.sh/classnames@^2.5.1",
31
+ "zustand": "https://esm.sh/zustand@^5.0.5",
32
+ "path": "https://esm.sh/path@^0.12.7"
33
+ }
34
+ }
35
+ </script>
36
+ <link rel="stylesheet" href="/index.css">
37
+ </head>
38
+
39
+ <body>
40
+ <noscript>You need to enable JavaScript to run this app.</noscript>
41
+ <div id="root"></div>
42
+ <script src="index.tsx" type="module"></script>
43
+ <script type="module" src="/index.tsx"></script>
44
+ </body>
45
+ </html>
index.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Copyright 2024 Google LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ import React from 'react';
22
+ import ReactDOM from 'react-dom/client';
23
+
24
+ import App from './App';
25
+
26
+ const root = ReactDOM.createRoot(
27
+ document.getElementById('root') as HTMLElement
28
+ );
29
+ root.render(
30
+ <React.StrictMode>
31
+ <App />
32
+ </React.StrictMode>
33
+ );
metadata.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Final Alif implementation",
3
+ "description": "An interactive tool that allows you to design, test, and banter with custom AI characters on the fly.",
4
+ "requestFramePermissions": [
5
+ "microphone"
6
+ ],
7
+ "prompt": ""
8
+ }
package.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "final-alif-implementation",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "run-p dev:frontend dev:backend",
8
+ "dev:frontend": "vite",
9
+ "dev:backend": "node server.cjs",
10
+ "build": "vite build",
11
+ "preview": "vite preview",
12
+ "start": "node server.cjs",
13
+ "migrate": "drizzle-kit generate",
14
+ "db:migrate": "tsx scripts/migrate.ts"
15
+ },
16
+ "dependencies": {
17
+ "@google/genai": "^1.4.0",
18
+ "@neondatabase/serverless": "^1.0.1",
19
+ "@types/pg": "^8.15.5",
20
+ "@vitejs/plugin-react": "^5.0.2",
21
+ "classnames": "^2.5.1",
22
+ "cors": "^2.8.5",
23
+ "dotenv": "^17.2.1",
24
+ "drizzle-kit": "^0.31.4",
25
+ "drizzle-orm": "^0.44.5",
26
+ "eventemitter3": "^5.0.1",
27
+ "express": "^5.1.0",
28
+ "firebase": "^12.2.1",
29
+ "lodash": "^4.17.21",
30
+ "npm-run-all": "^4.1.5",
31
+ "path": "^0.12.7",
32
+ "pg": "^8.16.3",
33
+ "react": "^19.1.0",
34
+ "react-dom": "^19.1.0",
35
+ "tsx": "^4.20.5",
36
+ "vite": "^6.3.5",
37
+ "zustand": "^5.0.5"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.14.0",
41
+ "typescript": "~5.8.2",
42
+ "vite": "^6.2.0"
43
+ }
44
+ }
server.cjs ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Load environment variables first
2
+ require('dotenv').config({ path: '.env' });
3
+
4
+ const express = require('express');
5
+ const cors = require('cors');
6
+ const path = require('path');
7
+
8
+ const app = express();
9
+ const PORT = process.env.PORT || 3001;
10
+
11
+ // Serve static files from the React app
12
+ app.use(express.static(path.join(__dirname, 'dist')));
13
+
14
+ // Middleware
15
+ app.use(cors());
16
+ app.use(express.json());
17
+ app.use((req, res, next) => {
18
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin-allow-popups');
19
+ res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
20
+ next();
21
+ });
22
+
23
+ // API routes
24
+ app.use('/api/init', require('./api/init.cjs'));
25
+ app.use('/api/users', require('./api/users.cjs'));
26
+ app.use('/api/sessions', require('./api/sessions.cjs'));
27
+
28
+ // Health check endpoint
29
+ app.get('/health', (req, res) => {
30
+ res.json({ status: 'OK', timestamp: new Date().toISOString() });
31
+ });
32
+
33
+ // Start server
34
+ app.listen(PORT, () => {
35
+ console.log(`Server running on port ${PORT}`);
36
+ console.log(`Health check: http://localhost:${PORT}/health`);
37
+ });
tsconfig.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "types": [
14
+ "node"
15
+ ],
16
+ "moduleResolution": "bundler",
17
+ "isolatedModules": true,
18
+ "moduleDetection": "force",
19
+ "allowJs": true,
20
+ "jsx": "react-jsx",
21
+ "paths": {
22
+ "@/*": [
23
+ "./*"
24
+ ]
25
+ },
26
+ "allowImportingTsExtensions": true,
27
+ "noEmit": true
28
+ }
29
+ }
vercel.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"rewrites":[{"source":"/(.*)","destination":"/index.html"}]}
vite.config.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import { defineConfig, loadEnv } from 'vite';
3
+ import react from '@vitejs/plugin-react';
4
+
5
+ export default defineConfig(({ mode }) => {
6
+ const env = loadEnv(mode, '.', '');
7
+ return {
8
+ plugins: [react()],
9
+ define: {
10
+ 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
11
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
12
+ },
13
+ server: {
14
+ proxy: {
15
+ '/api': {
16
+ target: 'http://localhost:3001',
17
+ changeOrigin: true,
18
+ secure: false
19
+ }
20
+ }
21
+ },
22
+ resolve: {
23
+ alias: {
24
+ '@': path.resolve(__dirname, '.'),
25
+ }
26
+ }
27
+ };
28
+ });