This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .env.example +0 -4
  2. .github/workflows/deploy-prod.yml +0 -77
  3. .gitignore +1 -7
  4. Dockerfile +6 -9
  5. MCP-SERVER.md +428 -0
  6. README.md +12 -7
  7. actions/mentions.ts +0 -31
  8. actions/projects.ts +0 -175
  9. app/(public)/layout.tsx +0 -14
  10. app/(public)/page.tsx +0 -25
  11. app/(public)/signin/page.tsx +0 -21
  12. app/[owner]/[repoId]/page.tsx +0 -35
  13. app/api/ask/route.ts +0 -183
  14. app/api/auth/[...nextauth]/route.ts +0 -6
  15. app/api/healthcheck/route.ts +0 -5
  16. app/api/projects/[repoId]/[commitId]/route.ts +0 -49
  17. app/api/projects/[repoId]/download/route.ts +0 -76
  18. app/api/projects/[repoId]/medias/route.ts +0 -87
  19. app/api/projects/[repoId]/rename/route.ts +0 -92
  20. app/api/projects/[repoId]/route.ts +0 -104
  21. app/api/projects/route.ts +0 -145
  22. app/api/redesign/route.ts +0 -73
  23. app/favicon.ico +0 -0
  24. app/layout.tsx +0 -108
  25. app/new/page.tsx +0 -18
  26. app/not-found.tsx +0 -17
  27. {app → assets}/globals.css +244 -32
  28. assets/hf-logo.svg +0 -7
  29. assets/logo.svg +316 -0
  30. assets/pro.svg +0 -10
  31. chart/Chart.yaml +0 -5
  32. chart/env/prod.yaml +0 -59
  33. chart/templates/_helpers.tpl +0 -22
  34. chart/templates/config.yaml +0 -10
  35. chart/templates/deployment.yaml +0 -81
  36. chart/templates/hpa.yaml +0 -45
  37. chart/templates/infisical.yaml +0 -24
  38. chart/templates/ingress-internal.yaml +0 -32
  39. chart/templates/ingress.yaml +0 -33
  40. chart/templates/network-policy.yaml +0 -36
  41. chart/templates/service-account.yaml +0 -13
  42. chart/templates/service-monitor.yaml +0 -17
  43. chart/templates/service.yaml +0 -21
  44. chart/values.yaml +0 -81
  45. components.json +4 -5
  46. components/animated-blobs/index.tsx +34 -0
  47. components/animated-text/index.tsx +123 -0
  48. components/ask-ai/ask-ai-landing.tsx +0 -75
  49. components/ask-ai/ask-ai.tsx +0 -215
  50. components/ask-ai/context.tsx +0 -123
.env.example DELETED
@@ -1,4 +0,0 @@
1
- AUTH_HUGGINGFACE_ID=
2
- AUTH_HUGGINGFACE_SECRET=
3
- NEXTAUTH_URL=http://localhost:3001
4
- AUTH_SECRET=
 
 
 
 
 
.github/workflows/deploy-prod.yml DELETED
@@ -1,77 +0,0 @@
1
- name: Deploy to k8s
2
- on:
3
- # run this workflow manually from the Actions tab
4
- workflow_dispatch:
5
-
6
- jobs:
7
- build-and-publish:
8
- runs-on:
9
- group: cpu-high
10
- steps:
11
- - name: Checkout
12
- uses: actions/checkout@v4
13
-
14
- - name: Login to Registry
15
- uses: docker/login-action@v3
16
- with:
17
- registry: registry.internal.huggingface.tech
18
- username: ${{ secrets.DOCKER_INTERNAL_USERNAME }}
19
- password: ${{ secrets.DOCKER_INTERNAL_PASSWORD }}
20
-
21
- - name: Docker metadata
22
- id: meta
23
- uses: docker/metadata-action@v5
24
- with:
25
- images: |
26
- registry.internal.huggingface.tech/deepsite/deepsite
27
- tags: |
28
- type=raw,value=latest,enable={{is_default_branch}}
29
- type=sha,enable=true,prefix=sha-,format=short,sha-len=8
30
-
31
- - name: Set up Docker Buildx
32
- uses: docker/setup-buildx-action@v3
33
-
34
- - name: Inject slug/short variables
35
- uses: rlespinasse/github-slug-action@v4
36
-
37
- - name: Build and Publish image
38
- uses: docker/build-push-action@v5
39
- with:
40
- context: .
41
- file: Dockerfile
42
- push: ${{ github.event_name != 'pull_request' }}
43
- tags: ${{ steps.meta.outputs.tags }}
44
- labels: ${{ steps.meta.outputs.labels }}
45
- platforms: linux/amd64
46
- cache-to: type=gha,mode=max,scope=amd64
47
- cache-from: type=gha,scope=amd64
48
- provenance: false
49
-
50
- deploy:
51
- name: Deploy on prod
52
- runs-on: ubuntu-latest
53
- needs: ["build-and-publish"]
54
- steps:
55
- - name: Inject slug/short variables
56
- uses: rlespinasse/github-slug-action@v4
57
-
58
- - name: Gen values
59
- run: |
60
- VALUES=$(cat <<-END
61
- image:
62
- tag: "sha-${{ env.GITHUB_SHA_SHORT }}"
63
- END
64
- )
65
- echo "VALUES=$(echo "$VALUES" | yq -o=json | jq tostring)" >> $GITHUB_ENV
66
-
67
- - name: Deploy on infra-deployments
68
- uses: the-actions-org/workflow-dispatch@v2
69
- with:
70
- workflow: Update application single value
71
- repo: huggingface/infra-deployments
72
- wait-for-completion: true
73
- wait-for-completion-interval: 10s
74
- display-workflow-run-url-interval: 10s
75
- ref: refs/heads/main
76
- token: ${{ secrets.GIT_TOKEN_INFRA_DEPLOYMENT }}
77
- inputs: '{"path": "hub/deepsite/deepsite.yaml", "value": ${{ env.VALUES }}, "url": "${{ github.event.head_commit.url }}"}'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -31,7 +31,7 @@ yarn-error.log*
31
  .pnpm-debug.log*
32
 
33
  # env files (can opt-in for committing if needed)
34
- .env
35
 
36
  # vercel
37
  .vercel
@@ -39,9 +39,3 @@ yarn-error.log*
39
  # typescript
40
  *.tsbuildinfo
41
  next-env.d.ts
42
-
43
- .idea
44
-
45
- # binary assets (hosted on CDN)
46
- assets/assistant.jpg
47
- .gitattributes
 
31
  .pnpm-debug.log*
32
 
33
  # env files (can opt-in for committing if needed)
34
+ .env*
35
 
36
  # vercel
37
  .vercel
 
39
  # typescript
40
  *.tsbuildinfo
41
  next-env.d.ts
 
 
 
 
 
 
Dockerfile CHANGED
@@ -1,22 +1,19 @@
1
  FROM node:20-alpine
2
  USER root
3
 
4
- # Install pnpm
5
- RUN corepack enable && corepack prepare pnpm@latest --activate
6
-
7
  USER 1000
8
  WORKDIR /usr/src/app
9
- # Copy package.json and pnpm-lock.yaml to the container
10
- COPY --chown=1000 package.json pnpm-lock.yaml ./
11
 
12
  # Copy the rest of the application files to the container
13
  COPY --chown=1000 . .
14
 
15
- RUN pnpm install
16
- RUN pnpm run build
17
 
18
  # Expose the application port (assuming your app runs on port 3000)
19
- EXPOSE 3001
20
 
21
  # Start the application
22
- CMD ["pnpm", "start"]
 
1
  FROM node:20-alpine
2
  USER root
3
 
 
 
 
4
  USER 1000
5
  WORKDIR /usr/src/app
6
+ # Copy package.json and package-lock.json to the container
7
+ COPY --chown=1000 package.json package-lock.json ./
8
 
9
  # Copy the rest of the application files to the container
10
  COPY --chown=1000 . .
11
 
12
+ RUN npm install
13
+ RUN npm run build
14
 
15
  # Expose the application port (assuming your app runs on port 3000)
16
+ EXPOSE 3000
17
 
18
  # Start the application
