monorepotime 1.1.12 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -55247,7 +55247,7 @@ __export(index_exports, {
55247
55247
  io: () => io2
55248
55248
  });
55249
55249
  module.exports = __toCommonJS(index_exports);
55250
- var import_express24 = __toESM(require_express2());
55250
+ var import_express25 = __toESM(require_express2());
55251
55251
  var import_cors = __toESM(require_lib3());
55252
55252
  var import_path19 = __toESM(require("path"));
55253
55253
 
@@ -56729,12 +56729,33 @@ var EXCLUDE_PATTERNS = {
56729
56729
  "**/turbo.json": true,
56730
56730
  "**/nodemon.json": true,
56731
56731
  "**/temp.md": true,
56732
- "**/*postcss*": true,
56733
- "**/*tailwind*": true,
56734
- "**/*tsconfig*": true,
56735
- "**/*eslint*": true,
56736
- "**/*prettier*": true,
56737
- "**/*vite*": true,
56732
+ "**/postcss.config.js": true,
56733
+ "**/configs": true,
56734
+ "**/tailwind.config.js": true,
56735
+ "**/tsconfig.app.json": true,
56736
+ "**/tsconfig.json": true,
56737
+ "**/tsconfig.node.json": true,
56738
+ "**/tsconfig.spec.json": true,
56739
+ "**/eslint.json": true,
56740
+ "**/eslint.config.js": true,
56741
+ "**/eslint.config.mjs": true,
56742
+ "**/prettier.config.js": true,
56743
+ "**/prettier.config.mjs": true,
56744
+ "**/prettier.config.cjs": true,
56745
+ "**/vite.config.ts": true,
56746
+ "**/vite.config.js": true,
56747
+ "**/tsup.config.ts": true,
56748
+ "**/rollup.config.ts": true,
56749
+ "**/rollup.config.js": true,
56750
+ "**/webpack.config.js": true,
56751
+ "**/babel.config.js": true,
56752
+ "**/jest.config.js": true,
56753
+ "**/jest.config.ts": true,
56754
+ "**/next.config.js": true,
56755
+ "**/next.config.mjs": true,
56756
+ "**/postcss.config.cjs": true,
56757
+ "**/postcss.config.mjs": true,
56758
+ "**/tailwind.config.ts": true,
56738
56759
  "_temp": true,
56739
56760
  ".gitignore": true,
56740
56761
  ".vscode": true,
@@ -57624,7 +57645,7 @@ router17.post("/stop-all", async (req, res) => {
57624
57645
  var apidocker_default = router17;
57625
57646
 
57626
57647
  // src/routes/availabletemplates.ts
57627
- var import_express21 = __toESM(require_express2());
57648
+ var import_express22 = __toESM(require_express2());
57628
57649
 
57629
57650
  // ../../packages/template/databases/mysql.ts
57630
57651
  var MySQL = {
@@ -57666,6 +57687,11 @@ const EDITOR_URL = 'http://localhost/phpmyadmin'; // Change this to your preferr
57666
57687
  cmd: "npm",
57667
57688
  args: ["pkg", "set", "scripts.stop=echo 'Note: MySQL is running as a system service. Please stop it manually.'"]
57668
57689
  },
57690
+ {
57691
+ action: "command",
57692
+ cmd: "npm",
57693
+ args: ["pkg", "set", "description=MySQL (Local)"]
57694
+ },
57669
57695
  {
57670
57696
  action: "command",
57671
57697
  cmd: "npm",
@@ -57861,7 +57887,12 @@ process.on('SIGTERM', cleanup);`
57861
57887
  {
57862
57888
  action: "command",
57863
57889
  cmd: "npm",
57864
- args: ["pkg", "set", "fontawesomeIcon=fas fa-database text-blue-300"]
57890
+ args: ["pkg", "set", "description=PostgreSQL (Docker)"]
57891
+ },
57892
+ {
57893
+ action: "command",
57894
+ cmd: "npm",
57895
+ args: ["pkg", "set", "fontawesomeIcon=fas fa-database text-blue-500"]
57865
57896
  }
57866
57897
  ]
57867
57898
  };
@@ -57895,7 +57926,12 @@ var Supabase = {
57895
57926
  {
57896
57927
  action: "command",
57897
57928
  cmd: "npm",
57898
- args: ["pkg", "set", "fontawesomeIcon=fas fa-bolt text-green-400"]
57929
+ args: ["pkg", "set", "description=Supabase (Docker)"]
57930
+ },
57931
+ {
57932
+ action: "command",
57933
+ cmd: "npm",
57934
+ args: ["pkg", "set", "fontawesomeIcon=fas fa-bolt text-green-500"]
57899
57935
  }
57900
57936
  ]
57901
57937
  };
@@ -58050,7 +58086,12 @@ process.on('SIGTERM', cleanup);`
58050
58086
  {
58051
58087
  action: "command",
58052
58088
  cmd: "npm",
58053
- args: ["pkg", "set", "fontawesomeIcon=fas fa-server text-red-400"]
58089
+ args: ["pkg", "set", "description=Redis (Docker)"]
58090
+ },
58091
+ {
58092
+ action: "command",
58093
+ cmd: "npm",
58094
+ args: ["pkg", "set", "fontawesomeIcon=fas fa-server text-red-500"]
58054
58095
  }
58055
58096
  ]
58056
58097
  };
@@ -58205,6 +58246,11 @@ process.on('SIGTERM', cleanup);`
58205
58246
  cmd: "npm",
58206
58247
  args: ["pkg", "set", `scripts.stop=node -e 'const fs=require("fs"); try{const p=JSON.parse(fs.readFileSync(".runtime.json")).port; fetch("http://localhost:"+p+"/stop").catch(e=>{})}catch(e){}'`]
58207
58248
  },
58249
+ {
58250
+ action: "command",
58251
+ cmd: "npm",
58252
+ args: ["pkg", "set", "description=MongoDB (Docker)"]
58253
+ },
58208
58254
  {
58209
58255
  action: "command",
58210
58256
  cmd: "npm",
@@ -58362,6 +58408,11 @@ process.on('SIGTERM', cleanup);`
58362
58408
  cmd: "npm",
58363
58409
  args: ["pkg", "set", "fontawesomeIcon=fab fa-meetup text-green-500"]
58364
58410
  },
58411
+ {
58412
+ action: "command",
58413
+ cmd: "npm",
58414
+ args: ["pkg", "set", "description=Meilisearch (Docker)"]
58415
+ },
58365
58416
  {
58366
58417
  action: "command",
58367
58418
  cmd: "npm",
@@ -58537,6 +58588,11 @@ process.on('SIGTERM', cleanup);`
58537
58588
  cmd: "npm",
58538
58589
  args: ["pkg", "set", "fontawesomeIcon=fab fa-amazon text-blue-500"]
58539
58590
  },
58591
+ {
58592
+ action: "command",
58593
+ cmd: "npm",
58594
+ args: ["pkg", "set", "description=MinIO (Docker)"]
58595
+ },
58540
58596
  {
58541
58597
  action: "command",
58542
58598
  cmd: "npm",
@@ -58557,1552 +58613,315 @@ var templates = [
58557
58613
  ];
58558
58614
  var database_default = templates;
58559
58615
 
58560
- // ../../packages/template/demo/aichat/files/indexHtml.ts
58561
- var indexHTML = `<!DOCTYPE html>
58562
- <html lang="en">
58563
- <head>
58564
- <meta charset="UTF-8">
58565
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
58566
- <title>FreshFruit - Premium Organic Fruits</title>
58567
- <meta name="description" content="Fresh organic fruits delivered to your doorstep. Premium quality, farm-to-table freshness.">
58568
- <link rel="preconnect" href="https://fonts.googleapis.com">
58569
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
58570
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
58571
- <link rel="stylesheet" href="/styles.css">
58572
- </head>
58573
- <body>
58574
- <!-- Header -->
58575
- <header class="header">
58576
- <div class="header-content">
58577
- <div class="logo">\u{1F34A} FreshFruit</div>
58578
- <nav>
58579
- <ul class="nav-links">
58580
- <li><a href="#products">Products</a></li>
58581
- <li><a href="#about">About</a></li>
58582
- <li><a href="#contact">Contact</a></li>
58583
- </ul>
58584
- </nav>
58585
- </div>
58586
- </header>
58587
-
58588
- <!-- Hero Section -->
58589
- <section class="hero">
58590
- <h1>Fresh Organic Fruits Delivered Daily</h1>
58591
- <p>Experience the finest selection of farm-fresh fruits, handpicked and delivered straight to your doorstep. Taste the difference of truly organic produce.</p>
58592
- <button class="cta-button">Shop Now</button>
58593
- </section>
58594
-
58595
- <!-- Products Section -->
58596
- <section class="products" id="products">
58597
- <h2>Our Fresh Selection</h2>
58598
- <div class="products-grid">
58599
- <div class="product-card">
58600
- <div class="product-image">\u{1F34E}</div>
58601
- <div class="product-info">
58602
- <h3>Organic Apples</h3>
58603
- <p>Crisp and sweet, straight from our partner orchards</p>
58604
- <span class="product-price">$4.99/lb</span>
58605
- </div>
58606
- </div>
58607
- <div class="product-card">
58608
- <div class="product-image">\u{1F34A}</div>
58609
- <div class="product-info">
58610
- <h3>Valencia Oranges</h3>
58611
- <p>Juicy and vitamin-packed, perfect for fresh juice</p>
58612
- <span class="product-price">$5.49/lb</span>
58613
- </div>
58614
- </div>
58615
- <div class="product-card">
58616
- <div class="product-image">\u{1F347}</div>
58617
- <div class="product-info">
58618
- <h3>Premium Grapes</h3>
58619
- <p>Seedless and bursting with natural sweetness</p>
58620
- <span class="product-price">$6.99/lb</span>
58621
- </div>
58622
- </div>
58623
- <div class="product-card">
58624
- <div class="product-image">\u{1F96D}</div>
58625
- <div class="product-info">
58626
- <h3>Alphonso Mangoes</h3>
58627
- <p>The king of fruits, rich and aromatic</p>
58628
- <span class="product-price">$8.99/lb</span>
58629
- </div>
58630
- </div>
58631
- <div class="product-card">
58632
- <div class="product-image">\u{1F353}</div>
58633
- <div class="product-info">
58634
- <h3>Fresh Strawberries</h3>
58635
- <p>Hand-picked at peak ripeness for maximum flavor</p>
58636
- <span class="product-price">$7.49/lb</span>
58637
- </div>
58638
- </div>
58639
- <div class="product-card">
58640
- <div class="product-image">\u{1F34C}</div>
58641
- <div class="product-info">
58642
- <h3>Organic Bananas</h3>
58643
- <p>Naturally ripened, perfect for smoothies</p>
58644
- <span class="product-price">$2.99/lb</span>
58645
- </div>
58646
- </div>
58647
- </div>
58648
- </section>
58649
-
58650
- <!-- Chat Widget -->
58651
- <div class="chat-widget">
58652
- <div class="chat-window" id="chatWindow">
58653
- <div class="chat-header">
58654
- <div class="chat-header-avatar">\u{1F34A}</div>
58655
- <div class="chat-header-info">
58656
- <h4>FreshFruit Support</h4>
58657
- <span>We typically reply instantly</span>
58658
- </div>
58659
- </div>
58660
- <div class="chat-messages" id="chatMessages">
58661
- <div class="message bot">
58662
- Hi! \u{1F44B} Welcome to FreshFruit! How can I help you today? I can answer questions about our products, delivery, or anything else!
58663
- </div>
58664
- </div>
58665
- <div class="chat-input-container">
58666
- <input type="text" class="chat-input" id="chatInput" placeholder="Type your message..." autocomplete="off">
58667
- <button class="chat-send" id="chatSend">
58668
- <svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
58669
- </button>
58670
- </div>
58671
- </div>
58672
- <button class="chat-toggle" id="chatToggle">
58673
- <svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
58674
- </button>
58675
- </div>
58676
-
58677
- <script>
58678
- // Chat Widget Functionality
58679
- const chatToggle = document.getElementById('chatToggle');
58680
- const chatWindow = document.getElementById('chatWindow');
58681
- const chatInput = document.getElementById('chatInput');
58682
- const chatSend = document.getElementById('chatSend');
58683
- const chatMessages = document.getElementById('chatMessages');
58684
-
58685
- // Toggle chat window
58686
- chatToggle.addEventListener('click', () => {
58687
- chatWindow.classList.toggle('open');
58688
- if (chatWindow.classList.contains('open')) {
58689
- chatInput.focus();
58690
- }
58691
- });
58692
-
58693
- // Send message function
58694
- async function sendMessage() {
58695
- const message = chatInput.value.trim();
58696
- if (!message) return;
58697
-
58698
- // Add user message
58699
- addMessage(message, 'user');
58700
- chatInput.value = '';
58701
-
58702
- // Show typing indicator
58703
- const typingEl = showTyping();
58704
-
58705
- try {
58706
- const response = await fetch('/api/chat', {
58707
- method: 'POST',
58708
- headers: { 'Content-Type': 'application/json' },
58709
- body: JSON.stringify({ message })
58710
- });
58711
-
58712
- const data = await response.json();
58713
- typingEl.remove();
58714
-
58715
- if (data.reply) {
58716
- addMessage(data.reply, 'bot');
58717
- } else {
58718
- addMessage('Sorry, I encountered an error. Please try again.', 'bot');
58719
- }
58720
- } catch (error) {
58721
- typingEl.remove();
58722
- addMessage('Sorry, I\\'m having trouble connecting. Please try again later.', 'bot');
58723
- }
58724
- }
58616
+ // ../../packages/template/demo/monochat.ts
58617
+ var MonoChat = {
58618
+ name: "Chat To MonoChat",
58619
+ description: "React Frontend, needs custom backend",
58620
+ notes: "Vite React + TailwindCSS + TypeScript",
58621
+ templating: [
58622
+ {
58623
+ action: "command",
58624
+ cmd: "rm -rf ./* ./.[!.]*",
58625
+ args: []
58626
+ },
58627
+ {
58628
+ action: "file",
58629
+ file: ".gitignore",
58630
+ filecontent: "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
58631
+ },
58632
+ {
58633
+ action: "file",
58634
+ file: "README.md",
58635
+ filecontent: "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh\n- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh\n\n## React Compiler\n\nThe React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).\n\n## Expanding the ESLint configuration\n\nIf you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:\n\n```js\nexport default defineConfig([\n globalIgnores(['dist']),\n {\n files: ['**/*.{ts,tsx}'],\n extends: [\n // Other configs...\n\n // Remove tseslint.configs.recommended and replace with this\n tseslint.configs.recommendedTypeChecked,\n // Alternatively, use this for stricter rules\n tseslint.configs.strictTypeChecked,\n // Optionally, add this for stylistic rules\n tseslint.configs.stylisticTypeChecked,\n\n // Other configs...\n ],\n languageOptions: {\n parserOptions: {\n project: ['./tsconfig.node.json', './tsconfig.app.json'],\n tsconfigRootDir: import.meta.dirname,\n },\n // other options...\n },\n },\n])\n```\n\nYou can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:\n\n```js\n// eslint.config.js\nimport reactX from 'eslint-plugin-react-x'\nimport reactDom from 'eslint-plugin-react-dom'\n\nexport default defineConfig([\n globalIgnores(['dist']),\n {\n files: ['**/*.{ts,tsx}'],\n extends: [\n // Other configs...\n // Enable lint rules for React\n reactX.configs['recommended-typescript'],\n // Enable lint rules for React DOM\n reactDom.configs.recommended,\n ],\n languageOptions: {\n parserOptions: {\n project: ['./tsconfig.node.json', './tsconfig.app.json'],\n tsconfigRootDir: import.meta.dirname,\n },\n // other options...\n },\n },\n])\n```\n"
58636
+ },
58637
+ {
58638
+ action: "file",
58639
+ file: "eslint.config.js",
58640
+ filecontent: "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reactRefresh from 'eslint-plugin-react-refresh'\nimport tseslint from 'typescript-eslint'\nimport { defineConfig, globalIgnores } from 'eslint/config'\n\nexport default defineConfig([\n globalIgnores(['dist']),\n {\n files: ['**/*.{ts,tsx}'],\n extends: [\n js.configs.recommended,\n tseslint.configs.recommended,\n reactHooks.configs.flat.recommended,\n reactRefresh.configs.vite,\n ],\n languageOptions: {\n ecmaVersion: 2020,\n globals: globals.browser,\n },\n },\n])\n"
58641
+ },
58642
+ {
58643
+ action: "file",
58644
+ file: "index.html",
58645
+ filecontent: '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <link rel="icon" type="image/svg+xml" href="/logo.svg" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <title>MonoChat</title>\n </head>\n <body>\n <div id="root"></div>\n <script type="module" src="/src/main.tsx"></script>\n </body>\n</html>\n'
58646
+ },
58647
+ {
58648
+ action: "file",
58649
+ file: "netlify.toml",
58650
+ filecontent: '[build]\n command = "npm run build"\n publish = "dist"\n\n[[redirects]]\n from = "/*"\n to = "/index.html"\n status = 200'
58651
+ },
58652
+ {
58653
+ action: "file",
58654
+ file: "package.json",
58655
+ filecontent: '{\n "name": "z-chat",\n "private": true,\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "tsc -b && vite build",\n "lint": "eslint .",\n "preview": "vite preview",\n "stop": ""\n },\n "dependencies": {\n "react": "^19.2.0",\n "react-dom": "^19.2.0",\n "zustand": "^4.5.7"\n },\n "devDependencies": {\n "@eslint/js": "^9.39.1",\n "@tailwindcss/postcss": "^4.0.0",\n "@types/node": "^24.10.1",\n "@types/react": "^19.2.5",\n "@types/react-dom": "^19.2.3",\n "@vitejs/plugin-react": "^5.1.1",\n "autoprefixer": "^10.4.20",\n "eslint": "^9.39.1",\n "eslint-plugin-react-hooks": "^7.0.1",\n "eslint-plugin-react-refresh": "^0.4.24",\n "globals": "^16.5.0",\n "jiti": "^2.4.2",\n "postcss": "^8.5.1",\n "tailwindcss": "^4.0.0",\n "typescript": "~5.9.3",\n "typescript-eslint": "^8.46.4",\n "vite": "^7.2.4"\n },\n "description": "Vite React TS",\n "fontawesomeIcon": "fab fa-react text-blue-500"\n}\n'
58656
+ },
58657
+ {
58658
+ action: "file",
58659
+ file: "postcss.config.js",
58660
+ filecontent: 'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n autoprefixer: {},\n },\n}'
58661
+ },
58662
+ {
58663
+ action: "file",
58664
+ file: "public/logo.svg",
58665
+ filecontent: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">\n <defs>\n <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">\n <stop offset="0%" style="stop-color:#6366f1;stop-opacity:1" />\n <stop offset="100%" style="stop-color:#a855f7;stop-opacity:1" />\n </linearGradient>\n </defs>\n <rect width="512" height="512" rx="128" fill="url(#grad)" />\n <path d="M375.3 325.7c22.1-24.9 36.7-56.3 36.7-91.7c0-79.5-72.7-144-162-144S88 154.5 88 234s72.7 144 162 144c20.3 0 39.7-3.4 57.6-9.6L368 400l-45.7-31.3c18.5-12.7 34.6-28 47-45.7v2.7z" fill="white" />\n <circle cx="178" cy="234" r="20" fill="#6366f1"/>\n <circle cx="250" cy="234" r="20" fill="#6366f1"/>\n <circle cx="322" cy="234" r="20" fill="#6366f1"/>\n</svg>\n'
58666
+ },
58667
+ {
58668
+ action: "file",
58669
+ file: "render.yaml",
58670
+ filecontent: "services:\n - type: web\n name: vite-react-app\n env: static\n buildCommand: npm install && npm run build\n staticPublishPath: ./dist"
58671
+ },
58672
+ {
58673
+ action: "file",
58674
+ file: "src/App.tsx",
58675
+ filecontent: "import Background from './components/Background';\nimport ChatContainer from './components/ChatContainer';\nimport Header from './components/Header';\n\nexport default function App() {\n return (\n <div className={`\n h-[100vh]\n w-[100vw]\n overflow-hidden\n bg-zinc-900 \n `}>\n <div className='w-full h-full'>\n <Background />\n <Header />\n <ChatContainer />\n </div>\n </div>\n );\n}"
58676
+ },
58677
+ {
58678
+ action: "file",
58679
+ file: "src/_FetchToWho.ts",
58680
+ filecontent: 'import type { ChatItem } from "./app/chat";\n\n//@ts-ignore\nexport default async function FetchToWho( chats: ChatItem[]) {\n // const lastChat = chats[chats.length - 1];\n // if (!lastChat) return;\n // const response = await fetch("https://api.openai.com/v1/chat/completions", {\n // method: "POST",\n // headers: {\n // "Content-Type": "application/json",\n // "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`\n // },\n // body: JSON.stringify({\n // model: "gpt-3.5-turbo",\n // messages: [\n // { role: "system", content: "You are a helpful assistant." },\n // { role: "user", content: lastChat.message }\n // ]\n // })\n // });\n // const data = await response.json();\n // return data.choices[0].message.content;\n\n const lorem = "Please edit the FetchToWho function. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.";\n return lorem;\n}\n'
58681
+ },
58682
+ {
58683
+ action: "file",
58684
+ file: "src/app/chat.ts",
58685
+ filecontent: `import { create } from 'zustand';
58686
+ import { createSelectors } from './zustandSelector';
58725
58687
 
58726
- // Add message to chat
58727
- function addMessage(text, type) {
58728
- const messageEl = document.createElement('div');
58729
- messageEl.className = 'message ' + type;
58730
- messageEl.textContent = text;
58731
- chatMessages.appendChild(messageEl);
58732
- chatMessages.scrollTop = chatMessages.scrollHeight;
58733
- }
58688
+ export interface ChatItem {
58689
+ id: number;
58690
+ who: "user" | "system";
58691
+ timestamp: number;
58692
+ message: string;
58693
+ }
58734
58694
 
58735
- // Show typing indicator
58736
- function showTyping() {
58737
- const typingEl = document.createElement('div');
58738
- typingEl.className = 'typing-indicator';
58739
- typingEl.innerHTML = '<span></span><span></span><span></span>';
58740
- chatMessages.appendChild(typingEl);
58741
- chatMessages.scrollTop = chatMessages.scrollHeight;
58742
- return typingEl;
58743
- }
58695
+ interface chatContext {
58696
+ chats: Array<ChatItem>;
58697
+ textinput: string;
58698
+ addChat: (chat: ChatItem) => void;
58699
+ setTextinput: (textinput: string) => void;
58700
+ }
58744
58701
 
58745
- // Event listeners
58746
- chatSend.addEventListener('click', sendMessage);
58747
- chatInput.addEventListener('keypress', (e) => {
58748
- if (e.key === 'Enter') sendMessage();
58749
- });
58750
- </script>
58751
- </body>
58752
- </html>
58753
- `;
58702
+ const chatstate = create<chatContext>()((set) => ({
58703
+ chats: [],
58704
+ textinput: "",
58705
+ addChat: (chat: ChatItem) => set((state) => ({
58706
+ chats: [...state.chats, chat]
58707
+ })),
58708
+ setTextinput: (textinput: string) => set(() => ({
58709
+ textinput: textinput
58710
+ }))
58754
58711
 
58755
- // ../../packages/template/demo/aichat/files/adminHtml.ts
58756
- var adminHTML = `<!DOCTYPE html>
58757
- <html lang="en">
58758
- <head>
58759
- <meta charset="UTF-8">
58760
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
58761
- <title>Admin Panel - AI Chat Configuration</title>
58762
- <link rel="preconnect" href="https://fonts.googleapis.com">
58763
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
58764
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
58765
- <link rel="stylesheet" href="/styles.css">
58766
- </head>
58767
- <body>
58768
- <header class="header">
58769
- <div class="header-content">
58770
- <div class="logo">\u{1F34A} FreshFruit Admin</div>
58771
- <nav>
58772
- <ul class="nav-links">
58773
- <li><a href="/">View Store</a></li>
58774
- </ul>
58775
- </nav>
58776
- </div>
58777
- </header>
58712
+ }));
58778
58713
 
58779
- <div class="admin-container">
58780
- <div class="admin-header">
58781
- <h1>AI Chat Configuration</h1>
58782
- <p>Configure your AI provider and embed FAQ knowledge for customer support</p>
58783
- </div>
58714
+ const useChatState = createSelectors(chatstate);
58715
+ export default useChatState;
58784
58716
 
58785
- <!-- AI Provider Configuration -->
58786
- <div class="admin-card">
58787
- <h2>\u{1F511} AI Provider Settings</h2>
58788
- <form id="configForm">
58789
- <div class="form-group">
58790
- <label for="apiKey">API Key</label>
58791
- <input type="password" class="form-input" id="apiKey" placeholder="sk-..." autocomplete="off">
58792
- </div>
58793
- <div class="form-group">
58794
- <label for="providerUrl">Chat Completions URL</label>
58795
- <input type="text" class="form-input" id="providerUrl" placeholder="https://api.openai.com/v1/chat/completions">
58796
- </div>
58797
- <div class="form-group">
58798
- <label for="embeddingsUrl">Embeddings URL</label>
58799
- <input type="text" class="form-input" id="embeddingsUrl" placeholder="https://api.openai.com/v1/embeddings">
58800
- </div>
58801
- <div class="form-group">
58802
- <label for="model">Chat Model</label>
58803
- <input type="text" class="form-input" id="model" placeholder="gpt-3.5-turbo">
58804
- </div>
58805
- <div class="form-group">
58806
- <label for="embeddingsModel">Embeddings Model</label>
58807
- <input type="text" class="form-input" id="embeddingsModel" placeholder="text-embedding-3-small">
58717
+ `
58718
+ },
58719
+ {
58720
+ action: "file",
58721
+ file: "src/app/zustandSelector.ts",
58722
+ filecontent: "//from: https://docs.pmnd.rs/zustand/guides/auto-generating-selectors\nimport type { StoreApi, UseBoundStore } from 'zustand'\n\ntype WithSelectors<S> = S extends { getState: () => infer T }\n ? S & { use: { [K in keyof T]: () => T[K] } }\n : never\n\nexport const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(\n _store: S\n) => {\n let store = _store as WithSelectors<typeof _store>\n store.use = {}\n for (let k of Object.keys(store.getState())) {\n ;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])\n }\n\n return store\n}\n"
58723
+ },
58724
+ {
58725
+ action: "file",
58726
+ file: "src/components/Background.tsx",
58727
+ filecontent: 'export default function Background() {\n return (\n <>\n <div className="absolute -top-[25%] -left-[10%] w-[80%] h-[80%] bg-blue-800/10 rounded-full blur-[128px]"></div>\n <div className="absolute bottom-[0%] right-[0%] w-[80%] h-[40%] bg-purple-800/10 rounded-full blur-[128px]"></div>\n </>\n )\n}'
58728
+ },
58729
+ {
58730
+ action: "file",
58731
+ file: "src/components/ChatContainer.tsx",
58732
+ filecontent: `import useChatState from '../app/chat';
58733
+ import TextInput from './TextInput';
58734
+ import ChatContents from './ChatContents';
58735
+
58736
+ export default function ChatContainer() {
58737
+ const chats = useChatState.use.chats();
58738
+
58739
+ return (
58740
+ <div className="flex flex-col h-[calc(100vh-6rem)] w-full max-w-[900px] mx-auto relative font-sans">
58741
+ {chats.length > 0 ? (
58742
+ <>
58743
+ <ChatContents />
58744
+ <TextInput />
58745
+ </>
58746
+ ) : (
58747
+ <div className="flex flex-col items-center justify-center h-full w-full p-4">
58748
+ <div className="flex gap-4 items-center justify-center">
58749
+ <div className="w-10 h-10 mb-8">
58750
+ <img src="/logo.svg" alt="MonoChat Logo" className="w-full h-full object-contain" />
58751
+ </div>
58752
+ <h2 className="text-xl font-bold text-white mb-8">How can I help you today?</h2>
58753
+ </div>
58754
+ <TextInput />
58808
58755
  </div>
58809
- <button type="submit" class="btn">\u{1F4BE} Save Configuration</button>
58810
- </form>
58756
+ )}
58811
58757
  </div>
58758
+ );
58759
+ }
58812
58760
 
58813
- <!-- Knowledge Base Embedding -->
58814
- <div class="admin-card">
58815
- <h2>\u{1F4DA} Knowledge Base</h2>
58816
- <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
58817
- Enter your FAQ content below. Each paragraph will be embedded and used to answer customer questions.
58818
- </p>
58819
- <form id="embedForm">
58820
- <div class="form-group">
58821
- <label for="faqContent">FAQ Content</label>
58822
- <textarea class="form-input" id="faqContent" placeholder="Enter your FAQ content here...
58823
-
58824
- Example:
58825
- Q: What are your delivery hours?
58826
- A: We deliver from 8 AM to 8 PM, Monday through Saturday.
58827
-
58828
- Q: How do I return a product?
58829
- A: You can return any product within 24 hours of delivery if you're not satisfied. Contact our support team.
58761
+ `
58762
+ },
58763
+ {
58764
+ action: "file",
58765
+ file: "src/components/ChatContents.tsx",
58766
+ filecontent: "import { useEffect, useRef } from \"react\";\nimport useChatState from \"../app/chat\";\n\nexport default function ChatContents() {\n const chats = useChatState.use.chats();\n const messagesEndRef = useRef<HTMLDivElement>(null);\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [chats]);\n\n return (\n <div className=\"flex-1 overflow-y-auto w-full px-4 scrollbar-hide\">\n {chats.map((chat) => (\n <div\n key={chat.id}\n className={`group flex w-full ${chat.who === 'user' ? 'justify-end' : 'justify-start'}`}\n >\n <div className={`flex max-w-[85%] md:max-w-[80%] gap-4 ${chat.who === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>\n <div className={`flex flex-col ${chat.who === 'user' ? 'items-end' : 'items-start'}`}>\n <div className={`flex items-center gap-2 mb-1.5 opacity-90 ${chat.who === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>\n <span className=\"text-[10px] text-zinc-500\">\n {new Date(chat.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}\n </span>\n </div>\n <div\n className={`relative px-5 py-3 rounded-2xl text-md leading-relaxed transition-transform duration-200 ${chat.who === 'user'\n ? 'bg-zinc-800/80 text-white rounded-tr-sm border border-zinc-700/50 shadow-lg backdrop-blur-sm'\n : ' text-zinc-100'\n }`}\n >\n {chat.message}\n </div>\n </div>\n </div>\n </div>\n ))}\n <div ref={messagesEndRef} className=\"h-4\" />\n </div>\n )\n}"
58767
+ },
58768
+ {
58769
+ action: "file",
58770
+ file: "src/components/Header.tsx",
58771
+ filecontent: '\nexport default function Header() {\n return (\n <div className="w-full h-12">\n <div className="container px-4 max-w-7xl mx-auto h-full flex items-center gap-3">\n <img src="/logo.svg" alt="MonoChat Logo" className="w-8 h-8" />\n <h1 className="font-bold text-lg tracking-wide text-white">\n MonoChat\n </h1>\n </div>\n </div>\n )\n}'
58772
+ },
58773
+ {
58774
+ action: "file",
58775
+ file: "src/components/SendIcon.tsx",
58776
+ filecontent: '\nexport default function SendIcon({ className }: { className?: string }) {\n return (\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className={className}>\n <path d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z" />\n </svg>\n )\n}'
58777
+ },
58778
+ {
58779
+ action: "file",
58780
+ file: "src/components/TextInput.tsx",
58781
+ filecontent: `import FetchToWho from "../_FetchToWho";
58782
+ import useChatState from "../app/chat";
58783
+ import SendIcon from "./SendIcon";
58784
+
58785
+ export default function TextInput() {
58786
+ const chats = useChatState.use.chats();
58787
+ const textinput = useChatState.use.textinput();
58788
+ const setTextinput = useChatState.use.setTextinput();
58789
+ const addChat = useChatState.use.addChat();
58790
+
58791
+ const handleSend = async () => {
58792
+ if (!textinput.trim()) return;
58793
+ addChat({
58794
+ id: Date.now(),
58795
+ who: "user",
58796
+ timestamp: Date.now(),
58797
+ message: textinput
58798
+ });
58799
+ setTextinput("");
58800
+
58801
+ const userChat = chats.filter((chat) => chat.who === "user");
58802
+ const response = await FetchToWho(userChat);
58803
+ addChat({
58804
+ id: Date.now(),
58805
+ who: "system",
58806
+ timestamp: Date.now(),
58807
+ message: response
58808
+ });
58809
+ };
58830
58810
 
58831
- Q: Do you offer organic certification?
58832
- A: Yes, all our products are certified organic by USDA."></textarea>
58833
- </div>
58834
- <button type="submit" class="btn" id="embedBtn">\u{1F52E} Embed Knowledge</button>
58835
- </form>
58836
- <div class="status-bar">
58837
- <div class="status-item">
58838
- <div class="value" id="embeddingCount">0</div>
58839
- <div class="label">Embeddings</div>
58840
- </div>
58841
- <div class="status-item">
58842
- <div class="value" id="lastUpdated">Never</div>
58843
- <div class="label">Last Updated</div>
58844
- </div>
58845
- <div class="status-item">
58846
- <div class="value" id="configStatus">\u274C</div>
58847
- <div class="label">API Configured</div>
58811
+ const handleKeyDown = (e: React.KeyboardEvent) => {
58812
+ if (e.key === 'Enter' && !e.shiftKey) {
58813
+ e.preventDefault();
58814
+ handleSend();
58815
+ }
58816
+ };
58817
+
58818
+ return (
58819
+ <div className="p-4 w-full">
58820
+ <div className="w-full max-w-3xl mx-auto">
58821
+ <div className="relative flex items-end gap-2 bg-zinc-900/80 hover:bg-zinc-900/90 focus-within:bg-black/90 transition-all duration-300 border border-white/10 rounded-[24px] p-2 pr-2 shadow-xl backdrop-blur-xl ring-1 ring-white/5 focus-within:ring-indigo-500/30">
58822
+ <textarea
58823
+ value={textinput}
58824
+ onChange={(e) => setTextinput(e.target.value)}
58825
+ onKeyDown={handleKeyDown}
58826
+ placeholder="Message MonoChat..."
58827
+ className="w-full pl-4 py-3 bg-transparent active:bg-transparent border-none outline-none focus:outline-none text-zinc-100 placeholder:text-zinc-500 focus:ring-0 resize-none max-h-48 min-h-[44px] scrollbar-hide text-md leading-6"
58828
+ rows={1}
58829
+ style={{ height: 'auto', minHeight: '44px' }}
58830
+ onInput={(e) => {
58831
+ const target = e.target as HTMLTextAreaElement;
58832
+ target.style.height = 'auto';
58833
+ target.style.height = \`\${Math.min(target.scrollHeight, 192)}px\`;
58834
+ }}
58835
+ />
58836
+ <button
58837
+ onClick={handleSend}
58838
+ disabled={!textinput.trim()}
58839
+ className={\`p-2 rounded-full mb-1 transition-all duration-200 flex-shrink-0 \${textinput.trim()
58840
+ ? 'bg-indigo-600 text-white shadow-lg shadow-indigo-500/20 hover:bg-indigo-500'
58841
+ : 'bg-zinc-800 text-zinc-600 cursor-not-allowed'
58842
+ }\`}
58843
+ >
58844
+ <SendIcon className="w-5 h-5" />
58845
+ </button>
58848
58846
  </div>
58849
58847
  </div>
58850
58848
  </div>
58849
+ )
58850
+ }
58851
+ `
58852
+ },
58853
+ {
58854
+ action: "file",
58855
+ file: "src/index.css",
58856
+ filecontent: '@import "tailwindcss";'
58857
+ },
58858
+ {
58859
+ action: "file",
58860
+ file: "src/main.tsx",
58861
+ filecontent: "import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n)"
58862
+ },
58863
+ {
58864
+ action: "file",
58865
+ file: "tailwind.config.js",
58866
+ filecontent: `/** @type {import('tailwindcss').Config} */
58867
+ export default {
58868
+ content: [
58869
+ "./index.html",
58870
+ "./src/**/*.{js,ts,jsx,tsx}",
58871
+ ],
58872
+ theme: {
58873
+ extend: {},
58874
+ },
58875
+ plugins: [],
58876
+ };`
58877
+ },
58878
+ {
58879
+ action: "file",
58880
+ file: "tsconfig.app.json",
58881
+ filecontent: '{\n "compilerOptions": {\n "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",\n "target": "ES2022",\n "useDefineForClassFields": true,\n "lib": ["ES2022", "DOM", "DOM.Iterable"],\n "module": "ESNext",\n "types": ["vite/client"],\n "skipLibCheck": true,\n\n /* Bundler mode */\n "moduleResolution": "bundler",\n "allowImportingTsExtensions": true,\n "verbatimModuleSyntax": true,\n "moduleDetection": "force",\n "noEmit": true,\n "jsx": "react-jsx",\n\n /* Linting */\n "strict": true,\n "noUnusedLocals": true,\n "noUnusedParameters": true,\n "erasableSyntaxOnly": true,\n "noFallthroughCasesInSwitch": true,\n "noUncheckedSideEffectImports": true\n },\n "include": ["src"]\n}\n'
58882
+ },
58883
+ {
58884
+ action: "file",
58885
+ file: "tsconfig.json",
58886
+ filecontent: '{\n "files": [],\n "references": [\n { "path": "./tsconfig.app.json" },\n { "path": "./tsconfig.node.json" }\n ]\n}\n'
58887
+ },
58888
+ {
58889
+ action: "file",
58890
+ file: "tsconfig.node.json",
58891
+ filecontent: '{\n "compilerOptions": {\n "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",\n "target": "ES2023",\n "lib": ["ES2023"],\n "module": "ESNext",\n "types": ["node"],\n "skipLibCheck": true,\n\n /* Bundler mode */\n "moduleResolution": "bundler",\n "allowImportingTsExtensions": true,\n "verbatimModuleSyntax": true,\n "moduleDetection": "force",\n "noEmit": true,\n\n /* Linting */\n "strict": true,\n "noUnusedLocals": true,\n "noUnusedParameters": true,\n "erasableSyntaxOnly": true,\n "noFallthroughCasesInSwitch": true,\n "noUncheckedSideEffectImports": true\n },\n "include": ["vite.config.ts"]\n}\n'
58892
+ },
58893
+ {
58894
+ action: "file",
58895
+ file: "vercel.json",
58896
+ filecontent: '{\n "rewrites": [\n {\n "source": "/(.*)",\n "destination": "/index.html"\n }\n ]\n}'
58897
+ },
58898
+ {
58899
+ action: "file",
58900
+ file: "vite.config.ts",
58901
+ filecontent: "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vite.dev/config/\nexport default defineConfig({\n plugins: [react()],\n})\n"
58902
+ },
58903
+ {
58904
+ action: "command",
58905
+ cmd: "npm",
58906
+ args: ["install"]
58907
+ },
58908
+ {
58909
+ action: "command",
58910
+ cmd: "npm",
58911
+ args: ["pkg", "set", "name=$(basename $PWD)"]
58912
+ }
58913
+ ]
58914
+ };
58915
+ var monochat_default = MonoChat;
58851
58916
 
58852
- <!-- Clear Data -->
58853
- <div class="admin-card">
58854
- <h2>\u{1F5D1}\uFE0F Data Management</h2>
58855
- <p style="color: var(--text-muted); margin-bottom: 1.5rem;">
58856
- Clear all embeddings to start fresh or to re-embed with new content.
58857
- </p>
58858
- <button class="btn btn-secondary" id="clearBtn">Clear All Embeddings</button>
58859
- </div>
58860
- </div>
58861
-
58862
- <div class="toast" id="toast"></div>
58863
-
58864
- <script>
58865
- // Toast notification
58866
- function showToast(message, duration = 3000) {
58867
- const toast = document.getElementById('toast');
58868
- toast.textContent = message;
58869
- toast.classList.add('show');
58870
- setTimeout(() => toast.classList.remove('show'), duration);
58871
- }
58872
-
58873
- // Load configuration on page load
58874
- async function loadConfig() {
58875
- try {
58876
- const response = await fetch('/api/config');
58877
- const data = await response.json();
58878
-
58879
- if (data.config) {
58880
- document.getElementById('apiKey').value = data.config.apiKey || '';
58881
- document.getElementById('providerUrl').value = data.config.providerUrl || 'https://api.openai.com/v1/chat/completions';
58882
- document.getElementById('embeddingsUrl').value = data.config.embeddingsUrl || 'https://api.openai.com/v1/embeddings';
58883
- document.getElementById('model').value = data.config.model || 'gpt-3.5-turbo';
58884
- document.getElementById('embeddingsModel').value = data.config.embeddingsModel || 'text-embedding-3-small';
58885
- }
58886
-
58887
- document.getElementById('embeddingCount').textContent = data.embeddingCount || 0;
58888
- document.getElementById('lastUpdated').textContent = data.lastUpdated || 'Never';
58889
- document.getElementById('configStatus').textContent = data.config?.apiKey ? '\u2705' : '\u274C';
58890
- } catch (error) {
58891
- console.error('Failed to load config:', error);
58892
- }
58893
- }
58894
-
58895
- // Save configuration
58896
- document.getElementById('configForm').addEventListener('submit', async (e) => {
58897
- e.preventDefault();
58898
-
58899
- const config = {
58900
- apiKey: document.getElementById('apiKey').value,
58901
- providerUrl: document.getElementById('providerUrl').value,
58902
- embeddingsUrl: document.getElementById('embeddingsUrl').value,
58903
- model: document.getElementById('model').value,
58904
- embeddingsModel: document.getElementById('embeddingsModel').value
58905
- };
58906
-
58907
- try {
58908
- const response = await fetch('/api/config', {
58909
- method: 'POST',
58910
- headers: { 'Content-Type': 'application/json' },
58911
- body: JSON.stringify(config)
58912
- });
58913
-
58914
- if (response.ok) {
58915
- showToast('\u2705 Configuration saved successfully!');
58916
- document.getElementById('configStatus').textContent = config.apiKey ? '\u2705' : '\u274C';
58917
- } else {
58918
- showToast('\u274C Failed to save configuration');
58919
- }
58920
- } catch (error) {
58921
- showToast('\u274C Error saving configuration');
58922
- }
58923
- });
58924
-
58925
- // Embed knowledge
58926
- document.getElementById('embedForm').addEventListener('submit', async (e) => {
58927
- e.preventDefault();
58928
-
58929
- const content = document.getElementById('faqContent').value.trim();
58930
- if (!content) {
58931
- showToast('\u26A0\uFE0F Please enter some content to embed');
58932
- return;
58933
- }
58934
-
58935
- const embedBtn = document.getElementById('embedBtn');
58936
- embedBtn.disabled = true;
58937
- embedBtn.textContent = '\u23F3 Embedding...';
58938
-
58939
- try {
58940
- const response = await fetch('/api/embed', {
58941
- method: 'POST',
58942
- headers: { 'Content-Type': 'application/json' },
58943
- body: JSON.stringify({ content })
58944
- });
58945
-
58946
- const data = await response.json();
58947
-
58948
- if (response.ok) {
58949
- showToast('\u2705 ' + (data.message || 'Content embedded successfully!'));
58950
- document.getElementById('embeddingCount').textContent = data.count || 0;
58951
- document.getElementById('lastUpdated').textContent = new Date().toLocaleString();
58952
- document.getElementById('faqContent').value = '';
58953
- } else {
58954
- showToast('\u274C ' + (data.error || 'Failed to embed content'));
58955
- }
58956
- } catch (error) {
58957
- showToast('\u274C Error embedding content');
58958
- } finally {
58959
- embedBtn.disabled = false;
58960
- embedBtn.textContent = '\u{1F52E} Embed Knowledge';
58961
- }
58962
- });
58963
-
58964
- // Clear embeddings
58965
- document.getElementById('clearBtn').addEventListener('click', async () => {
58966
- if (!confirm('Are you sure you want to clear all embeddings?')) return;
58967
-
58968
- try {
58969
- const response = await fetch('/api/embed', {
58970
- method: 'DELETE'
58971
- });
58972
-
58973
- if (response.ok) {
58974
- showToast('\u2705 All embeddings cleared');
58975
- document.getElementById('embeddingCount').textContent = '0';
58976
- } else {
58977
- showToast('\u274C Failed to clear embeddings');
58978
- }
58979
- } catch (error) {
58980
- showToast('\u274C Error clearing embeddings');
58981
- }
58982
- });
58983
-
58984
- // Load config on page load
58985
- loadConfig();
58986
- </script>
58987
- </body>
58988
- </html>
58989
- `;
58990
-
58991
- // ../../packages/template/demo/aichat/files/stylesTs.ts
58992
- var stylesCSS = `/* === CSS Variables === */
58993
- :root {
58994
- --primary: #22c55e;
58995
- --primary-dark: #16a34a;
58996
- --secondary: #f97316;
58997
- --bg-dark: #0f172a;
58998
- --bg-card: rgba(255, 255, 255, 0.05);
58999
- --text-light: #f8fafc;
59000
- --text-muted: #94a3b8;
59001
- --border-color: rgba(255, 255, 255, 0.1);
59002
- --shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
59003
- --glass: rgba(255, 255, 255, 0.1);
59004
- }
59005
-
59006
- * {
59007
- margin: 0;
59008
- padding: 0;
59009
- box-sizing: border-box;
59010
- }
59011
-
59012
- body {
59013
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
59014
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
59015
- color: var(--text-light);
59016
- min-height: 100vh;
59017
- line-height: 1.6;
59018
- }
59019
-
59020
- /* === Header === */
59021
- .header {
59022
- background: var(--glass);
59023
- backdrop-filter: blur(10px);
59024
- border-bottom: 1px solid var(--border-color);
59025
- padding: 1rem 2rem;
59026
- position: fixed;
59027
- top: 0;
59028
- left: 0;
59029
- right: 0;
59030
- z-index: 100;
59031
- }
59032
-
59033
- .header-content {
59034
- max-width: 1200px;
59035
- margin: 0 auto;
59036
- display: flex;
59037
- justify-content: space-between;
59038
- align-items: center;
59039
- }
59040
-
59041
- .logo {
59042
- font-size: 1.5rem;
59043
- font-weight: 700;
59044
- background: linear-gradient(135deg, var(--primary), var(--secondary));
59045
- -webkit-background-clip: text;
59046
- -webkit-text-fill-color: transparent;
59047
- background-clip: text;
59048
- }
59049
-
59050
- .nav-links {
59051
- display: flex;
59052
- gap: 2rem;
59053
- list-style: none;
59054
- }
59055
-
59056
- .nav-links a {
59057
- color: var(--text-muted);
59058
- text-decoration: none;
59059
- transition: color 0.3s;
59060
- }
59061
-
59062
- .nav-links a:hover {
59063
- color: var(--primary);
59064
- }
59065
-
59066
- /* === Hero Section === */
59067
- .hero {
59068
- padding: 8rem 2rem 4rem;
59069
- text-align: center;
59070
- max-width: 900px;
59071
- margin: 0 auto;
59072
- }
59073
-
59074
- .hero h1 {
59075
- font-size: 3.5rem;
59076
- font-weight: 800;
59077
- margin-bottom: 1rem;
59078
- background: linear-gradient(135deg, #fff, var(--primary));
59079
- -webkit-background-clip: text;
59080
- -webkit-text-fill-color: transparent;
59081
- background-clip: text;
59082
- }
59083
-
59084
- .hero p {
59085
- font-size: 1.25rem;
59086
- color: var(--text-muted);
59087
- margin-bottom: 2rem;
59088
- }
59089
-
59090
- .cta-button {
59091
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
59092
- color: white;
59093
- padding: 1rem 2.5rem;
59094
- border: none;
59095
- border-radius: 50px;
59096
- font-size: 1.1rem;
59097
- font-weight: 600;
59098
- cursor: pointer;
59099
- transition: transform 0.3s, box-shadow 0.3s;
59100
- }
59101
-
59102
- .cta-button:hover {
59103
- transform: translateY(-2px);
59104
- box-shadow: 0 10px 30px rgba(34, 197, 94, 0.3);
59105
- }
59106
-
59107
- /* === Products Grid === */
59108
- .products {
59109
- max-width: 1200px;
59110
- margin: 0 auto;
59111
- padding: 4rem 2rem;
59112
- }
59113
-
59114
- .products h2 {
59115
- text-align: center;
59116
- font-size: 2.5rem;
59117
- margin-bottom: 3rem;
59118
- }
59119
-
59120
- .products-grid {
59121
- display: grid;
59122
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
59123
- gap: 2rem;
59124
- }
59125
-
59126
- .product-card {
59127
- background: var(--bg-card);
59128
- border: 1px solid var(--border-color);
59129
- border-radius: 20px;
59130
- overflow: hidden;
59131
- transition: transform 0.3s, box-shadow 0.3s;
59132
- }
59133
-
59134
- .product-card:hover {
59135
- transform: translateY(-5px);
59136
- box-shadow: var(--shadow);
59137
- }
59138
-
59139
- .product-image {
59140
- width: 100%;
59141
- height: 200px;
59142
- object-fit: cover;
59143
- background: linear-gradient(135deg, var(--primary), var(--secondary));
59144
- display: flex;
59145
- align-items: center;
59146
- justify-content: center;
59147
- font-size: 4rem;
59148
- }
59149
-
59150
- .product-info {
59151
- padding: 1.5rem;
59152
- }
59153
-
59154
- .product-info h3 {
59155
- font-size: 1.25rem;
59156
- margin-bottom: 0.5rem;
59157
- }
59158
-
59159
- .product-info p {
59160
- color: var(--text-muted);
59161
- font-size: 0.9rem;
59162
- margin-bottom: 1rem;
59163
- }
59164
-
59165
- .product-price {
59166
- font-size: 1.5rem;
59167
- font-weight: 700;
59168
- color: var(--primary);
59169
- }
59170
-
59171
- /* === Chatbox Widget === */
59172
- .chat-widget {
59173
- position: fixed;
59174
- bottom: 20px;
59175
- right: 20px;
59176
- z-index: 1000;
59177
- }
59178
-
59179
- .chat-toggle {
59180
- width: 60px;
59181
- height: 60px;
59182
- border-radius: 50%;
59183
- background: linear-gradient(135deg, var(--primary), var(--secondary));
59184
- border: none;
59185
- cursor: pointer;
59186
- display: flex;
59187
- align-items: center;
59188
- justify-content: center;
59189
- box-shadow: 0 5px 30px rgba(34, 197, 94, 0.4);
59190
- transition: transform 0.3s;
59191
- }
59192
-
59193
- .chat-toggle:hover {
59194
- transform: scale(1.1);
59195
- }
59196
-
59197
- .chat-toggle svg {
59198
- width: 28px;
59199
- height: 28px;
59200
- fill: white;
59201
- }
59202
-
59203
- .chat-window {
59204
- position: absolute;
59205
- bottom: 80px;
59206
- right: 0;
59207
- width: 380px;
59208
- height: 500px;
59209
- background: var(--bg-dark);
59210
- border: 1px solid var(--border-color);
59211
- border-radius: 20px;
59212
- box-shadow: var(--shadow);
59213
- display: none;
59214
- flex-direction: column;
59215
- overflow: hidden;
59216
- animation: slideUp 0.3s ease;
59217
- }
59218
-
59219
- .chat-window.open {
59220
- display: flex;
59221
- }
59222
-
59223
- @keyframes slideUp {
59224
- from {
59225
- opacity: 0;
59226
- transform: translateY(20px);
59227
- }
59228
- to {
59229
- opacity: 1;
59230
- transform: translateY(0);
59231
- }
59232
- }
59233
-
59234
- .chat-header {
59235
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
59236
- padding: 1rem 1.5rem;
59237
- display: flex;
59238
- align-items: center;
59239
- gap: 0.75rem;
59240
- }
59241
-
59242
- .chat-header-avatar {
59243
- width: 40px;
59244
- height: 40px;
59245
- border-radius: 50%;
59246
- background: rgba(255, 255, 255, 0.2);
59247
- display: flex;
59248
- align-items: center;
59249
- justify-content: center;
59250
- font-size: 1.25rem;
59251
- }
59252
-
59253
- .chat-header-info h4 {
59254
- font-size: 1rem;
59255
- font-weight: 600;
59256
- }
59257
-
59258
- .chat-header-info span {
59259
- font-size: 0.75rem;
59260
- opacity: 0.8;
59261
- }
59262
-
59263
- .chat-messages {
59264
- flex: 1;
59265
- overflow-y: auto;
59266
- padding: 1rem;
59267
- display: flex;
59268
- flex-direction: column;
59269
- gap: 0.75rem;
59270
- }
59271
-
59272
- .message {
59273
- max-width: 80%;
59274
- padding: 0.75rem 1rem;
59275
- border-radius: 18px;
59276
- font-size: 0.9rem;
59277
- line-height: 1.4;
59278
- }
59279
-
59280
- .message.bot {
59281
- background: var(--glass);
59282
- border: 1px solid var(--border-color);
59283
- align-self: flex-start;
59284
- border-bottom-left-radius: 4px;
59285
- }
59286
-
59287
- .message.user {
59288
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
59289
- align-self: flex-end;
59290
- border-bottom-right-radius: 4px;
59291
- }
59292
-
59293
- .typing-indicator {
59294
- display: flex;
59295
- gap: 4px;
59296
- padding: 0.75rem 1rem;
59297
- background: var(--glass);
59298
- border: 1px solid var(--border-color);
59299
- border-radius: 18px;
59300
- align-self: flex-start;
59301
- border-bottom-left-radius: 4px;
59302
- }
59303
-
59304
- .typing-indicator span {
59305
- width: 8px;
59306
- height: 8px;
59307
- background: var(--text-muted);
59308
- border-radius: 50%;
59309
- animation: bounce 1.4s infinite ease-in-out;
59310
- }
59311
-
59312
- .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
59313
- .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
59314
-
59315
- @keyframes bounce {
59316
- 0%, 80%, 100% { transform: scale(0); }
59317
- 40% { transform: scale(1); }
59318
- }
59319
-
59320
- .chat-input-container {
59321
- padding: 1rem;
59322
- border-top: 1px solid var(--border-color);
59323
- display: flex;
59324
- gap: 0.5rem;
59325
- }
59326
-
59327
- .chat-input {
59328
- flex: 1;
59329
- background: var(--glass);
59330
- border: 1px solid var(--border-color);
59331
- border-radius: 25px;
59332
- padding: 0.75rem 1rem;
59333
- color: var(--text-light);
59334
- font-size: 0.9rem;
59335
- outline: none;
59336
- }
59337
-
59338
- .chat-input::placeholder {
59339
- color: var(--text-muted);
59340
- }
59341
-
59342
- .chat-send {
59343
- width: 44px;
59344
- height: 44px;
59345
- border-radius: 50%;
59346
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
59347
- border: none;
59348
- cursor: pointer;
59349
- display: flex;
59350
- align-items: center;
59351
- justify-content: center;
59352
- transition: transform 0.2s;
59353
- }
59354
-
59355
- .chat-send:hover {
59356
- transform: scale(1.05);
59357
- }
59358
-
59359
- .chat-send svg {
59360
- width: 20px;
59361
- height: 20px;
59362
- fill: white;
59363
- }
59364
-
59365
- /* === Admin Panel Styles === */
59366
- .admin-container {
59367
- max-width: 900px;
59368
- margin: 0 auto;
59369
- padding: 6rem 2rem 4rem;
59370
- }
59371
-
59372
- .admin-header {
59373
- text-align: center;
59374
- margin-bottom: 3rem;
59375
- }
59376
-
59377
- .admin-header h1 {
59378
- font-size: 2.5rem;
59379
- margin-bottom: 0.5rem;
59380
- }
59381
-
59382
- .admin-header p {
59383
- color: var(--text-muted);
59384
- }
59385
-
59386
- .admin-card {
59387
- background: var(--bg-card);
59388
- border: 1px solid var(--border-color);
59389
- border-radius: 20px;
59390
- padding: 2rem;
59391
- margin-bottom: 2rem;
59392
- }
59393
-
59394
- .admin-card h2 {
59395
- font-size: 1.25rem;
59396
- margin-bottom: 1.5rem;
59397
- display: flex;
59398
- align-items: center;
59399
- gap: 0.5rem;
59400
- }
59401
-
59402
- .form-group {
59403
- margin-bottom: 1.5rem;
59404
- }
59405
-
59406
- .form-group label {
59407
- display: block;
59408
- font-size: 0.9rem;
59409
- color: var(--text-muted);
59410
- margin-bottom: 0.5rem;
59411
- }
59412
-
59413
- .form-input {
59414
- width: 100%;
59415
- background: var(--glass);
59416
- border: 1px solid var(--border-color);
59417
- border-radius: 10px;
59418
- padding: 0.875rem 1rem;
59419
- color: var(--text-light);
59420
- font-size: 0.95rem;
59421
- outline: none;
59422
- transition: border-color 0.3s;
59423
- }
59424
-
59425
- .form-input:focus {
59426
- border-color: var(--primary);
59427
- }
59428
-
59429
- .form-input::placeholder {
59430
- color: var(--text-muted);
59431
- }
59432
-
59433
- textarea.form-input {
59434
- min-height: 200px;
59435
- resize: vertical;
59436
- font-family: inherit;
59437
- }
59438
-
59439
- .btn {
59440
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
59441
- color: white;
59442
- padding: 0.875rem 2rem;
59443
- border: none;
59444
- border-radius: 10px;
59445
- font-size: 1rem;
59446
- font-weight: 600;
59447
- cursor: pointer;
59448
- transition: transform 0.2s, box-shadow 0.2s;
59449
- display: inline-flex;
59450
- align-items: center;
59451
- gap: 0.5rem;
59452
- }
59453
-
59454
- .btn:hover {
59455
- transform: translateY(-2px);
59456
- box-shadow: 0 10px 30px rgba(34, 197, 94, 0.3);
59457
- }
59458
-
59459
- .btn:disabled {
59460
- opacity: 0.6;
59461
- cursor: not-allowed;
59462
- transform: none;
59463
- }
59464
-
59465
- .btn-secondary {
59466
- background: var(--glass);
59467
- border: 1px solid var(--border-color);
59468
- }
59469
-
59470
- .status-bar {
59471
- background: var(--glass);
59472
- border: 1px solid var(--border-color);
59473
- border-radius: 10px;
59474
- padding: 1rem 1.5rem;
59475
- display: flex;
59476
- justify-content: space-between;
59477
- align-items: center;
59478
- margin-top: 1.5rem;
59479
- }
59480
-
59481
- .status-item {
59482
- text-align: center;
59483
- }
59484
-
59485
- .status-item .value {
59486
- font-size: 1.5rem;
59487
- font-weight: 700;
59488
- color: var(--primary);
59489
- }
59490
-
59491
- .status-item .label {
59492
- font-size: 0.8rem;
59493
- color: var(--text-muted);
59494
- }
59495
-
59496
- .toast {
59497
- position: fixed;
59498
- bottom: 20px;
59499
- left: 50%;
59500
- transform: translateX(-50%);
59501
- background: var(--primary);
59502
- color: white;
59503
- padding: 1rem 2rem;
59504
- border-radius: 10px;
59505
- box-shadow: var(--shadow);
59506
- opacity: 0;
59507
- transition: opacity 0.3s;
59508
- z-index: 2000;
59509
- }
59510
-
59511
- .toast.show {
59512
- opacity: 1;
59513
- }
59514
-
59515
- /* === Responsive === */
59516
- @media (max-width: 768px) {
59517
- .hero h1 {
59518
- font-size: 2.5rem;
59519
- }
59520
-
59521
- .chat-window {
59522
- width: calc(100vw - 40px);
59523
- height: 60vh;
59524
- }
59525
-
59526
- .nav-links {
59527
- display: none;
59528
- }
59529
- }
59530
- `;
59531
-
59532
- // ../../packages/template/demo/aichat/files/serverTs.ts
59533
- var serverTs = `import express, { Request, Response } from 'express';
59534
- import path from 'path';
59535
- import chatRouter from './routes/chat';
59536
- import embedRouter from './routes/embed';
59537
- import configRouter from './routes/config';
59538
- import { loadEmbeddings } from './vectorStore';
59539
- import { loadConfig } from './aiClient';
59540
-
59541
- const app = express();
59542
- const port = 3500;
59543
-
59544
- // Middleware
59545
- app.use(express.static(path.join(__dirname, '../public')));
59546
- app.use(express.json());
59547
-
59548
- // Load persisted data
59549
- loadEmbeddings();
59550
- loadConfig();
59551
-
59552
- // API Routes
59553
- app.use('/api/chat', chatRouter);
59554
- app.use('/api/embed', embedRouter);
59555
- app.use('/api/config', configRouter);
59556
-
59557
- // Serve main page
59558
- app.get('/', (req: Request, res: Response) => {
59559
- res.sendFile(path.join(__dirname, '../public/index.html'));
59560
- });
59561
-
59562
- // Start server
59563
- app.listen(port, () => {
59564
- console.log('');
59565
- console.log('\u{1F34A} ====================================');
59566
- console.log(' FreshFruit AI Chat Demo');
59567
- console.log('====================================');
59568
- console.log('');
59569
- console.log('\u{1F4CD} Store: http://localhost:' + port);
59570
- console.log('\u{1F527} Admin Panel: http://localhost:' + port + '/admin.html');
59571
- console.log('');
59572
- console.log('\u{1F4DD} Quick Start:');
59573
- console.log(' 1. Open the Admin Panel');
59574
- console.log(' 2. Configure your OpenAI API key');
59575
- console.log(' 3. Add FAQ content and click "Embed"');
59576
- console.log(' 4. Open the Store and chat with the AI!');
59577
- console.log('');
59578
- });
59579
- `;
59580
- var tsconfigJson = `{
59581
- "compilerOptions": {
59582
- "target": "es2016",
59583
- "module": "commonjs",
59584
- "outDir": "./dist",
59585
- "esModuleInterop": true,
59586
- "forceConsistentCasingInFileNames": true,
59587
- "strict": true,
59588
- "skipLibCheck": true
59589
- }
59590
- }`;
59591
- var tsupConfig = `import { defineConfig } from 'tsup';
59592
-
59593
- export default defineConfig({
59594
- entry: ['src/index.ts'],
59595
- splitting: false,
59596
- sourcemap: true,
59597
- clean: true,
59598
- format: ['cjs'],
59599
- });`;
59600
-
59601
- // ../../packages/template/demo/aichat/files/vectorStoreTs.ts
59602
- var vectorStoreTs = `import fs from 'fs';
59603
- import path from 'path';
59604
-
59605
- const EMBEDDINGS_FILE = path.join(__dirname, '../embeddings.json');
59606
-
59607
- interface EmbeddingEntry {
59608
- id: string;
59609
- text: string;
59610
- embedding: number[];
59611
- timestamp: number;
59612
- }
59613
-
59614
- // In-memory vector database
59615
- let vectorStore: EmbeddingEntry[] = [];
59616
-
59617
- // Load embeddings from file on startup
59618
- export function loadEmbeddings(): void {
59619
- try {
59620
- if (fs.existsSync(EMBEDDINGS_FILE)) {
59621
- const data = fs.readFileSync(EMBEDDINGS_FILE, 'utf-8');
59622
- vectorStore = JSON.parse(data);
59623
- console.log('\u{1F4DA} Loaded ' + vectorStore.length + ' embeddings from file');
59624
- }
59625
- } catch (error) {
59626
- console.error('Failed to load embeddings:', error);
59627
- vectorStore = [];
59628
- }
59629
- }
59630
-
59631
- // Save embeddings to file
59632
- export function saveEmbeddings(): void {
59633
- try {
59634
- fs.writeFileSync(EMBEDDINGS_FILE, JSON.stringify(vectorStore, null, 2));
59635
- } catch (error) {
59636
- console.error('Failed to save embeddings:', error);
59637
- }
59638
- }
59639
-
59640
- // Add embedding to store
59641
- export function addEmbedding(text: string, embedding: number[]): void {
59642
- const entry: EmbeddingEntry = {
59643
- id: Date.now().toString(36) + Math.random().toString(36).substr(2),
59644
- text,
59645
- embedding,
59646
- timestamp: Date.now()
59647
- };
59648
- vectorStore.push(entry);
59649
- saveEmbeddings();
59650
- }
59651
-
59652
- // Clear all embeddings
59653
- export function clearEmbeddings(): void {
59654
- vectorStore = [];
59655
- saveEmbeddings();
59656
- }
59657
-
59658
- // Get embedding count
59659
- export function getEmbeddingCount(): number {
59660
- return vectorStore.length;
59661
- }
59662
-
59663
- // Cosine similarity between two vectors
59664
- function cosineSimilarity(a: number[], b: number[]): number {
59665
- if (a.length !== b.length) return 0;
59666
-
59667
- let dotProduct = 0;
59668
- let normA = 0;
59669
- let normB = 0;
59670
-
59671
- for (let i = 0; i < a.length; i++) {
59672
- dotProduct += a[i] * b[i];
59673
- normA += a[i] * a[i];
59674
- normB += b[i] * b[i];
59675
- }
59676
-
59677
- if (normA === 0 || normB === 0) return 0;
59678
- return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
59679
- }
59680
-
59681
- // Search for similar embeddings
59682
- export function searchSimilar(queryEmbedding: number[], topK: number = 3): { text: string; score: number }[] {
59683
- const results = vectorStore
59684
- .map(entry => ({
59685
- text: entry.text,
59686
- score: cosineSimilarity(queryEmbedding, entry.embedding)
59687
- }))
59688
- .sort((a, b) => b.score - a.score)
59689
- .slice(0, topK);
59690
-
59691
- return results;
59692
- }
59693
-
59694
- // Get last update time
59695
- export function getLastUpdated(): string | null {
59696
- if (vectorStore.length === 0) return null;
59697
- const latest = Math.max(...vectorStore.map(e => e.timestamp));
59698
- return new Date(latest).toLocaleString();
59699
- }
59700
- `;
59701
-
59702
- // ../../packages/template/demo/aichat/files/aiClientTs.ts
59703
- var aiClientTs = `import fs from 'fs';
59704
- import path from 'path';
59705
-
59706
- const CONFIG_FILE = path.join(__dirname, '../config.json');
59707
-
59708
- export interface AIConfig {
59709
- apiKey: string;
59710
- providerUrl: string;
59711
- embeddingsUrl: string;
59712
- model: string;
59713
- embeddingsModel: string;
59714
- }
59715
-
59716
- // Default configuration
59717
- const defaultConfig: AIConfig = {
59718
- apiKey: '',
59719
- providerUrl: 'https://api.openai.com/v1/chat/completions',
59720
- embeddingsUrl: 'https://api.openai.com/v1/embeddings',
59721
- model: 'gpt-3.5-turbo',
59722
- embeddingsModel: 'text-embedding-3-small'
59723
- };
59724
-
59725
- let currentConfig: AIConfig = { ...defaultConfig };
59726
-
59727
- // Load configuration from file
59728
- export function loadConfig(): void {
59729
- try {
59730
- if (fs.existsSync(CONFIG_FILE)) {
59731
- const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
59732
- currentConfig = { ...defaultConfig, ...JSON.parse(data) };
59733
- console.log('\u{1F511} Loaded AI configuration');
59734
- }
59735
- } catch (error) {
59736
- console.error('Failed to load config:', error);
59737
- currentConfig = { ...defaultConfig };
59738
- }
59739
- }
59740
-
59741
- // Save configuration to file
59742
- export function saveConfig(config: AIConfig): void {
59743
- currentConfig = { ...config };
59744
- try {
59745
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
59746
- } catch (error) {
59747
- console.error('Failed to save config:', error);
59748
- }
59749
- }
59750
-
59751
- // Get current configuration
59752
- export function getConfig(): AIConfig {
59753
- return currentConfig;
59754
- }
59755
-
59756
- // Call embeddings API
59757
- export async function callEmbeddingsAPI(text: string): Promise<number[]> {
59758
- const config = getConfig();
59759
-
59760
- if (!config.apiKey) {
59761
- throw new Error('API key not configured');
59762
- }
59763
-
59764
- const response = await fetch(config.embeddingsUrl, {
59765
- method: 'POST',
59766
- headers: {
59767
- 'Content-Type': 'application/json',
59768
- 'Authorization': 'Bearer ' + config.apiKey
59769
- },
59770
- body: JSON.stringify({
59771
- model: config.embeddingsModel,
59772
- input: text
59773
- })
59774
- });
59775
-
59776
- if (!response.ok) {
59777
- const error = await response.text();
59778
- throw new Error('Embeddings API error: ' + error);
59779
- }
59780
-
59781
- const data = await response.json();
59782
- return data.data[0].embedding;
59783
- }
59784
-
59785
- // Call chat completions API
59786
- export async function callChatAPI(systemPrompt: string, userMessage: string): Promise<string> {
59787
- const config = getConfig();
59788
-
59789
- if (!config.apiKey) {
59790
- throw new Error('API key not configured');
59791
- }
59792
-
59793
- const response = await fetch(config.providerUrl, {
59794
- method: 'POST',
59795
- headers: {
59796
- 'Content-Type': 'application/json',
59797
- 'Authorization': 'Bearer ' + config.apiKey
59798
- },
59799
- body: JSON.stringify({
59800
- model: config.model,
59801
- messages: [
59802
- { role: 'system', content: systemPrompt },
59803
- { role: 'user', content: userMessage }
59804
- ],
59805
- max_tokens: 500,
59806
- temperature: 0.7
59807
- })
59808
- });
59809
-
59810
- if (!response.ok) {
59811
- const error = await response.text();
59812
- throw new Error('Chat API error: ' + error);
59813
- }
59814
-
59815
- const data = await response.json();
59816
- return data.choices[0].message.content;
59817
- }
59818
- `;
59819
-
59820
- // ../../packages/template/demo/aichat/files/routesTs.ts
59821
- var chatRouteTs = `import { Router, Request, Response } from 'express';
59822
- import { searchSimilar } from '../vectorStore';
59823
- import { getConfig, callEmbeddingsAPI, callChatAPI } from '../aiClient';
59824
-
59825
- const router = Router();
59826
-
59827
- router.post('/', async (req: Request, res: Response) => {
59828
- try {
59829
- const { message } = req.body;
59830
-
59831
- if (!message) {
59832
- return res.status(400).json({ error: 'Message is required' });
59833
- }
59834
-
59835
- const config = getConfig();
59836
- if (!config.apiKey) {
59837
- return res.json({
59838
- reply: "I'm not configured yet. Please ask the admin to set up the AI provider in the admin panel."
59839
- });
59840
- }
59841
-
59842
- // Get embedding for the user's message
59843
- const queryEmbedding = await callEmbeddingsAPI(message);
59844
-
59845
- // Search for similar content in our knowledge base
59846
- const similarDocs = searchSimilar(queryEmbedding, 3);
59847
-
59848
- // Build context from similar documents
59849
- let context = '';
59850
- if (similarDocs.length > 0 && similarDocs[0].score > 0.3) {
59851
- context = 'Relevant information from our knowledge base:\\n' +
59852
- similarDocs
59853
- .filter(doc => doc.score > 0.3)
59854
- .map(doc => doc.text)
59855
- .join('\\n\\n');
59856
- }
59857
-
59858
- // Create the chat prompt
59859
- const systemPrompt = "You are a helpful customer support assistant for FreshFruit, " +
59860
- "a premium organic fruit delivery service. Be friendly, helpful, and concise. " +
59861
- "If you have relevant information from the knowledge base, use it to answer. " +
59862
- "If you don't know something, say so politely and suggest contacting human support.\\n\\n" +
59863
- (context ? 'Knowledge Base Context:\\n' + context : 'No specific knowledge base context available for this query.');
59864
-
59865
- // Call the chat API
59866
- const reply = await callChatAPI(systemPrompt, message);
59867
-
59868
- res.json({ reply });
59869
- } catch (error) {
59870
- console.error('Chat error:', error);
59871
- res.status(500).json({ error: 'Failed to process message' });
59872
- }
59873
- });
59874
-
59875
- export default router;
59876
- `;
59877
- var embedRouteTs = `import { Router, Request, Response } from 'express';
59878
- import { addEmbedding, clearEmbeddings, getEmbeddingCount } from '../vectorStore';
59879
- import { getConfig, callEmbeddingsAPI } from '../aiClient';
59880
-
59881
- const router = Router();
59882
-
59883
- // Embed new content
59884
- router.post('/', async (req: Request, res: Response) => {
59885
- try {
59886
- const { content } = req.body;
59887
-
59888
- if (!content) {
59889
- return res.status(400).json({ error: 'Content is required' });
59890
- }
59891
-
59892
- const config = getConfig();
59893
- if (!config.apiKey) {
59894
- return res.status(400).json({ error: 'API key not configured. Please configure in admin panel.' });
59895
- }
59896
-
59897
- // Split content into chunks (by double newline or paragraph)
59898
- const chunks = content
59899
- .split(/\\n\\n+/)
59900
- .map((chunk: string) => chunk.trim())
59901
- .filter((chunk: string) => chunk.length > 10);
59902
-
59903
- if (chunks.length === 0) {
59904
- return res.status(400).json({ error: 'No valid content chunks found' });
59905
- }
59906
-
59907
- // Embed each chunk
59908
- let embedded = 0;
59909
- for (const chunk of chunks) {
59910
- try {
59911
- const embedding = await callEmbeddingsAPI(chunk);
59912
- addEmbedding(chunk, embedding);
59913
- embedded++;
59914
- } catch (error) {
59915
- console.error('Failed to embed chunk:', error);
59916
- }
59917
- }
59918
-
59919
- res.json({
59920
- message: 'Successfully embedded ' + embedded + ' chunks',
59921
- count: getEmbeddingCount()
59922
- });
59923
- } catch (error) {
59924
- console.error('Embed error:', error);
59925
- res.status(500).json({ error: 'Failed to embed content' });
59926
- }
59927
- });
59928
-
59929
- // Clear all embeddings
59930
- router.delete('/', (req: Request, res: Response) => {
59931
- clearEmbeddings();
59932
- res.json({ message: 'All embeddings cleared', count: 0 });
59933
- });
59934
-
59935
- export default router;
59936
- `;
59937
- var configRouteTs = `import { Router, Request, Response } from 'express';
59938
- import { getConfig, saveConfig, AIConfig } from '../aiClient';
59939
- import { getEmbeddingCount, getLastUpdated } from '../vectorStore';
59940
-
59941
- const router = Router();
59942
-
59943
- // Get current config
59944
- router.get('/', (req: Request, res: Response) => {
59945
- const config = getConfig();
59946
- // Mask the API key for security
59947
- const maskedConfig = {
59948
- ...config,
59949
- apiKey: config.apiKey ? '\u2022\u2022\u2022\u2022\u2022\u2022' + config.apiKey.slice(-4) : ''
59950
- };
59951
-
59952
- res.json({
59953
- config: maskedConfig,
59954
- embeddingCount: getEmbeddingCount(),
59955
- lastUpdated: getLastUpdated()
59956
- });
59957
- });
59958
-
59959
- // Update config
59960
- router.post('/', (req: Request, res: Response) => {
59961
- try {
59962
- const { apiKey, providerUrl, embeddingsUrl, model, embeddingsModel } = req.body;
59963
-
59964
- const newConfig: AIConfig = {
59965
- apiKey: apiKey || '',
59966
- providerUrl: providerUrl || 'https://api.openai.com/v1/chat/completions',
59967
- embeddingsUrl: embeddingsUrl || 'https://api.openai.com/v1/embeddings',
59968
- model: model || 'gpt-3.5-turbo',
59969
- embeddingsModel: embeddingsModel || 'text-embedding-3-small'
59970
- };
59971
-
59972
- saveConfig(newConfig);
59973
- res.json({ message: 'Configuration saved' });
59974
- } catch (error) {
59975
- console.error('Config save error:', error);
59976
- res.status(500).json({ error: 'Failed to save configuration' });
59977
- }
59978
- });
59979
-
59980
- export default router;
59981
- `;
59982
-
59983
- // ../../packages/template/demo/aichat/index.ts
59984
- var AIChat = {
59985
- name: "AI Chat",
59986
- description: "Fullstack AI Customer Support Chat - Chat with your FAQ/Knowledge Base",
59987
- notes: "Requires Node.js, NPM, and an OpenAI-compatible API key",
59988
- templating: [
59989
- // Install dependencies
59990
- {
59991
- action: "command",
59992
- cmd: "npm",
59993
- args: ["init", "-y"]
59994
- },
59995
- {
59996
- action: "command",
59997
- cmd: "npm",
59998
- args: ["install", "express"]
59999
- },
60000
- {
60001
- action: "command",
60002
- cmd: "npm",
60003
- args: ["install", "-D", "nodemon", "typescript", "ts-node", "@types/node", "@types/express", "tsup"]
60004
- },
60005
- // Public files
60006
- {
60007
- action: "file",
60008
- file: "public/index.html",
60009
- filecontent: indexHTML
60010
- },
60011
- {
60012
- action: "file",
60013
- file: "public/admin.html",
60014
- filecontent: adminHTML
60015
- },
60016
- {
60017
- action: "file",
60018
- file: "public/styles.css",
60019
- filecontent: stylesCSS
60020
- },
60021
- // Source files
60022
- {
60023
- action: "file",
60024
- file: "src/index.ts",
60025
- filecontent: serverTs
60026
- },
60027
- {
60028
- action: "file",
60029
- file: "src/vectorStore.ts",
60030
- filecontent: vectorStoreTs
60031
- },
60032
- {
60033
- action: "file",
60034
- file: "src/aiClient.ts",
60035
- filecontent: aiClientTs
60036
- },
60037
- {
60038
- action: "file",
60039
- file: "src/routes/chat.ts",
60040
- filecontent: chatRouteTs
60041
- },
60042
- {
60043
- action: "file",
60044
- file: "src/routes/embed.ts",
60045
- filecontent: embedRouteTs
60046
- },
60047
- {
60048
- action: "file",
60049
- file: "src/routes/config.ts",
60050
- filecontent: configRouteTs
60051
- },
60052
- // Config files
60053
- {
60054
- action: "file",
60055
- file: "tsconfig.json",
60056
- filecontent: tsconfigJson
60057
- },
60058
- {
60059
- action: "file",
60060
- file: "tsup.config.ts",
60061
- filecontent: tsupConfig
60062
- },
60063
- // NPM scripts
60064
- {
60065
- action: "command",
60066
- cmd: "npm",
60067
- args: ["pkg", "set", "scripts.dev=nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"]
60068
- },
60069
- {
60070
- action: "command",
60071
- cmd: "npm",
60072
- args: ["pkg", "set", "scripts.build=tsup"]
60073
- },
60074
- {
60075
- action: "command",
60076
- cmd: "npm",
60077
- args: ["pkg", "set", "scripts.start=node dist/index.js"]
60078
- },
60079
- {
60080
- action: "command",
60081
- cmd: "npm",
60082
- args: ["pkg", "set", "scripts.stop=npx -y kill-port 3500"]
60083
- },
60084
- {
60085
- action: "command",
60086
- cmd: "npm",
60087
- args: ["pkg", "set", "fontawesomeIcon=fa-solid fa-comments"]
60088
- },
60089
- {
60090
- action: "command",
60091
- cmd: "npm",
60092
- args: ["pkg", "set", "name=$(basename $PWD)"]
60093
- }
60094
- ]
60095
- };
60096
-
60097
- // ../../packages/template/demo.ts
60098
- var templates2 = [
60099
- AIChat
60100
- ];
60101
- var demo_default = templates2;
60102
-
60103
- // ../../packages/template/projects/_viteapp.ts
60104
- var file = `import { useState } from 'react'
58917
+ // ../../packages/template/demo.ts
58918
+ var templates2 = [
58919
+ monochat_default
58920
+ ];
58921
+ var demo_default = templates2;
60105
58922
 
58923
+ // ../../packages/template/projects/files/_viteapp.ts
58924
+ var file = `
60106
58925
  function App() {
60107
58926
  return (
60108
58927
  <div className="min-h-screen bg-slate-950 text-white font-sans selection:bg-indigo-500 selection:text-white">
@@ -60227,7 +59046,7 @@ var ViteReact = {
60227
59046
  {
60228
59047
  action: "command",
60229
59048
  cmd: "npm",
60230
- args: ["install", "-D", "tailwindcss", "@tailwindcss/postcss", "autoprefixer"]
59049
+ args: ["install", "-D", "tailwindcss", "@tailwindcss/postcss", "postcss", "autoprefixer"]
60231
59050
  },
60232
59051
  {
60233
59052
  action: "file",
@@ -60235,19 +59054,9 @@ var ViteReact = {
60235
59054
  filecontent: 'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n autoprefixer: {},\n },\n}'
60236
59055
  },
60237
59056
  {
60238
- action: "file",
60239
- file: "tailwind.config.js",
60240
- filecontent: `/** @type {import('tailwindcss').Config} */
60241
- export default {
60242
- content: [
60243
- "./index.html",
60244
- "./src/**/*.{js,ts,jsx,tsx}",
60245
- ],
60246
- theme: {
60247
- extend: {},
60248
- },
60249
- plugins: [],
60250
- };`
59057
+ action: "command",
59058
+ cmd: "rm",
59059
+ args: ["src/App.css"]
60251
59060
  },
60252
59061
  {
60253
59062
  action: "file",
@@ -60268,11 +59077,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
60268
59077
  </React.StrictMode>,
60269
59078
  )`
60270
59079
  },
60271
- {
60272
- action: "command",
60273
- cmd: "rm",
60274
- args: ["src/App.css"]
60275
- },
60276
59080
  {
60277
59081
  action: "file",
60278
59082
  file: "src/App.tsx",
@@ -60325,12 +59129,17 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
60325
59129
  {
60326
59130
  action: "command",
60327
59131
  cmd: "npm",
60328
- args: ["pkg", "set", "fontawesomeIcon=fab fa-react text-blue-400"]
59132
+ args: ["pkg", "set", "description=Vite React TS"]
59133
+ },
59134
+ {
59135
+ action: "command",
59136
+ cmd: "npm",
59137
+ args: ["pkg", "set", "fontawesomeIcon=fab fa-react text-blue-500"]
60329
59138
  }
60330
59139
  ]
60331
59140
  };
60332
59141
 
60333
- // ../../packages/template/projects/_nextapp.ts
59142
+ // ../../packages/template/projects/files/_nextapp.ts
60334
59143
  var app = `export default function Home() {
60335
59144
  return (
60336
59145
  <div className="min-h-screen bg-slate-950 text-white font-sans selection:bg-indigo-500 selection:text-white">
@@ -60561,6 +59370,11 @@ export default function RootLayout({
60561
59370
  file: "Procfile",
60562
59371
  filecontent: "web: npm start"
60563
59372
  },
59373
+ {
59374
+ action: "command",
59375
+ cmd: "npm",
59376
+ args: ["pkg", "set", "description=Next.js TS"]
59377
+ },
60564
59378
  {
60565
59379
  action: "command",
60566
59380
  cmd: "npm",
@@ -60569,7 +59383,7 @@ export default function RootLayout({
60569
59383
  ]
60570
59384
  };
60571
59385
 
60572
- // ../../packages/template/projects/express.ts
59386
+ // ../../packages/template/projects/files/_express.ts
60573
59387
  var expressFile = `import express, { Request, Response } from "express";
60574
59388
  import path from "path";
60575
59389
  import cors from "cors";
@@ -60676,6 +59490,13 @@ var htmlFile = `<!DOCTYPE html>
60676
59490
  </body>
60677
59491
  </html>
60678
59492
  `;
59493
+ var express_default = {
59494
+ expressFile,
59495
+ helloRouterFile,
59496
+ htmlFile
59497
+ };
59498
+
59499
+ // ../../packages/template/projects/express.ts
60679
59500
  var ExpressTS = {
60680
59501
  name: "Express.js TS",
60681
59502
  description: "Express.js TS template",
@@ -60686,6 +59507,11 @@ var ExpressTS = {
60686
59507
  cmd: "npm",
60687
59508
  args: ["init", "-y"]
60688
59509
  },
59510
+ {
59511
+ action: "command",
59512
+ cmd: "node",
59513
+ args: ["-e", "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json'));if(p.name==='express'){p.name='express-app';fs.writeFileSync('package.json',JSON.stringify(p,null,2))}"]
59514
+ },
60689
59515
  {
60690
59516
  action: "command",
60691
59517
  cmd: "npm",
@@ -60704,17 +59530,17 @@ var ExpressTS = {
60704
59530
  {
60705
59531
  action: "file",
60706
59532
  file: "public/index.html",
60707
- filecontent: htmlFile
59533
+ filecontent: express_default.htmlFile
60708
59534
  },
60709
59535
  {
60710
59536
  action: "file",
60711
59537
  file: "src/routes/hello.ts",
60712
- filecontent: helloRouterFile
59538
+ filecontent: express_default.helloRouterFile
60713
59539
  },
60714
59540
  {
60715
59541
  action: "file",
60716
59542
  file: "src/index.ts",
60717
- filecontent: expressFile
59543
+ filecontent: express_default.expressFile
60718
59544
  },
60719
59545
  {
60720
59546
  action: "file",
@@ -60789,6 +59615,11 @@ var ExpressTS = {
60789
59615
  file: "Procfile",
60790
59616
  filecontent: "web: npm start"
60791
59617
  },
59618
+ {
59619
+ action: "command",
59620
+ cmd: "npm",
59621
+ args: ["pkg", "set", "description=Express.js TS"]
59622
+ },
60792
59623
  {
60793
59624
  action: "command",
60794
59625
  cmd: "npm",
@@ -60797,7 +59628,7 @@ var ExpressTS = {
60797
59628
  ]
60798
59629
  };
60799
59630
 
60800
- // ../../packages/template/projects/serverless-express.ts
59631
+ // ../../packages/template/projects/files/_serverless.ts
60801
59632
  var htmlFile2 = `<!DOCTYPE html>
60802
59633
  <html lang="en">
60803
59634
  <head>
@@ -60974,17 +59805,29 @@ functions:
60974
59805
  events:
60975
59806
  - httpApi: '*'
60976
59807
  `;
59808
+ var serverless_default = {
59809
+ htmlFile: htmlFile2,
59810
+ appFile,
59811
+ localFile,
59812
+ vercelApiFile,
59813
+ netlifyFunctionFile,
59814
+ helloRouterFile: helloRouterFile2,
59815
+ lambdaFile,
59816
+ dockerFile,
59817
+ serverlessYmlFile
59818
+ };
59819
+
59820
+ // ../../packages/template/projects/serverless-express.ts
60977
59821
  var ServerlessExpressTS = {
60978
- name: "Universal Express TS",
60979
- description: "Express.js TS template optimized for Serverless (Netlify, Vercel, AWS) & Containers (Docker, Render, Fly.io)",
60980
- notes: "Works on Vercel, Netlify, AWS Lambda, Google Cloud, Render, Heroku & Docker.",
59822
+ name: "Serverless Express TS",
59823
+ description: "Serverless Express TS template optimized for Serverless (Netlify, Vercel, AWS) & Containers (Docker, Render, Fly.io)",
59824
+ notes: "Node.js and NPM must be installed.",
60981
59825
  templating: [
60982
59826
  {
60983
59827
  action: "command",
60984
59828
  cmd: "npm",
60985
59829
  args: ["init", "-y"]
60986
59830
  },
60987
- // Dependencies
60988
59831
  {
60989
59832
  action: "command",
60990
59833
  cmd: "npm",
@@ -60999,44 +59842,44 @@ var ServerlessExpressTS = {
60999
59842
  {
61000
59843
  action: "file",
61001
59844
  file: "public/index.html",
61002
- filecontent: htmlFile2
59845
+ filecontent: serverless_default.htmlFile
61003
59846
  },
61004
59847
  {
61005
59848
  action: "file",
61006
59849
  file: "src/app.ts",
61007
- filecontent: appFile
59850
+ filecontent: serverless_default.appFile
61008
59851
  },
61009
59852
  {
61010
59853
  action: "file",
61011
59854
  file: "src/local.ts",
61012
- filecontent: localFile
59855
+ filecontent: serverless_default.localFile
61013
59856
  },
61014
59857
  {
61015
59858
  action: "file",
61016
59859
  file: "src/routes/hello.ts",
61017
- filecontent: helloRouterFile2
59860
+ filecontent: serverless_default.helloRouterFile
61018
59861
  },
61019
59862
  // Serverless Entry Points
61020
59863
  {
61021
59864
  action: "file",
61022
59865
  file: "api/index.ts",
61023
- filecontent: vercelApiFile
59866
+ filecontent: serverless_default.vercelApiFile
61024
59867
  },
61025
59868
  {
61026
59869
  action: "file",
61027
59870
  file: "netlify/functions/api.ts",
61028
- filecontent: netlifyFunctionFile
59871
+ filecontent: serverless_default.netlifyFunctionFile
61029
59872
  },
61030
59873
  {
61031
59874
  action: "file",
61032
59875
  file: "src/lambda.ts",
61033
- filecontent: lambdaFile
59876
+ filecontent: serverless_default.lambdaFile
61034
59877
  },
61035
59878
  // Container Configs
61036
59879
  {
61037
59880
  action: "file",
61038
59881
  file: "Dockerfile",
61039
- filecontent: dockerFile
59882
+ filecontent: serverless_default.dockerFile
61040
59883
  },
61041
59884
  {
61042
59885
  action: "file",
@@ -61046,7 +59889,7 @@ var ServerlessExpressTS = {
61046
59889
  {
61047
59890
  action: "file",
61048
59891
  file: "serverless.yml",
61049
- filecontent: serverlessYmlFile
59892
+ filecontent: serverless_default.serverlessYmlFile
61050
59893
  },
61051
59894
  // Config Files
61052
59895
  {
@@ -61124,6 +59967,11 @@ var ServerlessExpressTS = {
61124
59967
  cmd: "npm",
61125
59968
  args: ["pkg", "set", "scripts.stop=npx -y kill-port 3500"]
61126
59969
  },
59970
+ {
59971
+ action: "command",
59972
+ cmd: "npm",
59973
+ args: ["pkg", "set", "description=Serverless Express TS"]
59974
+ },
61127
59975
  {
61128
59976
  action: "command",
61129
59977
  cmd: "npm",
@@ -61132,6 +59980,82 @@ var ServerlessExpressTS = {
61132
59980
  ]
61133
59981
  };
61134
59982
 
59983
+ // ../../packages/template/projects/files/_php.ts
59984
+ var phpContent = `<?php
59985
+ $file = 'visits.txt';
59986
+ if (!file_exists($file)) {
59987
+ file_put_contents($file, 0);
59988
+ }
59989
+ $count = (int)file_get_contents($file);
59990
+ $count++;
59991
+ file_put_contents($file, $count);
59992
+ ?>
59993
+ <!DOCTYPE html>
59994
+ <html lang="en">
59995
+ <head>
59996
+ <meta charset="UTF-8">
59997
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
59998
+ <title>Welcome to Monorepo Time</title>
59999
+ <script src="https://cdn.tailwindcss.com"></script>
60000
+ <style>
60001
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&display=swap');
60002
+ body { font-family: 'Outfit', sans-serif; }
60003
+ </style>
60004
+ </head>
60005
+ <body class="bg-gray-900 text-white min-h-screen flex items-center justify-center relative overflow-hidden">
60006
+ <!-- Background Elements -->
60007
+ <div class="absolute top-0 left-0 w-full h-full overflow-hidden z-0">
60008
+ <div class="absolute top-[-10%] right-[-10%] w-96 h-96 bg-purple-600 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob"></div>
60009
+ <div class="absolute bottom-[-10%] left-[-10%] w-96 h-96 bg-blue-600 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob animation-delay-2000"></div>
60010
+ </div>
60011
+
60012
+ <div class="z-10 text-center p-8 bg-white/10 backdrop-blur-lg rounded-2xl border border-white/20 shadow-2xl max-w-lg w-full transform hover:scale-105 transition-transform duration-300">
60013
+ <div class="mb-6">
60014
+ <span class="text-4xl">\u{1F680}</span>
60015
+ </div>
60016
+ <h1 class="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
60017
+ Monorepo Time
60018
+ </h1>
60019
+ <p class="text-gray-300 text-lg mb-6">
60020
+ Your PHP application is up and running.
60021
+ </p>
60022
+
60023
+ <div class="bg-black/30 rounded-xl p-4 mb-6">
60024
+ <p class="text-sm text-gray-400 uppercase tracking-widest mb-1">Total Visits</p>
60025
+ <p class="text-3xl font-mono font-bold text-green-400">
60026
+ <?php echo number_format($count); ?>
60027
+ </p>
60028
+ </div>
60029
+
60030
+ <div class="flex justify-center gap-4">
60031
+ <a href="#" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 rounded-full font-medium transition-colors duration-200">
60032
+ Documentation
60033
+ </a>
60034
+ <a href="#" class="px-6 py-2 bg-transparent border border-white/30 hover:bg-white/10 rounded-full font-medium transition-colors duration-200">
60035
+ Learn More
60036
+ </a>
60037
+ </div>
60038
+ </div>
60039
+
60040
+ <!-- Animation Keyframes -->
60041
+ <style>
60042
+ @keyframes blob {
60043
+ 0% { transform: translate(0px, 0px) scale(1); }
60044
+ 33% { transform: translate(30px, -50px) scale(1.1); }
60045
+ 66% { transform: translate(-20px, 20px) scale(0.9); }
60046
+ 100% { transform: translate(0px, 0px) scale(1); }
60047
+ }
60048
+ .animate-blob {
60049
+ animation: blob 7s infinite;
60050
+ }
60051
+ .animation-delay-2000 {
60052
+ animation-delay: 2s;
60053
+ }
60054
+ </style>
60055
+ </body>
60056
+ </html>
60057
+ `;
60058
+
61135
60059
  // ../../packages/template/projects/php.ts
61136
60060
  var PHP = {
61137
60061
  name: "PHP",
@@ -61141,27 +60065,27 @@ var PHP = {
61141
60065
  {
61142
60066
  action: "file",
61143
60067
  file: "index.php",
61144
- filecontent: '<?php\n\necho "Hello World! Monorepo Time!";\n'
60068
+ filecontent: phpContent
61145
60069
  },
61146
60070
  {
61147
60071
  action: "command",
61148
60072
  cmd: "npm",
61149
- args: ["pkg", "set", "scripts.dev=php -S localhost:3000"]
60073
+ args: ["pkg", "set", "scripts.start=php -S localhost:3000"]
61150
60074
  },
61151
60075
  {
61152
60076
  action: "command",
61153
60077
  cmd: "npm",
61154
- args: ["pkg", "set", "scripts.start=php -S localhost:3000"]
60078
+ args: ["pkg", "set", "scripts.stop=npx kill-port 3000"]
61155
60079
  },
61156
60080
  {
61157
60081
  action: "command",
61158
60082
  cmd: "npm",
61159
- args: ["pkg", "set", "scripts.stop=npx kill-port 3000"]
60083
+ args: ["pkg", "set", "description=PHP"]
61160
60084
  },
61161
60085
  {
61162
60086
  action: "command",
61163
60087
  cmd: "npm",
61164
- args: ["pkg", "set", "fontawesomeIcon=fab fa-php text-indigo-400"]
60088
+ args: ["pkg", "set", "fontawesomeIcon=fab fa-php text-indigo-500"]
61165
60089
  }
61166
60090
  ]
61167
60091
  };
@@ -61202,6 +60126,11 @@ var Laravel = {
61202
60126
  cmd: "npm",
61203
60127
  args: ["pkg", "set", "scripts.stop=npx -y kill-port 8000"]
61204
60128
  },
60129
+ {
60130
+ action: "command",
60131
+ cmd: "npm",
60132
+ args: ["pkg", "set", "description=Laravel"]
60133
+ },
61205
60134
  {
61206
60135
  action: "command",
61207
60136
  cmd: "npm",
@@ -61210,31 +60139,171 @@ var Laravel = {
61210
60139
  ]
61211
60140
  };
61212
60141
 
61213
- // ../../packages/template/projects/python.ts
61214
- var pythonFile = `print("Monorepo Time Console!")
61215
- name = input("Please enter your name: ")
61216
- print("Hello " + name)
60142
+ // ../../packages/template/projects/files/_python.ts
60143
+ var mainPy = `import http.server
60144
+ import socketserver
60145
+ import os
60146
+ import sys
60147
+
60148
+ # Define port, default to 3000 or use environment variable
60149
+ PORT = int(os.environ.get('PORT', 3000))
60150
+
60151
+ class MyHandler(http.server.SimpleHTTPRequestHandler):
60152
+ def do_GET(self):
60153
+ # Route: / -> Load index.html
60154
+ if self.path == '/':
60155
+ self.path = 'index.html'
60156
+ return http.server.SimpleHTTPRequestHandler.do_GET(self)
60157
+
60158
+ # Route: /test -> Return "Hello World"
60159
+ elif self.path == '/test':
60160
+ self.send_response(200)
60161
+ self.send_header('Content-type', 'text/plain')
60162
+ self.end_headers()
60163
+ self.wfile.write(b"Hello World")
60164
+ return
60165
+
60166
+ # Default: Serve static files
60167
+ return http.server.SimpleHTTPRequestHandler.do_GET(self)
60168
+
60169
+ # Ensure index.html exists (though template should create it)
60170
+ if not os.path.exists('index.html'):
60171
+ with open('index.html', 'w') as f:
60172
+ f.write("<h1>Error: index.html not found</h1>")
60173
+
60174
+ print(f"Python server is running at http://localhost:{PORT}")
60175
+ print("Press Ctrl+C to stop.")
60176
+
60177
+ try:
60178
+ # Allow address reuse to prevent "Address already in use" errors on restart
60179
+ socketserver.TCPServer.allow_reuse_address = True
60180
+ with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
60181
+ httpd.serve_forever()
60182
+ except KeyboardInterrupt:
60183
+ print("^C received, shutting down server")
60184
+ sys.exit(0)
60185
+ `;
60186
+ var indexHtml = `<!DOCTYPE html>
60187
+ <html lang="en">
60188
+ <head>
60189
+ <meta charset="UTF-8">
60190
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
60191
+ <title>Monorepo Time - Python Backend</title>
60192
+ <script src="https://cdn.tailwindcss.com"></script>
60193
+ <style>
60194
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
60195
+ body { font-family: 'JetBrains Mono', monospace; }
60196
+ .glass {
60197
+ background: rgba(255, 255, 255, 0.05);
60198
+ backdrop-filter: blur(10px);
60199
+ -webkit-backdrop-filter: blur(10px);
60200
+ border: 1px solid rgba(255, 255, 255, 0.1);
60201
+ }
60202
+ </style>
60203
+ </head>
60204
+ <body class="bg-[#0f172a] text-gray-200 min-h-screen flex items-center justify-center relative overflow-hidden">
60205
+ <!-- Background Decor -->
60206
+ <div class="absolute top-0 left-0 w-full h-full overflow-hidden z-0 pointer-events-none">
60207
+ <div class="absolute top-1/4 left-1/4 w-96 h-96 bg-blue-500/20 rounded-full blur-[100px]"></div>
60208
+ <div class="absolute bottom-1/4 right-1/4 w-96 h-96 bg-yellow-500/10 rounded-full blur-[100px]"></div>
60209
+ </div>
60210
+
60211
+ <!-- Main Content -->
60212
+ <div class="z-10 w-full max-w-2xl px-4">
60213
+ <div class="glass rounded-2xl p-8 md:p-12 shadow-2xl border border-white/5 transform transition-all hover:scale-[1.01]">
60214
+ <div class="flex items-center justify-between mb-8">
60215
+ <div class="flex items-center space-x-3">
60216
+ <span class="text-4xl">\u{1F40D}</span>
60217
+ <h1 class="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-yellow-300">
60218
+ Python Backend
60219
+ </h1>
60220
+ </div>
60221
+ <div class="flex items-center space-x-2">
60222
+ <div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
60223
+ <span class="text-xs font-mono text-green-400 uppercase tracking-widest">Online</span>
60224
+ </div>
60225
+ </div>
60226
+
60227
+ <div class="space-y-6">
60228
+ <p class="text-xl text-gray-300 leading-relaxed font-light">
60229
+ Python server is running.
60230
+ </p>
60231
+
60232
+ <div class="p-4 rounded-lg bg-black/30 border border-white/10 font-mono text-sm text-gray-400">
60233
+ <p>$ python main.py</p>
60234
+ <p class="text-green-400">>> Server started at http://localhost:3000</p>
60235
+ </div>
60236
+
60237
+ <div class="pt-4 flex flex-col sm:flex-row gap-4">
60238
+ <a href="/test" class="group relative px-8 py-3 bg-blue-600 hover:bg-blue-500 rounded-lg font-bold text-white transition-all shadow-[0_0_20px_rgba(37,99,235,0.3)] hover:shadow-[0_0_30px_rgba(37,99,235,0.5)] overflow-hidden">
60239
+ <span class="relative z-10 flex items-center justify-center gap-2">
60240
+ Test Endpoint
60241
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
60242
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
60243
+ </svg>
60244
+ </span>
60245
+ </a>
60246
+
60247
+ <a href="https://docs.python.org/3/library/http.server.html" target="_blank" class="px-8 py-3 bg-transparent border border-white/20 hover:bg-white/5 rounded-lg font-bold text-gray-300 transition-colors text-center">
60248
+ Docs
60249
+ </a>
60250
+ </div>
60251
+ </div>
60252
+ </div>
60253
+
60254
+ <div class="mt-8 text-center text-sm text-gray-500">
60255
+ <p>Powered by Python Standard Library & Tailwind CSS</p>
60256
+ </div>
60257
+ </div>
60258
+ </body>
60259
+ </html>
61217
60260
  `;
60261
+ var python_default = {
60262
+ mainPy,
60263
+ indexHtml
60264
+ };
60265
+
60266
+ // ../../packages/template/projects/python.ts
61218
60267
  var PythonConsole = {
61219
- name: "Python Console",
61220
- description: "Simple Python Console Application",
60268
+ name: "Python Backend",
60269
+ description: "Simple Python Backend Application",
61221
60270
  notes: "Python 3 must be installed in your system.",
61222
60271
  templating: [
61223
60272
  {
61224
60273
  action: "file",
61225
60274
  file: "main.py",
61226
- filecontent: pythonFile
60275
+ filecontent: python_default.mainPy
60276
+ },
60277
+ {
60278
+ action: "file",
60279
+ file: "index.html",
60280
+ filecontent: python_default.indexHtml
60281
+ },
60282
+ {
60283
+ action: "command",
60284
+ cmd: "npm",
60285
+ args: ["install", "-D", "nodemon"]
61227
60286
  },
61228
60287
  {
61229
60288
  action: "command",
61230
60289
  cmd: "npm",
61231
- args: ["pkg", "set", "scripts.dev=python3 main.py"]
60290
+ args: ["pkg", "set", "scripts.dev=nodemon --watch . --ext py --exec python3 main.py"]
61232
60291
  },
61233
60292
  {
61234
60293
  action: "command",
61235
60294
  cmd: "npm",
61236
60295
  args: ["pkg", "set", "scripts.start=python3 main.py"]
61237
60296
  },
60297
+ {
60298
+ action: "command",
60299
+ cmd: "npm",
60300
+ args: ["pkg", "set", "description=Python Backend"]
60301
+ },
60302
+ {
60303
+ action: "command",
60304
+ cmd: "npm",
60305
+ args: ["pkg", "set", "scripts.stop=npx kill-port 3000"]
60306
+ },
61238
60307
  {
61239
60308
  action: "command",
61240
60309
  cmd: "npm",
@@ -61278,7 +60347,12 @@ var DotNetConsole = {
61278
60347
  {
61279
60348
  action: "command",
61280
60349
  cmd: "npm",
61281
- args: ["pkg", "set", "fontawesomeIcon=fab fa-windows text-blue-600"]
60350
+ args: ["pkg", "set", "description=.NET Console"]
60351
+ },
60352
+ {
60353
+ action: "command",
60354
+ cmd: "npm",
60355
+ args: ["pkg", "set", "fontawesomeIcon=fab fa-windows text-blue-500"]
61282
60356
  }
61283
60357
  ]
61284
60358
  };
@@ -61327,6 +60401,11 @@ var N8NLocal = {
61327
60401
  cmd: "npm",
61328
60402
  args: ["pkg", "set", "fontawesomeIcon=fas fa-project-diagram text-red-500"]
61329
60403
  },
60404
+ {
60405
+ action: "command",
60406
+ cmd: "npm",
60407
+ args: ["pkg", "set", "description=N8N (Local)"]
60408
+ },
61330
60409
  {
61331
60410
  action: "command",
61332
60411
  cmd: "npm",
@@ -61568,7 +60647,7 @@ networks:
61568
60647
  driver: bridge`;
61569
60648
 
61570
60649
  // ../../packages/template/services_list/aws/indexHtml.ts
61571
- var indexHtml = `<!DOCTYPE html>
60650
+ var indexHtml2 = `<!DOCTYPE html>
61572
60651
  <html lang="en" class="dark">
61573
60652
  <head>
61574
60653
  <meta charset="UTF-8">
@@ -61904,7 +60983,7 @@ spawn('docker', ['compose', 'down'], { stdio: 'inherit' });`;
61904
60983
 
61905
60984
  // ../../packages/template/services_list/aws.ts
61906
60985
  var AWSTemplate = {
61907
- name: "AWS Local",
60986
+ name: "Localstack (Experimental)",
61908
60987
  description: "AWS LocalStack Environment with Manager",
61909
60988
  notes: "Requires Docker, Node.js, and AWS CLI installed.",
61910
60989
  templating: [
@@ -61931,7 +61010,7 @@ var AWSTemplate = {
61931
61010
  {
61932
61011
  action: "file",
61933
61012
  file: "index.html",
61934
- filecontent: indexHtml
61013
+ filecontent: indexHtml2
61935
61014
  },
61936
61015
  {
61937
61016
  action: "command",
@@ -61961,7 +61040,12 @@ var AWSTemplate = {
61961
61040
  {
61962
61041
  action: "command",
61963
61042
  cmd: "npm",
61964
- args: ["pkg", "set", "fontawesomeIcon=fab fa-aws text-orange-400"]
61043
+ args: ["pkg", "set", "fontawesomeIcon=fab fa-aws text-orange-500"]
61044
+ },
61045
+ {
61046
+ action: "command",
61047
+ cmd: "npm",
61048
+ args: ["pkg", "set", "description=AWS LocalStack Environment with Manager"]
61965
61049
  },
61966
61050
  {
61967
61051
  action: "command",
@@ -62142,7 +61226,7 @@ const stripe = new Stripe('sk_test_mock_123', {
62142
61226
  })();
62143
61227
  `;
62144
61228
  var StripeTemplate = {
62145
- name: "Stripe Mock",
61229
+ name: "Stripe Mock (Experimental)",
62146
61230
  description: "Stripe API Mock Server",
62147
61231
  notes: "Runs the official stripe-mock image. Requires Docker.",
62148
61232
  templating: [
@@ -62191,6 +61275,11 @@ var StripeTemplate = {
62191
61275
  cmd: "npm",
62192
61276
  args: ["pkg", "set", "fontawesomeIcon=fas fa-credit-card text-green-500"]
62193
61277
  },
61278
+ {
61279
+ action: "command",
61280
+ cmd: "npm",
61281
+ args: ["pkg", "set", "description=Stripe Mock"]
61282
+ },
62194
61283
  {
62195
61284
  action: "command",
62196
61285
  cmd: "npm",
@@ -62217,7 +61306,7 @@ var MonorepoTemplates = {
62217
61306
  var template_default = MonorepoTemplates;
62218
61307
 
62219
61308
  // src/routes/availabletemplates.ts
62220
- var router18 = import_express21.default.Router();
61309
+ var router18 = import_express22.default.Router();
62221
61310
  router18.get("/", (req, res) => {
62222
61311
  try {
62223
61312
  const stripTemplating = (templates5) => {
@@ -62238,7 +61327,7 @@ router18.get("/", (req, res) => {
62238
61327
  var availabletemplates_default = router18;
62239
61328
 
62240
61329
  // src/routes/setworkspace/index.ts
62241
- var import_express22 = __toESM(require_express2());
61330
+ var import_express23 = __toESM(require_express2());
62242
61331
 
62243
61332
  // src/routes/setworkspace/template.ts
62244
61333
  var import_path17 = __toESM(require("path"));
@@ -62261,6 +61350,33 @@ async function writeFile(filePath, content) {
62261
61350
  await import_fs3.promises.writeFile(filePath, content, { encoding: "utf8" });
62262
61351
  }
62263
61352
  var isWindows = process.platform === "win32";
61353
+ async function findMonorepoRoot4(startDir) {
61354
+ let currentDir = startDir;
61355
+ while (true) {
61356
+ const markers = ["pnpm-workspace.yaml", "turbo.json", "lerna.json", ".git"];
61357
+ for (const marker of markers) {
61358
+ try {
61359
+ await import_fs3.promises.stat(import_path15.default.join(currentDir, marker));
61360
+ return currentDir;
61361
+ } catch {
61362
+ }
61363
+ }
61364
+ try {
61365
+ const pkgPath = import_path15.default.join(currentDir, "package.json");
61366
+ const content = await import_fs3.promises.readFile(pkgPath, "utf8");
61367
+ const pkg = JSON.parse(content);
61368
+ if (pkg.workspaces) {
61369
+ return currentDir;
61370
+ }
61371
+ } catch {
61372
+ }
61373
+ const parentDir = import_path15.default.dirname(currentDir);
61374
+ if (parentDir === currentDir) {
61375
+ return startDir;
61376
+ }
61377
+ currentDir = parentDir;
61378
+ }
61379
+ }
62264
61380
 
62265
61381
  // src/routes/setworkspace/command.ts
62266
61382
  var import_path16 = __toESM(require("path"));
@@ -62358,11 +61474,17 @@ async function executeTemplate(template, workspacePath, onProgress) {
62358
61474
  await ensureDirectory(workspacePath);
62359
61475
  const progress = onProgress || ((msg) => console.log(`[Template] ${msg}`));
62360
61476
  for (const step of template.templating) {
62361
- if (step.action === "command" && step.cmd) {
61477
+ if ((step.action === "command" || step.action === "root-command") && step.cmd) {
62362
61478
  const cmd = step.cmd;
62363
61479
  let args2 = step.args || [];
61480
+ let cwd = workspacePath;
61481
+ if (step.action === "root-command") {
61482
+ cwd = await findMonorepoRoot4(workspacePath);
61483
+ }
61484
+ let relativePath = import_path17.default.relative(cwd, workspacePath);
61485
+ if (relativePath === "") relativePath = ".";
62364
61486
  const rawFullCmd = args2.length > 0 ? `${cmd} ${args2.join(" ")}` : cmd;
62365
- const processedCmd = preprocessCommand(rawFullCmd, workspacePath);
61487
+ const processedCmd = preprocessCommand(rawFullCmd, cwd);
62366
61488
  let finalCmd = cmd;
62367
61489
  let finalArgs = args2;
62368
61490
  let useShell = false;
@@ -62370,13 +61492,16 @@ async function executeTemplate(template, workspacePath, onProgress) {
62370
61492
  finalCmd = processedCmd;
62371
61493
  finalArgs = [];
62372
61494
  useShell = true;
61495
+ finalCmd = finalCmd.replace(/\{\{RELATIVE_PATH\}\}/g, relativePath);
62373
61496
  } else {
62374
- const dirName = import_path17.default.basename(workspacePath);
62375
- finalArgs = finalArgs.map((arg) => arg.replace(/\$\(basename \$PWD\)/g, dirName));
61497
+ const dirName = import_path17.default.basename(cwd);
61498
+ finalArgs = finalArgs.map(
61499
+ (arg) => arg.replace(/\$\(basename \$PWD\)/g, dirName).replace(/\{\{RELATIVE_PATH\}\}/g, relativePath)
61500
+ );
62376
61501
  }
62377
- progress(`Running: ${finalCmd} ${finalArgs.join(" ")}`);
61502
+ progress(`Running in ${step.action === "root-command" ? "Root" : "Workspace"}: ${finalCmd} ${finalArgs.join(" ")}`);
62378
61503
  try {
62379
- const result = await runCommand2(finalCmd, finalArgs, workspacePath, useShell, (data) => {
61504
+ const result = await runCommand2(finalCmd, finalArgs, cwd, useShell, (data) => {
62380
61505
  const trimmed = data.trim();
62381
61506
  if (trimmed) progress(trimmed);
62382
61507
  });
@@ -62396,7 +61521,7 @@ ${cmdErr.message}`);
62396
61521
  }
62397
61522
 
62398
61523
  // src/routes/setworkspace/index.ts
62399
- var router19 = import_express22.default.Router();
61524
+ var router19 = import_express23.default.Router();
62400
61525
  router19.post("/", async (req, res) => {
62401
61526
  try {
62402
61527
  const { workspace, templatename } = req.body;
@@ -62451,12 +61576,12 @@ function setWorkspaceTemplateSocket(io3) {
62451
61576
  }
62452
61577
 
62453
61578
  // src/routes/stopTerminalWorkspace.ts
62454
- var import_express23 = __toESM(require_express2());
61579
+ var import_express24 = __toESM(require_express2());
62455
61580
  var import_fs_extra13 = __toESM(require_lib4());
62456
61581
  var import_path18 = __toESM(require("path"));
62457
61582
  init_execa();
62458
61583
  var import_tree_kill = __toESM(require_tree_kill());
62459
- var router20 = (0, import_express23.Router)();
61584
+ var router20 = (0, import_express24.Router)();
62460
61585
  async function killProcessTree(pid, signal = "SIGKILL") {
62461
61586
  return new Promise((resolve, reject) => {
62462
61587
  (0, import_tree_kill.default)(pid, signal, (err) => {
@@ -62631,14 +61756,14 @@ if (command === "init") {
62631
61756
  process.exit(1);
62632
61757
  });
62633
61758
  }
62634
- var app2 = (0, import_express24.default)();
61759
+ var app2 = (0, import_express25.default)();
62635
61760
  var port2 = config_default.apiPort;
62636
61761
  app2.use((0, import_cors.default)({
62637
61762
  origin: true,
62638
61763
  credentials: true
62639
61764
  }));
62640
- app2.use(import_express24.default.static("public"));
62641
- app2.use(import_express24.default.json());
61765
+ app2.use(import_express25.default.static("public"));
61766
+ app2.use(import_express25.default.json());
62642
61767
  app2.use("/", tester_default);
62643
61768
  app2.use("/" + api_default.scanWorkspace, scanworkspace_default);
62644
61769
  app2.use("/" + api_default.stopProcess, stopcmd_default);
@@ -62662,7 +61787,7 @@ app2.use("/" + api_default.docker, apidocker_default);
62662
61787
  app2.use("/" + api_default.availabletemplates, availabletemplates_default);
62663
61788
  app2.use("/" + api_default.setWorkspaceTemplate, setworkspace_default);
62664
61789
  var frontendPath = import_path19.default.join(__dirname, "../public");
62665
- app2.use(import_express24.default.static(frontendPath));
61790
+ app2.use(import_express25.default.static(frontendPath));
62666
61791
  app2.get("*", (req, res) => {
62667
61792
  res.sendFile(import_path19.default.join(frontendPath, "index.html"));
62668
61793
  });