19
+ CMD ["npm", "start"]
MCP-SERVER.md ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DeepSite MCP Server
2
+
3
+ DeepSite is now available as an MCP (Model Context Protocol) server, enabling AI assistants like Claude to create websites directly using natural language.
4
+
5
+ ## Two Ways to Use DeepSite MCP
6
+
7
+ **Quick Comparison:**
8
+
9
+ | Feature | Option 1: HTTP Server | Option 2: Local Server |
10
+ |---------|----------------------|------------------------|
11
+ | **Setup Difficulty** | ✅ Easy (just config) | ⚠️ Requires installation |
12
+ | **Authentication** | HF Token in config header | HF Token or session cookie in env |
13
+ | **Best For** | Most users | Developers, custom modifications |
14
+ | **Maintenance** | ✅ Always up-to-date | Need to rebuild for updates |
15
+
16
+ **Recommendation:** Use Option 1 (HTTP Server) unless you need to modify the MCP server code.
17
+
18
+ ---
19
+
20
+ ### 🌐 Option 1: HTTP Server (Recommended)
21
+
22
+ **No installation required!** Use DeepSite's hosted MCP server.
23
+
24
+ #### Setup for Claude Desktop
25
+
26
+ Add to your Claude Desktop configuration file:
27
+
28
+ **MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
29
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "deepsite": {
35
+ "url": "https://huggingface.co/deepsite/api/mcp",
36
+ "transport": {
37
+ "type": "sse"
38
+ },
39
+ "headers": {
40
+ "Authorization": "Bearer hf_your_token_here"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Getting Your Hugging Face Token:**
48
+
49
+ 1. Go to https://huggingface.co/settings/tokens
50
+ 2. Create a new token with `write` access
51
+ 3. Copy the token
52
+ 4. Add it to the `Authorization` header in your config (recommended for security)
53
+ 5. Alternatively, you can pass it as the `hf_token` parameter when using the tool
54
+
55
+ **⚠️ Security Recommendation:** Use the `Authorization` header in your config instead of passing the token in chat. This keeps your token secure and out of conversation history.
56
+
57
+ #### Example Usage with Claude
58
+
59
+ > "Create a portfolio website using DeepSite. Include a hero section, about section, and contact form."
60
+
61
+ Claude will automatically:
62
+ 1. Use the `create_project` tool
63
+ 2. Authenticate using the token from your config
64
+ 3. Create the website on Hugging Face Spaces
65
+ 4. Return the URLs to access your new site
66
+
67
+ ---
68
+
69
+ ### 💻 Option 2: Local Server
70
+
71
+ Run the MCP server locally for more control or offline use.
72
+
73
+ > **Note:** Most users should use Option 1 (HTTP Server) instead. Option 2 is only needed if you want to run the MCP server locally or modify its behavior.
74
+
75
+ #### Installation
76
+
77
+ ```bash
78
+ cd mcp-server
79
+ npm install
80
+ npm run build
81
+ ```
82
+
83
+ #### Setup for Claude Desktop
84
+
85
+ **Method A: Using HF Token (Recommended)**
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "deepsite-local": {
91
+ "command": "node",
92
+ "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
93
+ "env": {
94
+ "HF_TOKEN": "hf_your_token_here",
95
+ "DEEPSITE_API_URL": "https://huggingface.co/deepsite"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Method B: Using Session Cookie (Alternative)**
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "deepsite-local": {
108
+ "command": "node",
109
+ "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
110
+ "env": {
111
+ "DEEPSITE_AUTH_COOKIE": "your-session-cookie",
112
+ "DEEPSITE_API_URL": "https://huggingface.co/deepsite"
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Getting Your Session Cookie (Method B only):**
120
+
121
+ 1. Log in to https://huggingface.co/deepsite
122
+ 2. Open Developer Tools (F12)
123
+ 3. Go to Application → Cookies
124
+ 4. Copy the session cookie value
125
+ 5. Set as `DEEPSITE_AUTH_COOKIE` in the config
126
+
127
+ ---
128
+
129
+ ## Available Tools
130
+
131
+ ### `create_project`
132
+
133
+ Creates a new DeepSite project with HTML/CSS/JS files.
134
+
135
+ **Parameters:**
136
+
137
+ | Parameter | Type | Required | Description |
138
+ |-----------|------|----------|-------------|
139
+ | `title` | string | No | Project title (defaults to "DeepSite Project") |
140
+ | `pages` | array | Yes | Array of file objects with `path` and `html` |
141
+ | `prompt` | string | No | Commit message/description |
142
+ | `hf_token` | string | No* | Hugging Face API token (*optional if provided via Authorization header in config) |
143
+
144
+ **Page Object:**
145
+ ```typescript
146
+ {
147
+ path: string; // e.g., "index.html", "styles.css", "script.js"
148
+ html: string; // File content
149
+ }
150
+ ```
151
+
152
+ **Returns:**
153
+ ```json
154
+ {
155
+ "success": true,
156
+ "message": "Project created successfully!",
157
+ "projectUrl": "https://huggingface.co/deepsite/username/project-name",
158
+ "spaceUrl": "https://huggingface.co/spaces/username/project-name",
159
+ "liveUrl": "https://username-project-name.hf.space",
160
+ "spaceId": "username/project-name",
161
+ "projectId": "space-id",
162
+ "files": ["index.html", "styles.css"]
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Example Prompts for Claude
169
+
170
+ ### Simple Landing Page
171
+ > "Create a modern landing page for my SaaS product using DeepSite. Include a hero section with CTA, features grid, and footer. Use gradient background."
172
+
173
+ ### Portfolio Website
174
+ > "Build a portfolio website with DeepSite. I need:
175
+ > - Hero section with my name and photo
176
+ > - Projects gallery with 3 sample projects
177
+ > - Skills section with tech stack
178
+ > - Contact form
179
+ > Use dark mode with accent colors."
180
+
181
+ ### Blog Homepage
182
+ > "Create a blog homepage using DeepSite. Include:
183
+ > - Header with navigation
184
+ > - Featured post section
185
+ > - Grid of recent posts (3 cards)
186
+ > - Sidebar with categories
187
+ > - Footer with social links
188
+ > Clean, minimal design."
189
+
190
+ ### Interactive Dashboard
191
+ > "Make an analytics dashboard with DeepSite:
192
+ > - Sidebar navigation
193
+ > - 4 metric cards at top
194
+ > - 2 chart placeholders
195
+ > - Data table
196
+ > - Modern, professional UI with charts.css"
197
+
198
+ ---
199
+
200
+ ## Direct API Usage
201
+
202
+ You can also call the HTTP endpoint directly:
203
+
204
+ ### Using Authorization Header (Recommended)
205
+
206
+ ```bash
207
+ curl -X POST https://huggingface.co/deepsite/api/mcp \
208
+ -H "Content-Type: application/json" \
209
+ -H "Authorization: Bearer hf_your_token_here" \
210
+ -d '{
211
+ "jsonrpc": "2.0",
212
+ "id": 1,
213
+ "method": "tools/call",
214
+ "params": {
215
+ "name": "create_project",
216
+ "arguments": {
217
+ "title": "My Website",
218
+ "pages": [
219
+ {
220
+ "path": "index.html",
221
+ "html": "<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello World!</h1></body></html>"
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ }'
227
+ ```
228
+
229
+ ### Using Token Parameter (Fallback)
230
+
231
+ ```bash
232
+ curl -X POST https://huggingface.co/deepsite/api/mcp \
233
+ -H "Content-Type: application/json" \
234
+ -d '{
235
+ "jsonrpc": "2.0",
236
+ "id": 1,
237
+ "method": "tools/call",
238
+ "params": {
239
+ "name": "create_project",
240
+ "arguments": {
241
+ "title": "My Website",
242
+ "pages": [
243
+ {
244
+ "path": "index.html",
245
+ "html": "<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello World!</h1></body></html>"
246
+ }
247
+ ],
248
+ "hf_token": "hf_xxxxx"
249
+ }
250
+ }
251
+ }'
252
+ ```
253
+
254
+ ### List Available Tools
255
+
256
+ ```bash
257
+ curl -X POST https://huggingface.co/deepsite/api/mcp \
258
+ -H "Content-Type: application/json" \
259
+ -d '{
260
+ "jsonrpc": "2.0",
261
+ "id": 1,
262
+ "method": "tools/list",
263
+ "params": {}
264
+ }'
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Testing
270
+
271
+ ### Test Local Server
272
+
273
+ ```bash
274
+ cd mcp-server
275
+ ./test.sh
276
+ ```
277
+
278
+ ### Test HTTP Server
279
+
280
+ ```bash
281
+ curl -X POST https://huggingface.co/deepsite/api/mcp \
282
+ -H "Content-Type: application/json" \
283
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Migration Guide: From Parameter to Header Auth
289
+
290
+ If you're currently passing the token as a parameter in your prompts, here's how to migrate to the more secure header-based authentication:
291
+
292
+ ### Step 1: Update Your Config
293
+
294
+ Edit your Claude Desktop config file and add the `headers` section:
295
+
296
+ ```json
297
+ {
298
+ "mcpServers": {
299
+ "deepsite": {
300
+ "url": "https://huggingface.co/deepsite/api/mcp",
301
+ "transport": {
302
+ "type": "sse"
303
+ },
304
+ "headers": {
305
+ "Authorization": "Bearer hf_your_actual_token_here"
306
+ }
307
+ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ ### Step 2: Restart Claude Desktop
313
+
314
+ Completely quit and restart Claude Desktop for the changes to take effect.
315
+
316
+ ### Step 3: Use Simpler Prompts
317
+
318
+ Now you can simply say:
319
+ > "Create a portfolio website with DeepSite"
320
+
321
+ Instead of:
322
+ > "Create a portfolio website with DeepSite using token `hf_xxxxx`"
323
+
324
+ Your token is automatically included in all requests via the header!
325
+
326
+ ---
327
+
328
+ ## Security Notes
329
+
330
+ ### HTTP Server (Option 1)
331
+ - **✅ Recommended:** Store your HF token in the `Authorization` header in your Claude Desktop config
332
+ - The token is stored locally on your machine and never exposed in chat
333
+ - The token is sent with each request but only used to authenticate with Hugging Face API
334
+ - DeepSite does not store your token
335
+ - Use tokens with minimal required permissions (write access to spaces)
336
+ - You can revoke tokens anytime at https://huggingface.co/settings/tokens
337
+ - **⚠️ Fallback:** You can still pass the token as a parameter, but this is less secure as it appears in conversation history
338
+
339
+ ### Local Server (Option 2)
340
+ - Use `HF_TOKEN` environment variable (same security as Option 1)
341
+ - Or use `DEEPSITE_AUTH_COOKIE` if you prefer session-based auth
342
+ - All authentication data stays on your local machine
343
+ - Better for development and testing
344
+ - No need for both HTTP Server and Local Server - choose one!
345
+
346
+ ---
347
+
348
+ ## Troubleshooting
349
+
350
+ ### "Invalid Hugging Face token"
351
+ - Verify your token at https://huggingface.co/settings/tokens
352
+ - Ensure the token has write permissions
353
+ - Check that you copied the full token (starts with `hf_`)
354
+
355
+ ### "At least one page is required"
356
+ - Make sure you're providing the `pages` array
357
+ - Each page must have both `path` and `html` properties
358
+
359
+ ### "Failed to create project"
360
+ - Check your token permissions
361
+ - Ensure the project title doesn't conflict with existing spaces
362
+ - Verify your Hugging Face account is in good standing
363
+
364
+ ### Claude doesn't see the tool
365
+ - Restart Claude Desktop after modifying the config
366
+ - Check that the JSON config is valid (no trailing commas)
367
+ - For HTTP: verify the URL is correct
368
+ - For local: check the absolute path to index.js
369
+
370
+ ---
371
+
372
+ ## Architecture
373
+
374
+ ### HTTP Server Flow
375
+ ```
376
+ Claude Desktop
377
+
378
+ (HTTP Request)
379
+
380
+ huggingface.co/deepsite/api/mcp
381
+
382
+ Hugging Face API (with user's token)
383
+
384
+ New Space Created
385
+
386
+ URLs returned to Claude
387
+ ```
388
+
389
+ ### Local Server Flow
390
+ ```
391
+ Claude Desktop
392
+
393
+ (stdio transport)
394
+
395
+ Local MCP Server
396
+
397
+ (HTTP to DeepSite API)
398
+
399
+ huggingface.co/deepsite/api/me/projects
400
+
401
+ New Space Created
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Contributing
407
+
408
+ The MCP server implementation lives in:
409
+ - HTTP Server: `/app/api/mcp/route.ts`
410
+ - Local Server: `/mcp-server/index.ts`
411
+
412
+ Both use the same core DeepSite logic for creating projects - no duplication!
413
+
414
+ ---
415
+
416
+ ## License
417
+
418
+ MIT
419
+
420
+ ---
421
+
422
+ ## Resources
423
+
424
+ - [Model Context Protocol Spec](https://modelcontextprotocol.io/)
425
+ - [DeepSite Documentation](https://huggingface.co/deepsite)
426
+ - [Hugging Face Spaces](https://huggingface.co/docs/hub/spaces)
427
+ - [Claude Desktop](https://claude.ai/desktop)
428
+
README.md CHANGED
@@ -1,21 +1,26 @@
1
  ---
2
- title: DeepSite v4
3
  emoji: 🐳
4
  colorFrom: blue
5
  colorTo: blue
6
  sdk: docker
7
  pinned: true
8
- app_port: 3001
9
  license: mit
10
  failure_strategy: rollback
11
- short_description: Generate any application by Vibe Coding it
12
  models:
13
  - deepseek-ai/DeepSeek-V3-0324
14
- - deepseek-ai/DeepSeek-V3.2
15
- - Qwen/Qwen3-Coder-30B-A3B-Instruct
 
 
 
 
16
  - moonshotai/Kimi-K2-Instruct-0905
17
- - zai-org/GLM-4.7
18
- - MiniMaxAI/MiniMax-M2.1
 
19
  ---
20
 
21
  # DeepSite 🐳
 
1
  ---
2
+ title: DeepSite v3
3
  emoji: 🐳
4
  colorFrom: blue
5
  colorTo: blue
6
  sdk: docker
7
  pinned: true
8
+ app_port: 3000
9
  license: mit
10
  failure_strategy: rollback
11
+ short_description: Generate any application by Vibe Coding
12
  models:
13
  - deepseek-ai/DeepSeek-V3-0324
14
+ - deepseek-ai/DeepSeek-R1-0528
15
+ - deepseek-ai/DeepSeek-V3.1
16
+ - deepseek-ai/DeepSeek-V3.1-Terminus
17
+ - deepseek-ai/DeepSeek-V3.2-Exp
18
+ - Qwen/Qwen3-Coder-480B-A35B-Instruct
19
+ - moonshotai/Kimi-K2-Instruct
20
  - moonshotai/Kimi-K2-Instruct-0905
21
+ - zai-org/GLM-4.6
22
+ - MiniMaxAI/MiniMax-M2
23
+ - moonshotai/Kimi-K2-Thinking
24
  ---
25
 
26
  # DeepSite 🐳
actions/mentions.ts DELETED
@@ -1,31 +0,0 @@
1
- "use client";
2
-
3
- import { File } from "@/lib/type";
4
-
5
- export const searchMentions = async (query: string) => {
6
- const promises = [searchModels(query), searchDatasets(query)];
7
- const results = await Promise.all(promises);
8
- return { models: results[0], datasets: results[1] };
9
- };
10
-
11
- const searchModels = async (query: string) => {
12
- const response = await fetch(
13
- `https://huggingface.co/api/quicksearch?q=${query}&type=model&limit=3`
14
- );
15
- const data = await response.json();
16
- return data?.models ?? [];
17
- };
18
-
19
- const searchDatasets = async (query: string) => {
20
- const response = await fetch(
21
- `https://huggingface.co/api/quicksearch?q=${query}&type=dataset&limit=3`
22
- );
23
- const data = await response.json();
24
- return data?.datasets ?? [];
25
- };
26
-
27
- export const searchFilesMentions = async (query: string, files: File[]) => {
28
- if (!query) return files;
29
- const lowerQuery = query.toLowerCase();
30
- return files.filter((file) => file.path.toLowerCase().includes(lowerQuery));
31
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
actions/projects.ts DELETED
@@ -1,175 +0,0 @@
1
- "use server";
2
- import {
3
- downloadFile,
4
- listCommits,
5
- listFiles,
6
- listSpaces,
7
- RepoDesignation,
8
- SpaceEntry,
9
- spaceInfo,
10
- } from "@huggingface/hub";
11
-
12
- import { auth } from "@/lib/auth";
13
- import { Commit, File } from "@/lib/type";
14
-
15
- export interface ProjectWithCommits extends SpaceEntry {
16
- commits?: Commit[];
17
- medias?: string[];
18
- }
19
-
20
- const IGNORED_PATHS = ["README.md", ".gitignore", ".gitattributes"];
21
- const IGNORED_FORMATS = [
22
- ".png",
23
- ".jpg",
24
- ".jpeg",
25
- ".gif",
26
- ".svg",
27
- ".webp",
28
- ".mp4",
29
- ".mp3",
30
- ".wav",
31
- ];
32
-
33
- export const getProjects = async () => {
34
- const projects: SpaceEntry[] = [];
35
- const session = await auth();
36
- if (!session?.user) {
37
- return projects;
38
- }
39
- const token = session.accessToken;
40
- for await (const space of listSpaces({
41
- accessToken: token,
42
- additionalFields: ["author", "cardData"],
43
- search: {
44
- owner: session.user.username,
45
- },
46
- })) {
47
- if (
48
- space.sdk === "static" &&
49
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
50
- (space.cardData as { tags?: string[] })?.tags?.some((tag) =>
51
- tag.includes("deepsite")
52
- )
53
- ) {
54
- projects.push(space);
55
- }
56
- }
57
- return projects;
58
- };
59
- export const getProject = async (id: string, commitId?: string) => {
60
- const session = await auth();
61
- if (!session?.user) {
62
- return null;
63
- }
64
- const token = session.accessToken;
65
- try {
66
- const project: ProjectWithCommits | null = await spaceInfo({
67
- name: id,
68
- accessToken: token,
69
- additionalFields: ["author", "cardData"],
70
- });
71
- const repo: RepoDesignation = {
72
- type: "space",
73
- name: id,
74
- };
75
- const files: File[] = [];
76
- const medias: string[] = [];
77
- const params = { repo, accessToken: token };
78
- if (commitId) {
79
- Object.assign(params, { revision: commitId });
80
- }
81
- for await (const fileInfo of listFiles(params)) {
82
- if (IGNORED_PATHS.includes(fileInfo.path)) continue;
83
- if (IGNORED_FORMATS.some((format) => fileInfo.path.endsWith(format))) {
84
- medias.push(
85
- `https://huggingface.co/spaces/${id}/resolve/main/${fileInfo.path}`
86
- );
87
- continue;
88
- }
89
-
90
- if (fileInfo.type === "directory") {
91
- for await (const subFile of listFiles({
92
- repo,
93
- accessToken: token,
94
- path: fileInfo.path,
95
- })) {
96
- if (IGNORED_FORMATS.some((format) => subFile.path.endsWith(format))) {
97
- medias.push(
98
- `https://huggingface.co/spaces/${id}/resolve/main/${subFile.path}`
99
- );
100
- }
101
- const blob = await downloadFile({
102
- repo,
103
- accessToken: token,
104
- path: subFile.path,
105
- raw: true,
106
- ...(commitId ? { revision: commitId } : {}),
107
- }).catch((_) => {
108
- return null;
109
- });
110
- if (!blob) {
111
- continue;
112
- }
113
- const html = await blob?.text();
114
- if (!html) {
115
- continue;
116
- }
117
- files[subFile.path === "index.html" ? "unshift" : "push"]({
118
- path: subFile.path,
119
- content: html,
120
- });
121
- }
122
- } else {
123
- const blob = await downloadFile({
124
- repo,
125
- accessToken: token,
126
- path: fileInfo.path,
127
- raw: true,
128
- ...(commitId ? { revision: commitId } : {}),
129
- }).catch((_) => {
130
- return null;
131
- });
132
- if (!blob) {
133
- continue;
134
- }
135
- const html = await blob?.text();
136
- if (!html) {
137
- continue;
138
- }
139
- files[fileInfo.path === "index.html" ? "unshift" : "push"]({
140
- path: fileInfo.path,
141
- content: html,
142
- });
143
- }
144
- }
145
- const commits: Commit[] = [];
146
- const commitIterator = listCommits({ repo, accessToken: token });
147
- for await (const commit of commitIterator) {
148
- if (
149
- commit.title?.toLowerCase() === "initial commit" ||
150
- commit.title
151
- ?.toLowerCase()
152
- ?.includes("upload media files through deepsite")
153
- )
154
- continue;
155
- commits.push({
156
- title: commit.title,
157
- oid: commit.oid,
158
- date: commit.date,
159
- });
160
- if (commits.length >= 20) {
161
- break;
162
- }
163
- }
164
-
165
- project.commits = commits;
166
- project.medias = medias;
167
-
168
- return { project, files };
169
- } catch (error) {
170
- return {
171
- project: null,
172
- files: [],
173
- };
174
- }
175
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/layout.tsx DELETED
@@ -1,14 +0,0 @@
1
- import { Navigation } from "@/components/public/navigation";
2
-
3
- export default function PublicLayout({
4
- children,
5
- }: Readonly<{
6
- children: React.ReactNode;
7
- }>) {
8
- return (
9
- <div className="min-h-screen font-sans">
10
- <Navigation />
11
- {children}
12
- </div>
13
- );
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/page.tsx DELETED
@@ -1,25 +0,0 @@
1
- import { AnimatedDotsBackground } from "@/components/public/animated-dots-background";
2
- import { HeroHeader } from "@/components/public/hero-header";
3
- import { UserProjects } from "@/components/projects/user-projects";
4
- import { AskAiLanding } from "@/components/ask-ai/ask-ai-landing";
5
- import { Bento } from "@/components/public/bento";
6
-
7
- export const dynamic = "force-dynamic";
8
-
9
- export default async function Homepage() {
10
- return (
11
- <>
12
- <section className="container mx-auto relative z-10">
13
- <HeroHeader />
14
- <div className="absolute inset-0 -z-10">
15
- <AnimatedDotsBackground />
16
- </div>
17
- <div className="max-w-xl mx-auto px-6">
18
- <AskAiLanding />
19
- </div>
20
- </section>
21
- <UserProjects />
22
- <Bento />
23
- </>
24
- );
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/signin/page.tsx DELETED
@@ -1,21 +0,0 @@
1
- import { LoginButtons } from "@/components/login/login-buttons";
2
-
3
- export default async function SignInPage({
4
- searchParams,
5
- }: {
6
- searchParams: Promise<{ callbackUrl: string }>;
7
- }) {
8
- const { callbackUrl } = await searchParams;
9
- console.log(callbackUrl);
10
- return (
11
- <section className="min-h-screen font-sans">
12
- <div className="px-6 py-16 max-w-5xl mx-auto text-center">
13
- <h1 className="text-5xl font-bold mb-5">You shall not pass 🧙</h1>
14
- <p className="text-lg text-muted-foreground mb-8">
15
- You can&apos;t access this resource without being signed in.
16
- </p>
17
- <LoginButtons callbackUrl={callbackUrl ?? "/"} />
18
- </div>
19
- </section>
20
- );
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/[owner]/[repoId]/page.tsx DELETED
@@ -1,35 +0,0 @@
1
- import { getProject } from "@/actions/projects";
2
- import { AppEditor } from "@/components/editor";
3
- import { auth } from "@/lib/auth";
4
- import { notFound, redirect } from "next/navigation";
5
-
6
- export default async function ProjectPage({
7
- params,
8
- searchParams,
9
- }: {
10
- params: Promise<{ owner: string; repoId: string }>;
11
- searchParams: Promise<{ commit?: string }>;
12
- }) {
13
- const session = await auth();
14
-
15
- const { owner, repoId } = await params;
16
- const { commit } = await searchParams;
17
- if (!session) {
18
- redirect(
19
- `/api/auth/signin?callbackUrl=/${owner}/${repoId}${
20
- commit ? `?commit=${commit}` : ""
21
- }`
22
- );
23
- }
24
- const datas = await getProject(`${owner}/${repoId}`, commit);
25
- if (!datas?.project) {
26
- return notFound();
27
- }
28
- return (
29
- <AppEditor
30
- project={datas.project}
31
- files={datas.files ?? []}
32
- isHistoryView={!!commit}
33
- />
34
- );
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/ask/route.ts DELETED
@@ -1,183 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { InferenceClient } from "@huggingface/inference";
3
-
4
- import { FOLLOW_UP_SYSTEM_PROMPT, INITIAL_SYSTEM_PROMPT } from "@/lib/prompts";
5
- import { auth } from "@/lib/auth";
6
- import { File, Message } from "@/lib/type";
7
- import { DEFAULT_MODEL, MODELS } from "@/lib/providers";
8
-
9
- export async function POST(request: Request) {
10
- const session = await auth();
11
- if (!session) {
12
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13
- }
14
- const token = session.accessToken;
15
-
16
- const body = await request.json();
17
- const {
18
- prompt,
19
- previousMessages = [],
20
- files = [],
21
- provider,
22
- model,
23
- redesignMd,
24
- medias,
25
- } = body;
26
-
27
- if (!prompt) {
28
- return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
29
- }
30
- if (!model || !MODELS.find((m: (typeof MODELS)[0]) => m.value === model)) {
31
- return NextResponse.json({ error: "Model is required" }, { status: 400 });
32
- }
33
-
34
- const client = new InferenceClient(token);
35
-
36
- try {
37
- const encoder = new TextEncoder();
38
- const stream = new TransformStream();
39
- const writer = stream.writable.getWriter();
40
-
41
- const response = new NextResponse(stream.readable, {
42
- headers: {
43
- "Content-Type": "text/plain; charset=utf-8",
44
- "Cache-Control": "no-cache",
45
- Connection: "keep-alive",
46
- },
47
- });
48
- (async () => {
49
- let hasRetried = false;
50
- let currentModel = model;
51
-
52
- const tryGeneration = async (): Promise<void> => {
53
- try {
54
- const chatCompletion = client.chatCompletionStream({
55
- model: currentModel + (provider !== "auto" ? `:${provider}` : ""),
56
- messages: [
57
- {
58
- role: "system",
59
- content:
60
- files.length > 0
61
- ? FOLLOW_UP_SYSTEM_PROMPT
62
- : INITIAL_SYSTEM_PROMPT,
63
- },
64
- ...previousMessages.map((message: Message) => ({
65
- role: message.role,
66
- content: message.content,
67
- })),
68
- ...(files?.length > 0
69
- ? [
70
- {
71
- role: "user",
72
- content: `Here are the files that the user has provider:${files
73
- .map(
74
- (file: File) =>
75
- `File: ${file.path}\nContent: ${file.content}`
76
- )
77
- .join("\n")}\n\n${prompt}`,
78
- },
79
- ]
80
- : []),
81
- {
82
- role: "user",
83
- content: `${
84
- redesignMd?.url &&
85
- `Redesign the following website ${redesignMd.url}, try to use the same images and content, but you can still improve it if needed. Do the best version possibile. Here is the markdown:\n ${redesignMd.md} \n\n`
86
- }${prompt} ${
87
- medias && medias.length > 0
88
- ? `\nHere is the list of my media files: ${medias.join(
89
- ", "
90
- )}\n`
91
- : ""
92
- }`,
93
- }
94
- ],
95
- stream: true,
96
- max_tokens: 16_000,
97
- });
98
- while (true) {
99
- const { done, value } = await chatCompletion.next();
100
- if (done) {
101
- break;
102
- }
103
-
104
- const chunk = value.choices[0]?.delta?.content;
105
- if (chunk) {
106
- await writer.write(encoder.encode(chunk));
107
- }
108
- }
109
-
110
- await writer.close();
111
- } catch (error) {
112
- const errorMessage =
113
- error instanceof Error
114
- ? error.message
115
- : "An error occurred while processing your request";
116
-
117
- if (
118
- !hasRetried &&
119
- errorMessage?.includes(
120
- "Failed to perform inference: Model not found"
121
- )
122
- ) {
123
- hasRetried = true;
124
- if (model === DEFAULT_MODEL) {
125
- const availableFallbackModels = MODELS.filter(
126
- (m) => m.value !== model
127
- );
128
- const randomIndex = Math.floor(
129
- Math.random() * availableFallbackModels.length
130
- );
131
- currentModel = availableFallbackModels[randomIndex];
132
- } else {
133
- currentModel = DEFAULT_MODEL;
134
- }
135
- const switchMessage = `\n\n_Note: The selected model was not available. Switched to \`${currentModel}\`._\n\n`;
136
- await writer.write(encoder.encode(switchMessage));
137
-
138
- return tryGeneration();
139
- }
140
-
141
- try {
142
- let errorPayload = "";
143
- if (
144
- errorMessage?.includes("exceeded your monthly included credits") ||
145
- errorMessage?.includes("reached the free monthly usage limit")
146
- ) {
147
- errorPayload = JSON.stringify({
148
- messageError: errorMessage,
149
- showProMessage: true,
150
- isError: true,
151
- });
152
- } else {
153
- errorPayload = JSON.stringify({
154
- messageError: errorMessage,
155
- isError: true,
156
- });
157
- }
158
- await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`));
159
- await writer.close();
160
- } catch (closeError) {
161
- console.error("Failed to send error message:", closeError);
162
- try {
163
- await writer.abort(error);
164
- } catch (abortError) {
165
- console.error("Failed to abort writer:", abortError);
166
- }
167
- }
168
- }
169
- };
170
-
171
- await tryGeneration();
172
- })();
173
-
174
- return response;
175
- } catch (error) {
176
- return NextResponse.json(
177
- {
178
- error: error instanceof Error ? error.message : "Internal Server Error",
179
- },
180
- { status: 500 }
181
- );
182
- }
183
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/[...nextauth]/route.ts DELETED
@@ -1,6 +0,0 @@
1
- import NextAuth from "next-auth";
2
- import { authOptions } from "@/lib/auth";
3
-
4
- const handler = NextAuth(authOptions);
5
-
6
- export { handler as GET, handler as POST };
 
 
 
 
 
 
 
app/api/healthcheck/route.ts DELETED
@@ -1,5 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- export async function GET() {
4
- return NextResponse.json({ status: "ok" }, { status: 200 });
5
- }
 
 
 
 
 
 
app/api/projects/[repoId]/[commitId]/route.ts DELETED
@@ -1,49 +0,0 @@
1
- import { auth } from "@/lib/auth";
2
- import { createBranch, RepoDesignation } from "@huggingface/hub";
3
- import { format } from "date-fns";
4
- import { NextResponse } from "next/server";
5
-
6
- export async function POST(
7
- request: Request,
8
- { params }: { params: Promise<{ repoId: string; commitId: string }> }
9
- ) {
10
- const { repoId, commitId }: { repoId: string; commitId: string } =
11
- await params;
12
- const session = await auth();
13
- if (!session) {
14
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
15
- }
16
- const token = session.accessToken;
17
-
18
- const repo: RepoDesignation = {
19
- type: "space",
20
- name: session.user?.username + "/" + repoId,
21
- };
22
-
23
- const commitTitle = `🔖 ${format(new Date(), "dd/MM")} - ${format(
24
- new Date(),
25
- "HH:mm"
26
- )} - Set commit ${commitId} as default.`;
27
-
28
- await fetch(
29
- `https://huggingface.co/api/spaces/${session.user?.username}/${repoId}/branch/main`,
30
- {
31
- method: "POST",
32
- headers: {
33
- Authorization: `Bearer ${token}`,
34
- "Content-Type": "application/json",
35
- },
36
- body: JSON.stringify({
37
- startingPoint: commitId,
38
- overwrite: true,
39
- }),
40
- }
41
- ).catch((error) => {
42
- return NextResponse.json(
43
- { error: error ?? "Failed to create branch" },
44
- { status: 500 }
45
- );
46
- });
47
-
48
- return NextResponse.json({ success: true }, { status: 200 });
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/projects/[repoId]/download/route.ts DELETED
@@ -1,76 +0,0 @@
1
- import { auth } from "@/lib/auth";
2
- import { downloadFile, listFiles, RepoDesignation } from "@huggingface/hub";
3
- import { NextResponse } from "next/server";
4
- import JSZip from "jszip";
5
-
6
- export async function GET(
7
- request: Request,
8
- { params }: { params: Promise<{ repoId: string }> }
9
- ) {
10
- const { repoId }: { repoId: string } = await params;
11
- const session = await auth();
12
- if (!session) {
13
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14
- }
15
- const token = session.accessToken;
16
- const repo: RepoDesignation = {
17
- type: "space",
18
- name: session.user?.username + "/" + repoId,
19
- };
20
-
21
- try {
22
- const zip = new JSZip();
23
- for await (const fileInfo of listFiles({
24
- repo,
25
- accessToken: token as string,
26
- recursive: true,
27
- })) {
28
- if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
29
- continue;
30
- }
31
-
32
- try {
33
- const blob = await downloadFile({
34
- repo,
35
- accessToken: token as string,
36
- path: fileInfo.path,
37
- raw: true
38
- }).catch((error) => {
39
- return null;
40
- });
41
- if (!blob) {
42
- continue;
43
- }
44
-
45
- if (blob) {
46
- const arrayBuffer = await blob.arrayBuffer();
47
- zip.file(fileInfo.path, arrayBuffer);
48
- }
49
- } catch (error) {
50
- console.error(`Error downloading file ${fileInfo.path}:`, error);
51
- }
52
- }
53
-
54
- const zipBlob = await zip.generateAsync({
55
- type: "blob",
56
- compression: "DEFLATE",
57
- compressionOptions: {
58
- level: 6
59
- }
60
- });
61
-
62
- const projectName = `${session.user?.username}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
63
- const filename = `${projectName}.zip`;
64
-
65
- return new NextResponse(zipBlob, {
66
- headers: {
67
- "Content-Type": "application/zip",
68
- "Content-Disposition": `attachment; filename="${filename}"`,
69
- "Content-Length": zipBlob.size.toString(),
70
- },
71
- });
72
- } catch (error) {
73
- console.error("Error downloading project:", error);
74
- return NextResponse.json({ error: "Failed to download project" }, { status: 500 });
75
- }
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/projects/[repoId]/medias/route.ts DELETED
@@ -1,87 +0,0 @@
1
- import { auth } from "@/lib/auth";
2
- import { RepoDesignation, uploadFiles } from "@huggingface/hub";
3
- import { NextResponse } from "next/server";
4
-
5
- export async function POST(
6
- request: Request,
7
- { params }: { params: Promise<{ repoId: string }> }
8
- ) {
9
- const { repoId }: { repoId: string } = await params;
10
- const session = await auth();
11
- if (!session) {
12
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13
- }
14
- const token = session.accessToken;
15
-
16
- const repo: RepoDesignation = {
17
- type: "space",
18
- name: session.user?.username + "/" + repoId,
19
- };
20
-
21
- const formData = await request.formData();
22
- const newMedias = formData.getAll("images") as File[];
23
-
24
- const filesToUpload: File[] = [];
25
-
26
- if (!newMedias || newMedias.length === 0) {
27
- return NextResponse.json(
28
- {
29
- ok: false,
30
- error: "At least one media file is required under the 'images' key",
31
- },
32
- { status: 400 }
33
- );
34
- }
35
-
36
- try {
37
- for (const media of newMedias) {
38
- const isImage = media.type.startsWith("image/");
39
- const isVideo = media.type.startsWith("video/");
40
- const isAudio = media.type.startsWith("audio/");
41
-
42
- const folderPath = isImage
43
- ? "images/"
44
- : isVideo
45
- ? "videos/"
46
- : isAudio
47
- ? "audios/"
48
- : null;
49
-
50
- if (!folderPath) {
51
- return NextResponse.json(
52
- { ok: false, error: "Unsupported media type: " + media.type },
53
- { status: 400 }
54
- );
55
- }
56
-
57
- const mediaName = `${folderPath}${media.name}`;
58
- const processedFile = new File([media], mediaName, { type: media.type });
59
- filesToUpload.push(processedFile);
60
- }
61
-
62
- await uploadFiles({
63
- repo,
64
- files: filesToUpload,
65
- accessToken: token,
66
- commitTitle: `📁 Upload media files through DeepSite`,
67
- });
68
-
69
- return NextResponse.json(
70
- {
71
- success: true,
72
- medias: filesToUpload.map(
73
- (file) =>
74
- `https://huggingface.co/spaces/${session.user?.username}/${repoId}/resolve/main/${file.name}`
75
- ),
76
- },
77
- { status: 200 }
78
- );
79
- } catch (error) {
80
- return NextResponse.json(
81
- { ok: false, error: error ?? "Failed to upload media files" },
82
- { status: 500 }
83
- );
84
- }
85
-
86
- return NextResponse.json({ success: true }, { status: 200 });
87
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/projects/[repoId]/rename/route.ts DELETED
@@ -1,92 +0,0 @@
1
- import { auth } from "@/lib/auth";
2
- import { downloadFile, RepoDesignation, uploadFile } from "@huggingface/hub";
3
- import { format } from "date-fns";
4
- import { NextResponse } from "next/server";
5
-
6
- export async function PUT(
7
- request: Request,
8
- { params }: { params: Promise<{ repoId: string }> }
9
- ) {
10
- const { repoId }: { repoId: string } = await params;
11
- const session = await auth();
12
- if (!session) {
13
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14
- }
15
- const token = session.accessToken;
16
-
17
- const body = await request.json();
18
- const { newTitle } = body;
19
-
20
- if (!newTitle) {
21
- return NextResponse.json(
22
- { error: "newTitle is required" },
23
- { status: 400 }
24
- );
25
- }
26
-
27
- const repo: RepoDesignation = {
28
- type: "space",
29
- name: session.user?.username + "/" + repoId,
30
- };
31
-
32
- const blob = await downloadFile({
33
- repo,
34
- accessToken: token,
35
- path: "README.md",
36
- raw: true,
37
- }).catch((_) => {
38
- return null;
39
- });
40
-
41
- if (!blob) {
42
- return NextResponse.json(
43
- { error: "Could not fetch README.md" },
44
- { status: 500 }
45
- );
46
- }
47
-
48
- const readmeFile = await blob?.text();
49
- if (!readmeFile) {
50
- return NextResponse.json(
51
- { error: "Could not read README.md content" },
52
- { status: 500 }
53
- );
54
- }
55
-
56
- // Escape YAML values to prevent injection attacks
57
- const escapeYamlValue = (value: string): string => {
58
- if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
59
- return `"${value.replace(/"/g, '\\"')}"`;
60
- }
61
- return value;
62
- };
63
-
64
- // Escape commit message to prevent injection
65
- const escapeCommitMessage = (message: string): string => {
66
- return message.replace(/[\r\n]/g, " ").slice(0, 200);
67
- };
68
-
69
- const updatedReadmeFile = readmeFile.replace(
70
- /^title:\s*(.*)$/m,
71
- `title: ${escapeYamlValue(newTitle)}`
72
- );
73
-
74
- await uploadFile({
75
- repo,
76
- accessToken: token,
77
- file: new File([updatedReadmeFile], "README.md", { type: "text/markdown" }),
78
- commitTitle: escapeCommitMessage(
79
- `🐳 ${format(new Date(), "dd/MM")} - ${format(
80
- new Date(),
81
- "HH:mm"
82
- )} - Rename project to "${newTitle}"`
83
- ),
84
- });
85
-
86
- return NextResponse.json(
87
- {
88
- success: true,
89
- },
90
- { status: 200 }
91
- );
92
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/projects/[repoId]/route.ts DELETED
@@ -1,104 +0,0 @@
1
- import { auth } from "@/lib/auth";
2
- import { RepoDesignation, deleteRepo, uploadFiles } from "@huggingface/hub";
3
- import { format } from "date-fns";
4
- import { NextResponse } from "next/server";
5
-
6
- export async function PUT(
7
- request: Request,
8
- { params }: { params: Promise<{ repoId: string }> }
9
- ) {
10
- const { repoId }: { repoId: string } = await params;
11
- const session = await auth();
12
- if (!session) {
13
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14
- }
15
- const token = session.accessToken;
16
-
17
- const body = await request.json();
18
- const { files, prompt, isManualChanges } = body;
19
-
20
- if (!files) {
21
- return NextResponse.json({ error: "Files are required" }, { status: 400 });
22
- }
23
-
24
- if (!prompt) {
25
- return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
26
- }
27
-
28
- const repo: RepoDesignation = {
29
- type: "space",
30
- name: session.user?.username + "/" + repoId,
31
- };
32
-
33
- const filesToUpload: File[] = [];
34
- for (const file of files) {
35
- let mimeType = "text/x-python";
36
- if (file.path.endsWith(".txt")) {
37
- mimeType = "text/plain";
38
- } else if (file.path.endsWith(".md")) {
39
- mimeType = "text/markdown";
40
- } else if (file.path.endsWith(".json")) {
41
- mimeType = "application/json";
42
- }
43
- filesToUpload.push(new File([file.content], file.path, { type: mimeType }));
44
- }
45
- // Escape commit title to prevent injection
46
- const escapeCommitTitle = (title: string): string => {
47
- return title.replace(/[\r\n]/g, " ").slice(0, 200);
48
- };
49
-
50
- const baseTitle = isManualChanges
51
- ? ""
52
- : `🐳 ${format(new Date(), "dd/MM")} - ${format(new Date(), "HH:mm")} - `;
53
- const commitTitle = escapeCommitTitle(
54
- baseTitle + (prompt ?? "Follow-up DeepSite commit")
55
- );
56
- const response = await uploadFiles({
57
- repo,
58
- files: filesToUpload,
59
- accessToken: token,
60
- commitTitle,
61
- });
62
-
63
- return NextResponse.json(
64
- {
65
- success: true,
66
- commit: {
67
- oid: response.commit,
68
- title: commitTitle,
69
- date: new Date(),
70
- },
71
- },
72
- { status: 200 }
73
- );
74
- }
75
-
76
- export async function DELETE(
77
- request: Request,
78
- { params }: { params: Promise<{ repoId: string }> }
79
- ) {
80
- const { repoId }: { repoId: string } = await params;
81
- const session = await auth();
82
- if (!session) {
83
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
84
- }
85
- const token = session.accessToken;
86
-
87
- const repo: RepoDesignation = {
88
- type: "space",
89
- name: session.user?.username + "/" + repoId,
90
- };
91
-
92
- try {
93
- await deleteRepo({
94
- repo,
95
- accessToken: token as string,
96
- });
97
-
98
- return NextResponse.json({ success: true }, { status: 200 });
99
- } catch (error) {
100
- const errMsg =
101
- error instanceof Error ? error.message : "Failed to delete project";
102
- return NextResponse.json({ error: errMsg }, { status: 500 });
103
- }
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/projects/route.ts DELETED
@@ -1,145 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { RepoDesignation, createRepo, uploadFiles } from "@huggingface/hub";
3
-
4
- import { auth } from "@/lib/auth";
5
- import {
6
- COLORS,
7
- EMOJIS_FOR_SPACE,
8
- injectDeepSiteBadge,
9
- isIndexPage,
10
- } from "@/lib/utils";
11
-
12
- // todo: catch error while publishing project, and return the error to the user
13
- // if space has been created, but can't push, try again or catch well the error and return the error to the user
14
-
15
- export async function POST(request: Request) {
16
- const session = await auth();
17
- if (!session) {
18
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
- }
20
- const token = session.accessToken;
21
-
22
- const body = await request.json();
23
- const { projectTitle, files, prompt } = body;
24
-
25
- if (!files) {
26
- return NextResponse.json(
27
- { error: "Project title and files are required" },
28
- { status: 400 }
29
- );
30
- }
31
-
32
- const title =
33
- projectTitle || projectTitle !== "" ? projectTitle : "DeepSite Project";
34
-
35
- let formattedTitle = title
36
- .toLowerCase()
37
- .replace(/[^a-z0-9]+/g, "-")
38
- .split("-")
39
- .filter(Boolean)
40
- .join("-")
41
- .slice(0, 75);
42
-
43
- formattedTitle =
44
- formattedTitle + "-" + Math.random().toString(36).substring(2, 7);
45
-
46
- const repo: RepoDesignation = {
47
- type: "space",
48
- name: session.user?.username + "/" + formattedTitle,
49
- };
50
-
51
- // Escape YAML values to prevent injection attacks
52
- const escapeYamlValue = (value: string): string => {
53
- if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
54
- return `"${value.replace(/"/g, '\\"')}"`;
55
- }
56
- return value;
57
- };
58
-
59
- // Escape markdown headers to prevent injection
60
- const escapeMarkdownHeader = (value: string): string => {
61
- return value.replace(/^#+\s*/g, "").replace(/\n/g, " ");
62
- };
63
-
64
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
65
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
66
- const emoji =
67
- EMOJIS_FOR_SPACE[Math.floor(Math.random() * EMOJIS_FOR_SPACE.length)];
68
- const README = `---
69
- title: ${escapeYamlValue(projectTitle)}
70
- colorFrom: ${colorFrom}
71
- colorTo: ${colorTo}
72
- sdk: static
73
- emoji: ${emoji}
74
- tags:
75
- - deepsite-v4
76
- ---
77
-
78
- # ${escapeMarkdownHeader(title)}
79
-
80
- This project has been created with [DeepSite](https://deepsite.hf.co) AI Vibe Coding.
81
- `;
82
-
83
- const filesToUpload: File[] = [
84
- new File([README], "README.md", { type: "text/markdown" }),
85
- ];
86
- for (const file of files) {
87
- let mimeType = "text/html";
88
- if (file.path.endsWith(".css")) {
89
- mimeType = "text/css";
90
- } else if (file.path.endsWith(".js")) {
91
- mimeType = "text/javascript";
92
- }
93
- const content =
94
- mimeType === "text/html" && isIndexPage(file.path)
95
- ? injectDeepSiteBadge(file.content)
96
- : file.content;
97
-
98
- filesToUpload.push(new File([content], file.path, { type: mimeType }));
99
- }
100
-
101
- let repoUrl: string | undefined;
102
-
103
- try {
104
- // Create the space first
105
- const createResult = await createRepo({
106
- accessToken: token as string,
107
- repo: repo,
108
- sdk: "static",
109
- });
110
- repoUrl = createResult.repoUrl;
111
-
112
- // Escape commit message to prevent injection
113
- const escapeCommitMessage = (message: string): string => {
114
- return message.replace(/[\r\n]/g, " ").slice(0, 200);
115
- };
116
- const commitMessage = escapeCommitMessage(prompt ?? "Initial DeepSite commit");
117
-
118
- // Upload files to the created space
119
- await uploadFiles({
120
- repo,
121
- files: filesToUpload,
122
- accessToken: token as string,
123
- commitTitle: commitMessage,
124
- });
125
-
126
- const path = repoUrl.split("/").slice(-2).join("/");
127
-
128
- return NextResponse.json({ repoUrl: path }, { status: 200 });
129
- } catch (error) {
130
- const errMsg =
131
- error instanceof Error ? error.message : "Failed to create or upload to space";
132
-
133
- // If space was created but upload failed, include the repo URL in the error
134
- if (repoUrl) {
135
- const path = repoUrl.split("/").slice(-2).join("/");
136
- return NextResponse.json({
137
- error: `${errMsg}. Space was created at ${path} but files could not be uploaded.`,
138
- repoUrl: path,
139
- partialSuccess: true
140
- }, { status: 500 });
141
- }
142
-
143
- return NextResponse.json({ error: errMsg }, { status: 500 });
144
- }
145
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/redesign/route.ts DELETED
@@ -1,73 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { NextRequest, NextResponse } from "next/server";
3
-
4
- const FETCH_TIMEOUT = 30_000;
5
- export const maxDuration = 60;
6
-
7
- export async function PUT(request: NextRequest) {
8
- const body = await request.json();
9
- const { url } = body;
10
-
11
- if (!url) {
12
- return NextResponse.json({ error: "URL is required" }, { status: 400 });
13
- }
14
-
15
- try {
16
- const controller = new AbortController();
17
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
18
-
19
- try {
20
- const response = await fetch(
21
- `https://r.jina.ai/${encodeURIComponent(url)}`,
22
- {
23
- method: "POST",
24
- signal: controller.signal,
25
- }
26
- );
27
-
28
- clearTimeout(timeoutId);
29
-
30
- if (!response.ok) {
31
- return NextResponse.json(
32
- { error: "Failed to fetch redesign" },
33
- { status: 500 }
34
- );
35
- }
36
- const markdown = await response.text();
37
- return NextResponse.json(
38
- {
39
- ok: true,
40
- markdown,
41
- },
42
- { status: 200 }
43
- );
44
- } catch (fetchError: any) {
45
- clearTimeout(timeoutId);
46
-
47
- if (fetchError.name === "AbortError") {
48
- return NextResponse.json(
49
- {
50
- error:
51
- "Request timeout: The external service took too long to respond. Please try again.",
52
- },
53
- { status: 504 }
54
- );
55
- }
56
- throw fetchError;
57
- }
58
- } catch (error: any) {
59
- if (error.name === "AbortError" || error.message?.includes("timeout")) {
60
- return NextResponse.json(
61
- {
62
- error:
63
- "Request timeout: The external service took too long to respond. Please try again.",
64
- },
65
- { status: 504 }
66
- );
67
- }
68
- return NextResponse.json(
69
- { error: error.message || "An error occurred" },
70
- { status: 500 }
71
- );
72
- }
73
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/favicon.ico DELETED
Binary file (25.9 kB)
 
app/layout.tsx DELETED
@@ -1,108 +0,0 @@
1
- import type { Metadata, Viewport } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
3
- import { NextStepProvider } from "nextstepjs";
4
- import Script from "next/script";
5
-
6
- import "@/app/globals.css";
7
- import { ThemeProvider } from "@/components/providers/theme";
8
- import { AuthProvider } from "@/components/providers/session";
9
- import { Toaster } from "@/components/ui/sonner";
10
- import { ReactQueryProvider } from "@/components/providers/react-query";
11
- import { generateSEO, generateStructuredData } from "@/lib/seo";
12
- import { NotAuthorizedDomain } from "@/components/not-authorized";
13
-
14
- const geistSans = Geist({
15
- variable: "--font-geist-sans",
16
- subsets: ["latin"],
17
- });
18
-
19
- const geistMono = Geist_Mono({
20
- variable: "--font-geist-mono",
21
- subsets: ["latin"],
22
- });
23
-
24
- export const metadata: Metadata = {
25
- ...generateSEO({
26
- title: "DeepSite | Build with AI ✨",
27
- description:
28
- "DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
29
- path: "/",
30
- }),
31
- appleWebApp: {
32
- capable: true,
33
- title: "DeepSite",
34
- statusBarStyle: "black-translucent",
35
- },
36
- icons: {
37
- icon: "/logo.svg",
38
- shortcut: "/logo.svg",
39
- apple: "/logo.svg",
40
- },
41
- verification: {
42
- google: process.env.GOOGLE_SITE_VERIFICATION,
43
- },
44
- };
45
-
46
- export const viewport: Viewport = {
47
- initialScale: 1,
48
- maximumScale: 1,
49
- themeColor: "#4f46e5",
50
- };
51
-
52
- export default async function RootLayout({
53
- children,
54
- }: Readonly<{
55
- children: React.ReactNode;
56
- }>) {
57
- const structuredData = generateStructuredData("WebApplication", {
58
- name: "DeepSite",
59
- description: "Build websites with AI, no code required",
60
- url: "https://deepsite.hf.co",
61
- });
62
- const organizationData = generateStructuredData("Organization", {
63
- name: "DeepSite",
64
- url: "https://deepsite.hf.co",
65
- });
66
-
67
- return (
68
- <html lang="en" suppressHydrationWarning>
69
- <body
70
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
71
- >
72
- <script
73
- type="application/ld+json"
74
- dangerouslySetInnerHTML={{
75
- __html: JSON.stringify(structuredData),
76
- }}
77
- />
78
- <script
79
- type="application/ld+json"
80
- dangerouslySetInnerHTML={{
81
- __html: JSON.stringify(organizationData),
82
- }}
83
- />
84
- <Script
85
- defer
86
- data-domain="deepsite.hf.co"
87
- src="https://plausible.io/js/script.js"
88
- />
89
- <Toaster richColors />
90
- <AuthProvider>
91
- <ReactQueryProvider>
92
- <ThemeProvider
93
- attribute="class"
94
- defaultTheme="dark"
95
- enableSystem
96
- disableTransitionOnChange
97
- >
98
- <NextStepProvider>
99
- {children}
100
- <NotAuthorizedDomain />
101
- </NextStepProvider>
102
- </ThemeProvider>
103
- </ReactQueryProvider>
104
- </AuthProvider>
105
- </body>
106
- </html>
107
- );
108
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/new/page.tsx DELETED
@@ -1,18 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
- import { auth } from "@/lib/auth";
3
- import { redirect } from "next/navigation";
4
-
5
- export default async function NewProjectPage({
6
- searchParams,
7
- }: {
8
- searchParams: Promise<{ prompt: string }>;
9
- }) {
10
- const session = await auth();
11
-
12
- if (!session) {
13
- redirect("/api/auth/signin?callbackUrl=/new");
14
- }
15
-
16
- const { prompt } = await searchParams;
17
- return <AppEditor isNew={true} initialPrompt={prompt} />;
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/not-found.tsx DELETED
@@ -1,17 +0,0 @@
1
- import { NotFoundButtons } from "@/components/not-found/buttons";
2
- import { Navigation } from "@/components/public/navigation";
3
-
4
- export default function NotFound() {
5
- return (
6
- <div className="min-h-screen font-sans">
7
- <Navigation />
8
- <div className="px-6 py-16 max-w-5xl mx-auto text-center">
9
- <h1 className="text-5xl font-bold mb-5">Oh no! Page not found.</h1>
10
- <p className="text-lg text-muted-foreground mb-8">
11
- The page you are looking for does not exist.
12
- </p>
13
- <NotFoundButtons />
14
- </div>
15
- </div>
16
- );
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{app → assets}/globals.css RENAMED
@@ -6,8 +6,8 @@
6
  @theme inline {
7
  --color-background: var(--background);
8
  --color-foreground: var(--foreground);
9
- --font-sans: var(--font-geist-sans);
10
- --font-mono: var(--font-geist-mono);
11
  --color-sidebar-ring: var(--sidebar-ring);
12
  --color-sidebar-border: var(--sidebar-border);
13
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -44,7 +44,7 @@
44
  }
45
 
46
  :root {
47
- --radius: 0.65rem;
48
  --background: oklch(1 0 0);
49
  --foreground: oklch(0.145 0 0);
50
  --card: oklch(1 0 0);
@@ -68,7 +68,6 @@
68
  --chart-3: oklch(0.398 0.07 227.392);
69
  --chart-4: oklch(0.828 0.189 84.429);
70
  --chart-5: oklch(0.769 0.188 70.08);
71
- --radius: 0.625rem;
72
  --sidebar: oklch(0.985 0 0);
73
  --sidebar-foreground: oklch(0.145 0 0);
74
  --sidebar-primary: oklch(0.205 0 0);
@@ -113,6 +112,10 @@
113
  --sidebar-ring: oklch(0.556 0 0);
114
  }
115
 
 
 
 
 
116
  @layer base {
117
  * {
118
  @apply border-border outline-ring/50;
@@ -120,49 +123,258 @@
120
  body {
121
  @apply bg-background text-foreground;
122
  }
 
 
 
 
 
 
 
 
 
 
123
  }
124
 
125
  .monaco-editor .margin {
126
- @apply bg-background!;
127
  }
128
  .monaco-editor .monaco-editor-background {
129
- @apply bg-background!;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  }
131
- .monaco-editor .decorationsOverviewRuler {
132
- @apply opacity-0!;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
134
- .monaco-editor .view-line {
135
- /* @apply bg-primary/50!; */
 
 
 
 
 
 
 
136
  }
137
- .monaco-editor .scroll-decoration {
138
- @apply opacity-0!;
 
 
 
 
 
139
  }
140
- .monaco-editor .cursors-layer .cursor {
141
- @apply bg-primary!;
 
 
 
 
 
 
 
 
142
  }
143
 
144
- .content-placeholder::before {
145
- content: attr(data-placeholder);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  position: absolute;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  pointer-events: none;
148
- opacity: 0.5;
149
- @apply top-5 left-6;
150
  }
151
 
152
- .sp-layout
153
- .sp-file-explorer
154
- .sp-file-explorer-list
155
- .sp-explorer[data-active="true"] {
156
- @apply text-indigo-500!;
157
  }
158
 
159
- .sp-layout
160
- .sp-stack
161
- .sp-tabs
162
- .sp-tab-container[aria-selected="true"]
163
- .sp-tab-button {
164
- @apply text-indigo-500!;
165
- }
166
- .sp-layout .sp-stack .sp-tabs .sp-tab-container:has(button:focus) {
167
- @apply outline-none! border-none!;
168
  }
 
6
  @theme inline {
7
  --color-background: var(--background);
8
  --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-inter-sans);
10
+ --font-mono: var(--font-ptSans-mono);
11
  --color-sidebar-ring: var(--sidebar-ring);
12
  --color-sidebar-border: var(--sidebar-border);
13
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
 
44
  }
45
 
46
  :root {
47
+ --radius: 0.625rem;
48
  --background: oklch(1 0 0);
49
  --foreground: oklch(0.145 0 0);
50
  --card: oklch(1 0 0);
 
68
  --chart-3: oklch(0.398 0.07 227.392);
69
  --chart-4: oklch(0.828 0.189 84.429);
70
  --chart-5: oklch(0.769 0.188 70.08);
 
71
  --sidebar: oklch(0.985 0 0);
72
  --sidebar-foreground: oklch(0.145 0 0);
73
  --sidebar-primary: oklch(0.205 0 0);
 
112
  --sidebar-ring: oklch(0.556 0 0);
113
  }
114
 
115
+ body {
116
+ @apply scroll-smooth
117
+ }
118
+
119
  @layer base {
120
  * {
121
  @apply border-border outline-ring/50;
 
123
  body {
124
  @apply bg-background text-foreground;
125
  }
126
+ html {
127
+ @apply scroll-smooth;
128
+ }
129
+ }
130
+
131
+ .background__noisy {
132
+ @apply bg-blend-normal pointer-events-none opacity-90;
133
+ background-size: 25ww auto;
134
+ background-image: url("/deepsite/background_noisy.webp");
135
+ @apply fixed w-screen h-screen -z-1 top-0 left-0;
136
  }
137
 
138
  .monaco-editor .margin {
139
+ @apply !bg-neutral-900;
140
  }
141
  .monaco-editor .monaco-editor-background {
142
+ @apply !bg-neutral-900;
143
+ }
144
+ .monaco-editor .line-numbers {
145
+ @apply !text-neutral-500;
146
+ }
147
+
148
+ .matched-line {
149
+ @apply bg-sky-500/30;
150
+ }
151
+
152
+ /* Fast liquid deformation animations */
153
+ @keyframes liquidBlob1 {
154
+ 0%, 100% {
155
+ border-radius: 40% 60% 50% 50%;
156
+ transform: scaleX(1) scaleY(1) rotate(0deg);
157
+ }
158
+ 12.5% {
159
+ border-radius: 20% 80% 70% 30%;
160
+ transform: scaleX(1.6) scaleY(0.4) rotate(25deg);
161
+ }
162
+ 25% {
163
+ border-radius: 80% 20% 30% 70%;
164
+ transform: scaleX(0.5) scaleY(2.1) rotate(-15deg);
165
+ }
166
+ 37.5% {
167
+ border-radius: 30% 70% 80% 20%;
168
+ transform: scaleX(1.8) scaleY(0.6) rotate(40deg);
169
+ }
170
+ 50% {
171
+ border-radius: 70% 30% 20% 80%;
172
+ transform: scaleX(0.4) scaleY(1.9) rotate(-30deg);
173
+ }
174
+ 62.5% {
175
+ border-radius: 25% 75% 60% 40%;
176
+ transform: scaleX(1.5) scaleY(0.7) rotate(55deg);
177
+ }
178
+ 75% {
179
+ border-radius: 75% 25% 40% 60%;
180
+ transform: scaleX(0.6) scaleY(1.7) rotate(-10deg);
181
+ }
182
+ 87.5% {
183
+ border-radius: 50% 50% 75% 25%;
184
+ transform: scaleX(1.3) scaleY(0.8) rotate(35deg);
185
+ }
186
+ }
187
+
188
+ @keyframes liquidBlob2 {
189
+ 0%, 100% {
190
+ border-radius: 60% 40% 50% 50%;
191
+ transform: scaleX(1) scaleY(1) rotate(12deg);
192
+ }
193
+ 16% {
194
+ border-radius: 15% 85% 60% 40%;
195
+ transform: scaleX(0.3) scaleY(2.3) rotate(50deg);
196
+ }
197
+ 32% {
198
+ border-radius: 85% 15% 25% 75%;
199
+ transform: scaleX(2.0) scaleY(0.5) rotate(-20deg);
200
+ }
201
+ 48% {
202
+ border-radius: 30% 70% 85% 15%;
203
+ transform: scaleX(0.4) scaleY(1.8) rotate(70deg);
204
+ }
205
+ 64% {
206
+ border-radius: 70% 30% 15% 85%;
207
+ transform: scaleX(1.9) scaleY(0.6) rotate(-35deg);
208
+ }
209
+ 80% {
210
+ border-radius: 40% 60% 70% 30%;
211
+ transform: scaleX(0.7) scaleY(1.6) rotate(45deg);
212
+ }
213
+ }
214
+
215
+ @keyframes liquidBlob3 {
216
+ 0%, 100% {
217
+ border-radius: 50% 50% 40% 60%;
218
+ transform: scaleX(1) scaleY(1) rotate(0deg);
219
+ }
220
+ 20% {
221
+ border-radius: 10% 90% 75% 25%;
222
+ transform: scaleX(2.2) scaleY(0.3) rotate(-45deg);
223
+ }
224
+ 40% {
225
+ border-radius: 90% 10% 20% 80%;
226
+ transform: scaleX(0.4) scaleY(2.5) rotate(60deg);
227
+ }
228
+ 60% {
229
+ border-radius: 25% 75% 90% 10%;
230
+ transform: scaleX(1.7) scaleY(0.5) rotate(-25deg);
231
+ }
232
+ 80% {
233
+ border-radius: 75% 25% 10% 90%;
234
+ transform: scaleX(0.6) scaleY(2.0) rotate(80deg);
235
+ }
236
  }
237
+
238
+ @keyframes liquidBlob4 {
239
+ 0%, 100% {
240
+ border-radius: 45% 55% 50% 50%;
241
+ transform: scaleX(1) scaleY(1) rotate(-15deg);
242
+ }
243
+ 14% {
244
+ border-radius: 90% 10% 65% 35%;
245
+ transform: scaleX(0.2) scaleY(2.8) rotate(35deg);
246
+ }
247
+ 28% {
248
+ border-radius: 10% 90% 20% 80%;
249
+ transform: scaleX(2.4) scaleY(0.4) rotate(-50deg);
250
+ }
251
+ 42% {
252
+ border-radius: 35% 65% 90% 10%;
253
+ transform: scaleX(0.3) scaleY(2.1) rotate(70deg);
254
+ }
255
+ 56% {
256
+ border-radius: 80% 20% 10% 90%;
257
+ transform: scaleX(2.0) scaleY(0.5) rotate(-40deg);
258
+ }
259
+ 70% {
260
+ border-radius: 20% 80% 55% 45%;
261
+ transform: scaleX(0.5) scaleY(1.9) rotate(55deg);
262
+ }
263
+ 84% {
264
+ border-radius: 65% 35% 80% 20%;
265
+ transform: scaleX(1.6) scaleY(0.6) rotate(-25deg);
266
+ }
267
  }
268
+
269
+ /* Fast flowing movement animations */
270
+ @keyframes liquidFlow1 {
271
+ 0%, 100% { transform: translate(0, 0); }
272
+ 16% { transform: translate(60px, -40px); }
273
+ 32% { transform: translate(-45px, -70px); }
274
+ 48% { transform: translate(80px, 25px); }
275
+ 64% { transform: translate(-30px, 60px); }
276
+ 80% { transform: translate(50px, -20px); }
277
  }
278
+
279
+ @keyframes liquidFlow2 {
280
+ 0%, 100% { transform: translate(0, 0); }
281
+ 20% { transform: translate(-70px, 50px); }
282
+ 40% { transform: translate(90px, -30px); }
283
+ 60% { transform: translate(-40px, -55px); }
284
+ 80% { transform: translate(65px, 35px); }
285
  }
286
+
287
+ @keyframes liquidFlow3 {
288
+ 0%, 100% { transform: translate(0, 0); }
289
+ 12% { transform: translate(-50px, -60px); }
290
+ 24% { transform: translate(40px, -20px); }
291
+ 36% { transform: translate(-30px, 70px); }
292
+ 48% { transform: translate(70px, 20px); }
293
+ 60% { transform: translate(-60px, -35px); }
294
+ 72% { transform: translate(35px, 55px); }
295
+ 84% { transform: translate(-25px, -45px); }
296
  }
297
 
298
+ @keyframes liquidFlow4 {
299
+ 0%, 100% { transform: translate(0, 0); }
300
+ 14% { transform: translate(50px, 60px); }
301
+ 28% { transform: translate(-80px, -40px); }
302
+ 42% { transform: translate(30px, -90px); }
303
+ 56% { transform: translate(-55px, 45px); }
304
+ 70% { transform: translate(75px, -25px); }
305
+ 84% { transform: translate(-35px, 65px); }
306
+ }
307
+
308
+ /* Light sweep animation for buttons */
309
+ @keyframes lightSweep {
310
+ 0% {
311
+ transform: translateX(-150%);
312
+ opacity: 0;
313
+ }
314
+ 8% {
315
+ opacity: 0.3;
316
+ }
317
+ 25% {
318
+ opacity: 0.8;
319
+ }
320
+ 42% {
321
+ opacity: 0.3;
322
+ }
323
+ 50% {
324
+ transform: translateX(150%);
325
+ opacity: 0;
326
+ }
327
+ 58% {
328
+ opacity: 0.3;
329
+ }
330
+ 75% {
331
+ opacity: 0.8;
332
+ }
333
+ 92% {
334
+ opacity: 0.3;
335
+ }
336
+ 100% {
337
+ transform: translateX(-150%);
338
+ opacity: 0;
339
+ }
340
+ }
341
+
342
+ .light-sweep {
343
+ position: relative;
344
+ overflow: hidden;
345
+ }
346
+
347
+ .light-sweep::before {
348
+ content: '';
349
  position: absolute;
350
+ top: 0;
351
+ left: 0;
352
+ right: 0;
353
+ bottom: 0;
354
+ width: 300%;
355
+ background: linear-gradient(
356
+ 90deg,
357
+ transparent 0%,
358
+ transparent 20%,
359
+ rgba(56, 189, 248, 0.1) 35%,
360
+ rgba(56, 189, 248, 0.2) 45%,
361
+ rgba(255, 255, 255, 0.2) 50%,
362
+ rgba(168, 85, 247, 0.2) 55%,
363
+ rgba(168, 85, 247, 0.1) 65%,
364
+ transparent 80%,
365
+ transparent 100%
366
+ );
367
+ animation: lightSweep 7s cubic-bezier(0.4, 0, 0.2, 1) infinite;
368
  pointer-events: none;
369
+ z-index: 1;
370
+ filter: blur(1px);
371
  }
372
 
373
+ .transparent-scroll {
374
+ scrollbar-width: none; /* Firefox */
375
+ -ms-overflow-style: none; /* IE and Edge */
 
 
376
  }
377
 
378
+ .transparent-scroll::-webkit-scrollbar {
379
+ display: none; /* Chrome, Safari, Opera */
 
 
 
 
 
 
 
380
  }
assets/hf-logo.svg DELETED
assets/logo.svg ADDED
assets/pro.svg DELETED
chart/Chart.yaml DELETED
@@ -1,5 +0,0 @@
1
- apiVersion: v2
2
- name: deepsite
3
- version: 0.0.0-latest
4
- type: application
5
- icon: https://huggingface.co/front/assets/huggingface_logo-noborder.svg
 
 
 
 
 
 
chart/env/prod.yaml DELETED
@@ -1,59 +0,0 @@
1
- nodeSelector:
2
- role-deepsite: "true"
3
-
4
- tolerations:
5
- - key: "huggingface.co/deepsite"
6
- operator: "Equal"
7
- value: "true"
8
- effect: "NoSchedule"
9
-
10
- serviceAccount:
11
- enabled: true
12
- create: true
13
- name: deepsite-prod
14
-
15
- ingress:
16
- path: "/"
17
- annotations:
18
- alb.ingress.kubernetes.io/healthcheck-path: "/api/healthcheck"
19
- alb.ingress.kubernetes.io/listen-ports: "[{\"HTTP\": 80}, {\"HTTPS\": 443}]"
20
- alb.ingress.kubernetes.io/load-balancer-name: "hub-utils-prod-cloudfront"
21
- alb.ingress.kubernetes.io/group.name: "hub-utils-prod-cloudfront"
22
- alb.ingress.kubernetes.io/scheme: "internal"
23
- alb.ingress.kubernetes.io/ssl-redirect: "443"
24
- alb.ingress.kubernetes.io/tags: "Env=prod,Project=hub,Terraform=true"
25
- alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
26
- alb.ingress.kubernetes.io/target-type: "ip"
27
- alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:us-east-1:707930574880:certificate/5b25b145-75db-4837-b9f3-7f238ba8a9c7,arn:aws:acm:us-east-1:707930574880:certificate/bfdf509c-f44b-400f-b9e1-6f7a861abe91"
28
- kubernetes.io/ingress.class: "alb"
29
-
30
- networkPolicy:
31
- enabled: true
32
- allowedBlocks:
33
- - 10.0.0.0/16
34
-
35
-
36
- ingressInternal:
37
- enabled: false
38
-
39
- envVars:
40
- NEXTAUTH_URL: https://deepsite.hf.co/api/auth
41
-
42
- infisical:
43
- enabled: true
44
- env: "prod-us-east-1"
45
-
46
- autoscaling:
47
- enabled: true
48
- minReplicas: 1
49
- maxReplicas: 10
50
- targetMemoryUtilizationPercentage: "50"
51
- targetCPUUtilizationPercentage: "50"
52
-
53
- resources:
54
- requests:
55
- cpu: 2
56
- memory: 4Gi
57
- limits:
58
- cpu: 4
59
- memory: 8Gi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/_helpers.tpl DELETED
@@ -1,22 +0,0 @@
1
- {{- define "name" -}}
2
- {{- default $.Release.Name | trunc 63 | trimSuffix "-" -}}
3
- {{- end -}}
4
-
5
- {{- define "app.name" -}}
6
- chat-ui
7
- {{- end -}}
8
-
9
- {{- define "labels.standard" -}}
10
- release: {{ $.Release.Name | quote }}
11
- heritage: {{ $.Release.Service | quote }}
12
- chart: "{{ include "name" . }}"
13
- app: "{{ include "app.name" . }}"
14
- {{- end -}}
15
-
16
- {{- define "labels.resolver" -}}
17
- release: {{ $.Release.Name | quote }}
18
- heritage: {{ $.Release.Service | quote }}
19
- chart: "{{ include "name" . }}"
20
- app: "{{ include "app.name" . }}-resolver"
21
- {{- end -}}
22
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/config.yaml DELETED
@@ -1,10 +0,0 @@
1
- apiVersion: v1
2
- kind: ConfigMap
3
- metadata:
4
- labels: {{ include "labels.standard" . | nindent 4 }}
5
- name: {{ include "name" . }}
6
- namespace: {{ .Release.Namespace }}
7
- data:
8
- {{- range $key, $value := $.Values.envVars }}
9
- {{ $key }}: {{ $value | quote }}
10
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
chart/templates/deployment.yaml DELETED
@@ -1,81 +0,0 @@
1
- apiVersion: apps/v1
2
- kind: Deployment
3
- metadata:
4
- labels: {{ include "labels.standard" . | nindent 4 }}
5
- name: {{ include "name" . }}
6
- namespace: {{ .Release.Namespace }}
7
- {{- if .Values.infisical.enabled }}
8
- annotations:
9
- secrets.infisical.com/auto-reload: "true"
10
- {{- end }}
11
- spec:
12
- progressDeadlineSeconds: 600
13
- {{- if not $.Values.autoscaling.enabled }}
14
- replicas: {{ .Values.replicas }}
15
- {{- end }}
16
- revisionHistoryLimit: 10
17
- selector:
18
- matchLabels: {{ include "labels.standard" . | nindent 6 }}
19
- strategy:
20
- rollingUpdate:
21
- maxSurge: 25%
22
- maxUnavailable: 25%
23
- type: RollingUpdate
24
- template:
25
- metadata:
26
- labels: {{ include "labels.standard" . | nindent 8 }}
27
- annotations:
28
- checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
29
- {{- if $.Values.envVars.NODE_LOG_STRUCTURED_DATA }}
30
- co.elastic.logs/json.expand_keys: "true"
31
- {{- end }}
32
- spec:
33
- {{- if .Values.serviceAccount.enabled }}
34
- serviceAccountName: "{{ .Values.serviceAccount.name | default (include "name" .) }}"
35
- {{- end }}
36
- containers:
37
- - name: chat-ui
38
- image: "{{ .Values.image.repository }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
39
- imagePullPolicy: {{ .Values.image.pullPolicy }}
40
- readinessProbe:
41
- failureThreshold: 30
42
- periodSeconds: 10
43
- httpGet:
44
- path: {{ $.Values.envVars.APP_BASE | default "" }}/api/healthcheck
45
- port: {{ $.Values.envVars.APP_PORT | default 3001 | int }}
46
- livenessProbe:
47
- failureThreshold: 30
48
- periodSeconds: 10
49
- httpGet:
50
- path: {{ $.Values.envVars.APP_BASE | default "" }}/api/healthcheck
51
- port: {{ $.Values.envVars.APP_PORT | default 3001 | int }}
52
- ports:
53
- - containerPort: {{ $.Values.envVars.APP_PORT | default 3001 | int }}
54
- name: http
55
- protocol: TCP
56
- {{- if eq "true" $.Values.envVars.METRICS_ENABLED }}
57
- - containerPort: {{ $.Values.envVars.METRICS_PORT | default 5565 | int }}
58
- name: metrics
59
- protocol: TCP
60
- {{- end }}
61
- resources: {{ toYaml .Values.resources | nindent 12 }}
62
- {{- with $.Values.extraEnv }}
63
- env:
64
- {{- toYaml . | nindent 14 }}
65
- {{- end }}
66
- envFrom:
67
- - configMapRef:
68
- name: {{ include "name" . }}
69
- {{- if $.Values.infisical.enabled }}
70
- - secretRef:
71
- name: {{ include "name" $ }}-secs
72
- {{- end }}
73
- {{- with $.Values.extraEnvFrom }}
74
- {{- toYaml . | nindent 14 }}
75
- {{- end }}
76
- nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }}
77
- tolerations: {{ toYaml .Values.tolerations | nindent 8 }}
78
- volumes:
79
- - name: config
80
- configMap:
81
- name: {{ include "name" . }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/hpa.yaml DELETED
@@ -1,45 +0,0 @@
1
- {{- if $.Values.autoscaling.enabled }}
2
- apiVersion: autoscaling/v2
3
- kind: HorizontalPodAutoscaler
4
- metadata:
5
- labels: {{ include "labels.standard" . | nindent 4 }}
6
- name: {{ include "name" . }}
7
- namespace: {{ .Release.Namespace }}
8
- spec:
9
- scaleTargetRef:
10
- apiVersion: apps/v1
11
- kind: Deployment
12
- name: {{ include "name" . }}
13
- minReplicas: {{ $.Values.autoscaling.minReplicas }}
14
- maxReplicas: {{ $.Values.autoscaling.maxReplicas }}
15
- metrics:
16
- {{- if ne "" $.Values.autoscaling.targetMemoryUtilizationPercentage }}
17
- - type: Resource
18
- resource:
19
- name: memory
20
- target:
21
- type: Utilization
22
- averageUtilization: {{ $.Values.autoscaling.targetMemoryUtilizationPercentage | int }}
23
- {{- end }}
24
- {{- if ne "" $.Values.autoscaling.targetCPUUtilizationPercentage }}
25
- - type: Resource
26
- resource:
27
- name: cpu
28
- target:
29
- type: Utilization
30
- averageUtilization: {{ $.Values.autoscaling.targetCPUUtilizationPercentage | int }}
31
- {{- end }}
32
- behavior:
33
- scaleDown:
34
- stabilizationWindowSeconds: 600
35
- policies:
36
- - type: Percent
37
- value: 10
38
- periodSeconds: 60
39
- scaleUp:
40
- stabilizationWindowSeconds: 0
41
- policies:
42
- - type: Pods
43
- value: 1
44
- periodSeconds: 30
45
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/infisical.yaml DELETED
@@ -1,24 +0,0 @@
1
- {{- if .Values.infisical.enabled }}
2
- apiVersion: secrets.infisical.com/v1alpha1
3
- kind: InfisicalSecret
4
- metadata:
5
- name: {{ include "name" $ }}-infisical-secret
6
- namespace: {{ $.Release.Namespace }}
7
- spec:
8
- authentication:
9
- universalAuth:
10
- credentialsRef:
11
- secretName: {{ .Values.infisical.operatorSecretName | quote }}
12
- secretNamespace: {{ .Values.infisical.operatorSecretNamespace | quote }}
13
- secretsScope:
14
- envSlug: {{ .Values.infisical.env | quote }}
15
- projectSlug: {{ .Values.infisical.project | quote }}
16
- secretsPath: /
17
- hostAPI: {{ .Values.infisical.url | quote }}
18
- managedSecretReference:
19
- creationPolicy: Owner
20
- secretName: {{ include "name" $ }}-secs
21
- secretNamespace: {{ .Release.Namespace | quote }}
22
- secretType: Opaque
23
- resyncInterval: {{ .Values.infisical.resyncInterval }}
24
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/ingress-internal.yaml DELETED
@@ -1,32 +0,0 @@
1
- {{- if $.Values.ingressInternal.enabled }}
2
- apiVersion: networking.k8s.io/v1
3
- kind: Ingress
4
- metadata:
5
- annotations: {{ toYaml .Values.ingressInternal.annotations | nindent 4 }}
6
- labels: {{ include "labels.standard" . | nindent 4 }}
7
- name: {{ include "name" . }}-internal
8
- namespace: {{ .Release.Namespace }}
9
- spec:
10
- {{ if $.Values.ingressInternal.className }}
11
- ingressClassName: {{ .Values.ingressInternal.className }}
12
- {{ end }}
13
- {{- with .Values.ingressInternal.tls }}
14
- tls:
15
- - hosts:
16
- - {{ $.Values.domain | quote }}
17
- {{- with .secretName }}
18
- secretName: {{ . }}
19
- {{- end }}
20
- {{- end }}
21
- rules:
22
- - host: {{ .Values.domain }}
23
- http:
24
- paths:
25
- - backend:
26
- service:
27
- name: {{ include "name" . }}
28
- port:
29
- name: http
30
- path: {{ $.Values.ingressInternal.path | default "/" }}
31
- pathType: Prefix
32
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/ingress.yaml DELETED
@@ -1,33 +0,0 @@
1
- {{- if $.Values.ingress.enabled }}
2
- apiVersion: networking.k8s.io/v1
3
- kind: Ingress
4
- metadata:
5
- annotations: {{ toYaml .Values.ingress.annotations | nindent 4 }}
6
- labels: {{ include "labels.standard" . | nindent 4 }}
7
- name: {{ include "name" . }}
8
- namespace: {{ .Release.Namespace }}
9
- spec:
10
- {{ if $.Values.ingress.className }}
11
- ingressClassName: {{ .Values.ingress.className }}
12
- {{ end }}
13
- {{- with .Values.ingress.tls }}
14
- tls:
15
- - hosts:
16
- - {{ $.Values.domain | quote }}
17
- {{- with .secretName }}
18
- secretName: {{ . }}
19
- {{- end }}
20
- {{- end }}
21
- rules:
22
- - host: {{ .Values.domain }}
23
- http:
24
- paths:
25
- - backend:
26
- service:
27
- name: {{ include "name" . }}
28
- port:
29
- name: http
30
- path: {{ $.Values.ingress.path | default "/" }}
31
- pathType: Prefix
32
-
33
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/network-policy.yaml DELETED
@@ -1,36 +0,0 @@
1
- {{- if $.Values.networkPolicy.enabled }}
2
- apiVersion: networking.k8s.io/v1
3
- kind: NetworkPolicy
4
- metadata:
5
- name: {{ include "name" . }}
6
- namespace: {{ .Release.Namespace }}
7
- spec:
8
- egress:
9
- - ports:
10
- - port: 53
11
- protocol: UDP
12
- to:
13
- - namespaceSelector:
14
- matchLabels:
15
- kubernetes.io/metadata.name: kube-system
16
- podSelector:
17
- matchLabels:
18
- k8s-app: kube-dns
19
- - to:
20
- {{- range $ip := .Values.networkPolicy.allowedBlocks }}
21
- - ipBlock:
22
- cidr: {{ $ip | quote }}
23
- {{- end }}
24
- - to:
25
- - ipBlock:
26
- cidr: 0.0.0.0/0
27
- except:
28
- - 10.0.0.0/8
29
- - 172.16.0.0/12
30
- - 192.168.0.0/16
31
- - 169.254.169.254/32
32
- podSelector:
33
- matchLabels: {{ include "labels.standard" . | nindent 6 }}
34
- policyTypes:
35
- - Egress
36
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/service-account.yaml DELETED
@@ -1,13 +0,0 @@
1
- {{- if and .Values.serviceAccount.enabled .Values.serviceAccount.create }}
2
- apiVersion: v1
3
- kind: ServiceAccount
4
- automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
5
- metadata:
6
- name: "{{ .Values.serviceAccount.name | default (include "name" .) }}"
7
- namespace: {{ .Release.Namespace }}
8
- labels: {{ include "labels.standard" . | nindent 4 }}
9
- {{- with .Values.serviceAccount.annotations }}
10
- annotations:
11
- {{- toYaml . | nindent 4 }}
12
- {{- end }}
13
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/service-monitor.yaml DELETED
@@ -1,17 +0,0 @@
1
- {{- if eq "true" $.Values.envVars.METRICS_ENABLED }}
2
- apiVersion: monitoring.coreos.com/v1
3
- kind: ServiceMonitor
4
- metadata:
5
- labels: {{ include "labels.standard" . | nindent 4 }}
6
- name: {{ include "name" . }}
7
- namespace: {{ .Release.Namespace }}
8
- spec:
9
- selector:
10
- matchLabels: {{ include "labels.standard" . | nindent 6 }}
11
- endpoints:
12
- - port: metrics
13
- path: /metrics
14
- interval: 10s
15
- scheme: http
16
- scrapeTimeout: 10s
17
- {{- end }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/templates/service.yaml DELETED
@@ -1,21 +0,0 @@
1
- apiVersion: v1
2
- kind: Service
3
- metadata:
4
- name: "{{ include "name" . }}"
5
- annotations: {{ toYaml .Values.service.annotations | nindent 4 }}
6
- namespace: {{ .Release.Namespace }}
7
- labels: {{ include "labels.standard" . | nindent 4 }}
8
- spec:
9
- ports:
10
- - name: http
11
- port: 80
12
- protocol: TCP
13
- targetPort: http
14
- {{- if eq "true" $.Values.envVars.METRICS_ENABLED }}
15
- - name: metrics
16
- port: {{ $.Values.envVars.METRICS_PORT | default 5565 | int }}
17
- protocol: TCP
18
- targetPort: metrics
19
- {{- end }}
20
- selector: {{ include "labels.standard" . | nindent 4 }}
21
- type: {{.Values.service.type}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chart/values.yaml DELETED
@@ -1,81 +0,0 @@
1
- image:
2
- repository: registry.internal.huggingface.tech/deepsite
3
- name: deepsite
4
- tag: 0.0.0-latest
5
- pullPolicy: IfNotPresent
6
-
7
- replicas: 1
8
-
9
- domain: deepsite.hf.co
10
-
11
- networkPolicy:
12
- enabled: true
13
- allowedBlocks: []
14
- # allowedBlocks:
15
- # - 10.0.240.0/24
16
- # - 10.0.241.0/24
17
- # - 10.0.242.0/24
18
- # - 10.0.243.0/24
19
- # - 10.0.244.0/24
20
- # - 10.240.0.0/24
21
- # - 10.16.0.0/16
22
-
23
- service:
24
- type: NodePort
25
- annotations: { }
26
-
27
- serviceAccount:
28
- enabled: false
29
- create: false
30
- name: ""
31
- automountServiceAccountToken: true
32
- annotations: { }
33
-
34
- ingress:
35
- enabled: true
36
- path: "/"
37
- annotations: { }
38
- # className: "nginx"
39
- tls: { }
40
- # secretName: XXX
41
-
42
- ingressInternal:
43
- enabled: false
44
- path: "/"
45
- annotations: { }
46
- # className: "nginx"
47
- tls: { }
48
-
49
- resources:
50
- requests:
51
- cpu: 2
52
- memory: 4Gi
53
- limits:
54
- cpu: 2
55
- memory: 4Gi
56
- nodeSelector: {}
57
- tolerations: []
58
-
59
- envVars: { }
60
-
61
- infisical:
62
- enabled: false
63
- env: ""
64
- project: "deepsite-f-hvj"
65
- url: ""
66
- resyncInterval: 60
67
- operatorSecretName: "deepsite-operator-secrets"
68
- operatorSecretNamespace: "hub-utils"
69
-
70
- # Allow to environment injections on top or instead of infisical
71
- extraEnvFrom: []
72
- extraEnv: []
73
-
74
- autoscaling:
75
- enabled: false
76
- minReplicas: 1
77
- maxReplicas: 2
78
- targetMemoryUtilizationPercentage: ""
79
- targetCPUUtilizationPercentage: ""
80
-
81
- ## Metrics removed; monitoring configuration no longer used
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components.json CHANGED
@@ -5,12 +5,11 @@
5
  "tsx": true,
6
  "tailwind": {
7
  "config": "",
8
- "css": "app/globals.css",
9
- "baseColor": "zinc",
10
  "cssVariables": true,
11
  "prefix": ""
12
  },
13
- "iconLibrary": "lucide",
14
  "aliases": {
15
  "components": "@/components",
16
  "utils": "@/lib/utils",
@@ -18,5 +17,5 @@
18
  "lib": "@/lib",
19
  "hooks": "@/hooks"
20
  },
21
- "registries": {}
22
- }
 
5
  "tsx": true,
6
  "tailwind": {
7
  "config": "",
8
+ "css": "assets/globals.css",
9
+ "baseColor": "neutral",
10
  "cssVariables": true,
11
  "prefix": ""
12
  },
 
13
  "aliases": {
14
  "components": "@/components",
15
  "utils": "@/lib/utils",
 
17
  "lib": "@/lib",
18
  "hooks": "@/hooks"
19
  },
20
+ "iconLibrary": "lucide"
21
+ }
components/animated-blobs/index.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function AnimatedBlobs() {
2
+ return (
3
+ <div className="absolute inset-0 pointer-events-none -z-[1]">
4
+ <div
5
+ className="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 opacity-10 blur-3xl"
6
+ style={{
7
+ animation:
8
+ "liquidBlob1 4s ease-in-out infinite, liquidFlow1 6s ease-in-out infinite",
9
+ }}
10
+ />
11
+ <div
12
+ className="w-2/3 h-3/4 bg-gradient-to-r from-blue-500 to-teal-500 opacity-24 blur-3xl absolute -top-20 right-10"
13
+ style={{
14
+ animation:
15
+ "liquidBlob2 5s ease-in-out infinite, liquidFlow2 7s ease-in-out infinite",
16
+ }}
17
+ />
18
+ <div
19
+ className="w-1/2 h-1/2 bg-gradient-to-r from-amber-500 to-rose-500 opacity-20 blur-3xl absolute bottom-0 left-10"
20
+ style={{
21
+ animation:
22
+ "liquidBlob3 3.5s ease-in-out infinite, liquidFlow3 8s ease-in-out infinite",
23
+ }}
24
+ />
25
+ <div
26
+ className="w-48 h-48 bg-gradient-to-r from-cyan-500 to-indigo-500 opacity-20 blur-3xl absolute top-1/3 right-1/3"
27
+ style={{
28
+ animation:
29
+ "liquidBlob4 4.5s ease-in-out infinite, liquidFlow4 6.5s ease-in-out infinite",
30
+ }}
31
+ />
32
+ </div>
33
+ );
34
+ }
components/animated-text/index.tsx ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+
5
+ interface AnimatedTextProps {
6
+ className?: string;
7
+ }
8
+
9
+ export function AnimatedText({ className = "" }: AnimatedTextProps) {
10
+ const [displayText, setDisplayText] = useState("");
11
+ const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0);
12
+ const [isTyping, setIsTyping] = useState(true);
13
+ const [showCursor, setShowCursor] = useState(true);
14
+ const [lastTypedIndex, setLastTypedIndex] = useState(-1);
15
+ const [animationComplete, setAnimationComplete] = useState(false);
16
+
17
+ // Randomize suggestions on each component mount
18
+ const [suggestions] = useState(() => {
19
+ const baseSuggestions = [
20
+ "create a stunning portfolio!",
21
+ "build a tic tac toe game!",
22
+ "design a website for my restaurant!",
23
+ "make a sleek landing page!",
24
+ "build an e-commerce store!",
25
+ "create a personal blog!",
26
+ "develop a modern dashboard!",
27
+ "design a company website!",
28
+ "build a todo app!",
29
+ "create an online gallery!",
30
+ "make a contact form!",
31
+ "build a weather app!",
32
+ ];
33
+
34
+ // Fisher-Yates shuffle algorithm
35
+ const shuffled = [...baseSuggestions];
36
+ for (let i = shuffled.length - 1; i > 0; i--) {
37
+ const j = Math.floor(Math.random() * (i + 1));
38
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
39
+ }
40
+
41
+ return shuffled;
42
+ });
43
+
44
+ useEffect(() => {
45
+ if (animationComplete) return;
46
+
47
+ let timeout: NodeJS.Timeout;
48
+
49
+ const typeText = () => {
50
+ const currentSuggestion = suggestions[currentSuggestionIndex];
51
+
52
+ if (isTyping) {
53
+ if (displayText.length < currentSuggestion.length) {
54
+ setDisplayText(currentSuggestion.slice(0, displayText.length + 1));
55
+ setLastTypedIndex(displayText.length);
56
+ timeout = setTimeout(typeText, 80);
57
+ } else {
58
+ // Finished typing, wait then start erasing
59
+ setLastTypedIndex(-1);
60
+ timeout = setTimeout(() => {
61
+ setIsTyping(false);
62
+ }, 2000);
63
+ }
64
+ }
65
+ };
66
+
67
+ timeout = setTimeout(typeText, 100);
68
+ return () => clearTimeout(timeout);
69
+ }, [
70
+ displayText,
71
+ currentSuggestionIndex,
72
+ isTyping,
73
+ suggestions,
74
+ animationComplete,
75
+ ]);
76
+
77
+ // Cursor blinking effect
78
+ useEffect(() => {
79
+ if (animationComplete) {
80
+ setShowCursor(false);
81
+ return;
82
+ }
83
+
84
+ const cursorInterval = setInterval(() => {
85
+ setShowCursor((prev) => !prev);
86
+ }, 600);
87
+
88
+ return () => clearInterval(cursorInterval);
89
+ }, [animationComplete]);
90
+
91
+ useEffect(() => {
92
+ if (lastTypedIndex >= 0) {
93
+ const timeout = setTimeout(() => {
94
+ setLastTypedIndex(-1);
95
+ }, 400);
96
+
97
+ return () => clearTimeout(timeout);
98
+ }
99
+ }, [lastTypedIndex]);
100
+
101
+ return (
102
+ <p className={`font-mono ${className}`}>
103
+ Hey DeepSite,&nbsp;
104
+ {displayText.split("").map((char, index) => (
105
+ <span
106
+ key={`${currentSuggestionIndex}-${index}`}
107
+ className={`transition-colors duration-300 ${
108
+ index === lastTypedIndex ? "text-neutral-100" : ""
109
+ }`}
110
+ >
111
+ {char}
112
+ </span>
113
+ ))}
114
+ <span
115
+ className={`${
116
+ showCursor ? "opacity-100" : "opacity-0"
117
+ } transition-opacity`}
118
+ >
119
+ |
120
+ </span>
121
+ </p>
122
+ );
123
+ }
components/ask-ai/ask-ai-landing.tsx DELETED
@@ -1,75 +0,0 @@
1
- "use client";
2
- import { ArrowUp } from "lucide-react";
3
- import { useState } from "react";
4
- import { useLocalStorage, useMount } from "react-use";
5
- import { useRouter } from "next/navigation";
6
-
7
- import { Button } from "@/components/ui/button";
8
- import { ProviderType } from "@/lib/type";
9
- import { Models } from "./models";
10
- import { DEFAULT_MODEL } from "@/lib/providers";
11
- import { cn } from "@/lib/utils";
12
-
13
- export function AskAiLanding({ className }: { className?: string }) {
14
- const [model = DEFAULT_MODEL, setModel] = useLocalStorage<string>(
15
- "deepsite-model",
16
- DEFAULT_MODEL
17
- );
18
- const [provider, setProvider] = useLocalStorage<ProviderType>(
19
- "deepsite-provider",
20
- "auto" as ProviderType
21
- );
22
- const router = useRouter();
23
- const [prompt, setPrompt] = useState<string>("");
24
- const [mounted, setMounted] = useState<boolean>(false);
25
-
26
- useMount(() => {
27
- setMounted(true);
28
- });
29
-
30
- return (
31
- <div
32
- className={cn(
33
- "dark:bg-[#222222] bg-accent border border-border-muted rounded-xl p-3 block relative",
34
- className
35
- )}
36
- >
37
- <textarea
38
- id="prompt-input"
39
- className="w-full h-full resize-none outline-none text-primary text-sm"
40
- placeholder="Ask me anything..."
41
- value={prompt}
42
- onChange={(e) => setPrompt(e.target.value)}
43
- onKeyDown={(e) => {
44
- if (e.key === "Enter" && !e.shiftKey) {
45
- e.preventDefault();
46
- router.push(`/new?prompt=${prompt}`);
47
- }
48
- }}
49
- />
50
- <footer className="flex items-center justify-between">
51
- <div className="flex items-center gap-2">
52
- {mounted && (
53
- <Models
54
- model={model}
55
- setModel={setModel}
56
- provider={provider as ProviderType}
57
- setProvider={setProvider}
58
- />
59
- )}
60
- </div>
61
- <div>
62
- <Button
63
- size="icon-sm"
64
- className="rounded-full!"
65
- onClick={() => {
66
- router.push(`/new?prompt=${prompt}`);
67
- }}
68
- >
69
- <ArrowUp />
70
- </Button>
71
- </div>
72
- </footer>
73
- </div>
74
- );
75
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ask-ai/ask-ai.tsx DELETED
@@ -1,215 +0,0 @@
1
- "use client";
2
- import { ArrowUp, Paintbrush, X } from "lucide-react";
3
- import { FaHand } from "react-icons/fa6";
4
- import { useRef, useState } from "react";
5
- import { HiStop } from "react-icons/hi2";
6
- import { useLocalStorage, useMount, useUpdateEffect } from "react-use";
7
- import { useRouter } from "next/navigation";
8
- import { useNextStep } from "nextstepjs";
9
-
10
- import { Button } from "@/components/ui/button";
11
- import { cn } from "@/lib/utils";
12
- import { useGeneration } from "./useGeneration";
13
- import { File, MobileTabType, ProviderType } from "@/lib/type";
14
- import { Models } from "./models";
15
- import { DEFAULT_MODEL, MODELS } from "@/lib/providers";
16
- import { Redesign } from "./redesign";
17
- import { Uploader } from "./uploader";
18
- import { InputMentions } from "./input-mentions";
19
-
20
- export function AskAI({
21
- initialPrompt,
22
- className,
23
- onToggleMobileTab,
24
- files,
25
- medias,
26
- tourHasBeenShown,
27
- isNew = false,
28
- isHistoryView,
29
- projectName = "new",
30
- }: {
31
- initialPrompt?: string;
32
- className?: string;
33
- files?: File[] | null;
34
- medias?: string[] | null;
35
- tourHasBeenShown?: boolean;
36
- onToggleMobileTab?: (tab: MobileTabType) => void;
37
- isNew?: boolean;
38
- isHistoryView?: boolean;
39
- projectName?: string;
40
- }) {
41
- const contentEditableRef = useRef<HTMLDivElement | null>(null);
42
- const [model = DEFAULT_MODEL, setModel] = useLocalStorage<string>(
43
- "deepsite-model",
44
- DEFAULT_MODEL
45
- );
46
- const [provider, setProvider] = useLocalStorage<ProviderType>(
47
- "deepsite-provider",
48
- "auto" as ProviderType
49
- );
50
-
51
- const [prompt, setPrompt] = useState(initialPrompt ?? "");
52
- const [redesignMd, setRedesignMd] = useState<{
53
- md: string;
54
- url: string;
55
- } | null>(null);
56
- const [selectedMedias, setSelectedMedias] = useState<string[]>([]);
57
- const [imageLinks, setImageLinks] = useState<string[]>([]);
58
- const [startTour, setStartTour] = useState<boolean>(false);
59
-
60
- const router = useRouter();
61
- const { callAi, isLoading, stopGeneration, audio } =
62
- useGeneration(projectName);
63
- const { startNextStep } = useNextStep();
64
-
65
- const onComplete = () => {
66
- onToggleMobileTab?.("right-sidebar");
67
- };
68
-
69
- useMount(() => {
70
- if (initialPrompt && initialPrompt.trim() !== "" && isNew) {
71
- setTimeout(() => {
72
- if (isHistoryView) return;
73
- callAi(
74
- {
75
- prompt: initialPrompt,
76
- model,
77
- onComplete,
78
- provider,
79
- },
80
- setModel
81
- );
82
- router.replace("/new");
83
- }, 200);
84
- }
85
- });
86
-
87
- const onSubmit = () => {
88
- if (isHistoryView) return;
89
- if (contentEditableRef.current) {
90
- contentEditableRef.current.innerHTML = "";
91
- }
92
- callAi(
93
- {
94
- prompt,
95
- model,
96
- onComplete,
97
- provider,
98
- redesignMd,
99
- medias: [...(selectedMedias ?? []), ...(imageLinks ?? [])],
100
- },
101
- setModel
102
- );
103
- if (selectedMedias.length > 0) setSelectedMedias([]);
104
- if (imageLinks.length > 0) setImageLinks([]);
105
- if (redesignMd) setRedesignMd(null);
106
- };
107
-
108
- return (
109
- <div
110
- id="tour-ask-ai-section"
111
- className={cn(
112
- "dark:bg-[#222222] bg-accent border border-border-muted rounded-xl p-2.5 block relative",
113
- className
114
- )}
115
- >
116
- <InputMentions
117
- ref={contentEditableRef}
118
- files={files}
119
- prompt={prompt}
120
- setPrompt={setPrompt}
121
- redesignMdUrl={redesignMd?.url?.replace(/(^\w+:|^)\/\//, "")}
122
- onSubmit={onSubmit}
123
- imageLinks={imageLinks}
124
- setImageLinks={setImageLinks}
125
- />
126
- <footer className="flex items-center justify-between mt-0">
127
- <div className="flex items-center gap-1.5">
128
- {!tourHasBeenShown && (
129
- <div className="relative z-1">
130
- <Button
131
- variant="indigo"
132
- size="icon-xs"
133
- className="rounded-full!"
134
- onClick={() => {
135
- setStartTour(true);
136
- startNextStep("onboarding");
137
- }}
138
- >
139
- <FaHand className="size-3" />
140
- </Button>
141
- {!startTour && (
142
- <div className="animate-ping h-full rounded-full bg-indigo-500 w-full top-0 left-0 absolute -z-1" />
143
- )}
144
- </div>
145
- )}
146
- {!isNew && (
147
- <Uploader
148
- medias={medias}
149
- selected={selectedMedias}
150
- setSelected={setSelectedMedias}
151
- />
152
- )}
153
- <Models
154
- model={model}
155
- setModel={setModel}
156
- provider={provider as ProviderType}
157
- setProvider={setProvider}
158
- />
159
- {!files ||
160
- (files?.length === 0 &&
161
- (redesignMd ? (
162
- <Button
163
- size="xs"
164
- variant="indigo"
165
- className="rounded-full! px-2.5!"
166
- onClick={() => setRedesignMd(null)}
167
- >
168
- <Paintbrush className="size-3" />
169
- {redesignMd.url?.replace(/(^\w+:|^)\/\//, "")}
170
- <X className="size-3.5" onClick={() => setRedesignMd(null)} />
171
- </Button>
172
- ) : (
173
- <Redesign
174
- onRedesign={(md, url) => {
175
- setRedesignMd({
176
- md,
177
- url,
178
- });
179
- }}
180
- />
181
- )))}
182
- </div>
183
- <div>
184
- {isLoading ? (
185
- <Button
186
- size="icon-sm"
187
- className="rounded-full!"
188
- variant="bordered"
189
- onClick={stopGeneration}
190
- >
191
- <HiStop />
192
- </Button>
193
- ) : (
194
- <Button
195
- size="icon-sm"
196
- className="rounded-full!"
197
- disabled={
198
- isHistoryView ||
199
- isLoading ||
200
- (prompt.trim() === "" && !redesignMd)
201
- }
202
- onClick={onSubmit}
203
- >
204
- <ArrowUp />
205
- </Button>
206
- )}
207
- </div>
208
- </footer>
209
- <audio ref={audio} id="audio" className="hidden">
210
- <source src="/ding.mp3" type="audio/mpeg" />
211
- Your browser does not support the audio element.
212
- </audio>
213
- </div>
214
- );
215
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ask-ai/context.tsx DELETED
@@ -1,123 +0,0 @@
1
- import { AtSign, Braces, FileCode, FileText, X } from "lucide-react";
2
- import { useMemo, useState } from "react";
3
- import { useQueryClient } from "@tanstack/react-query";
4
-
5
- import {
6
- Popover,
7
- PopoverContent,
8
- PopoverTrigger,
9
- } from "@/components/ui/popover";
10
- import { Button } from "@/components/ui/button";
11
- import { File } from "@/lib/type";
12
- import { cn } from "@/lib/utils";
13
-
14
- export const Context = ({
15
- files,
16
- setFiles,
17
- }: {
18
- files: File[];
19
- setFiles: (files: File[]) => void;
20
- }) => {
21
- const queryClient = useQueryClient();
22
- const [open, setOpen] = useState(false);
23
-
24
- const getFileIcon = (filePath: string, size = "size-3.5") => {
25
- if (filePath.endsWith(".css")) {
26
- return <Braces className={size} />;
27
- } else if (filePath.endsWith(".js")) {
28
- return <FileCode className={size} />;
29
- } else if (filePath.endsWith(".json")) {
30
- return <Braces className={size} />;
31
- } else {
32
- return <FileText className={size} />;
33
- }
34
- };
35
-
36
- const getFiles = () => queryClient.getQueryData<File[]>(["files"]) ?? [];
37
-
38
- return (
39
- <div className="flex items-center justify-start gap-1 flex-wrap">
40
- <Popover open={open} onOpenChange={setOpen}>
41
- <PopoverTrigger asChild>
42
- <Button size="xxs" variant={open ? "default" : "bordered"}>
43
- <AtSign className="size-3" />
44
- Add Context...
45
- </Button>
46
- </PopoverTrigger>
47
- <PopoverContent
48
- align="start"
49
- className="translate-x-6 space-y-4 min-w-fit rounded-2xl! p-0!"
50
- >
51
- <main className="p-4">
52
- <p className="text-xs text-muted-foreground mb-2.5">
53
- Select a file to send as context
54
- </p>
55
- <div className="max-h-[200px] overflow-y-auto space-y-0.5">
56
- {getFiles().length === 0 ? (
57
- <div className="text-xs text-muted-foreground">
58
- No files available
59
- </div>
60
- ) : (
61
- <>
62
- <button
63
- onClick={() => {
64
- setFiles([]);
65
- setOpen(false);
66
- }}
67
- className={`cursor-pointer w-full px-2 py-1.5 text-xs text-left rounded-md hover:bg-accent hover:text-accent-foreground transition-colors ${
68
- files.length === 0
69
- ? "bg-linear-to-r from-indigo-500/20 to-indigo-500/5 text-primary font-medium"
70
- : "text-muted-foreground"
71
- }`}
72
- >
73
- All files (default)
74
- </button>
75
- {getFiles()?.map((page) => (
76
- <button
77
- key={page.path}
78
- onClick={() => {
79
- if (files.some((f) => f.path === page.path))
80
- setFiles(files.filter((f) => f.path !== page.path));
81
- else setFiles(files ? [...files, page] : [page]);
82
- setOpen(false);
83
- }}
84
- className={`cursor-pointer w-full px-2 py-1.5 text-xs text-left rounded-md hover:bg-accent hover:text-accent-foreground transition-colors flex items-center gap-1.5 ${
85
- files?.some((f) => f.path === page.path)
86
- ? "bg-linear-to-r from-indigo-500/20 to-indigo-500/5 text-primary font-medium"
87
- : "text-muted-foreground"
88
- }`}
89
- >
90
- <span className="shrink-0">
91
- {getFileIcon(page.path, "size-3")}
92
- </span>
93
- <span className="truncate flex-1">{page.path}</span>
94
- </button>
95
- ))}
96
- </>
97
- )}
98
- </div>
99
- </main>
100
- </PopoverContent>
101
- </Popover>
102
- {files?.map((file) => (
103
- <Button
104
- key={file.path}
105
- size="xxs"
106
- variant="bordered"
107
- className="cursor-default!"
108
- >
109
- {getFileIcon(file.path, "size-3")}
110
- {file.path}
111
- <span
112
- className="opacity-50 hover:opacity-80 cursor-pointer"
113
- onClick={() => {
114
- setFiles(files.filter((f) => f.path !== file.path));
115
- }}
116
- >
117
- <X className="size-3.5 opacity-50 hover:opacity-80 shrink-0" />
118
- </span>
119
- </Button>
120
- ))}
121
- </div>
122
- );
123
- };