hologramicon commited on
Commit
1b5663e
·
verified ·
1 Parent(s): 0c39f90

Upload 19 files

Browse files
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .env
4
+ .vscode
Dockerfile CHANGED
@@ -1,13 +1,24 @@
1
- FROM node:20-alpine
2
  WORKDIR /app
 
 
 
 
 
 
3
 
4
- COPY package.json ./
5
- COPY package-lock.json ./
6
- RUN npm install --production
7
-
8
  COPY . .
 
 
9
 
10
- RUN npm run build
11
-
 
 
 
 
 
12
  EXPOSE 4000
13
- CMD ["node", "dist/index.js"]
 
1
+ FROM node:20-alpine AS deps
2
  WORKDIR /app
3
+ COPY package.json .
4
+ COPY server/package.json ./server/package.json
5
+ COPY client/package.json ./client/package.json
6
+ RUN apk add --no-cache git
7
+ RUN cd server && npm install
8
+ RUN cd ../client && npm install
9
 
10
+ FROM node:20-alpine AS builder
11
+ WORKDIR /app
 
 
12
  COPY . .
13
+ RUN cd client && npm run build
14
+ RUN cd server && npm run build
15
 
16
+ FROM node:20-alpine AS runner
17
+ WORKDIR /app
18
+ ENV NODE_ENV=production
19
+ COPY --from=builder /app/server/dist ./server/dist
20
+ COPY --from=builder /app/client/dist ./client/dist
21
+ COPY server/package.json ./server/package.json
22
+ RUN cd server && npm install --production
23
  EXPOSE 4000
24
+ CMD ["node","server/dist/index.js"]
client/index.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Neuro Galaxies</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
client/package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "neuro-client",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0",
14
+ "three": "^0.152.2"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.5.0",
18
+ "vite": "^5.0.0",
19
+ "@vitejs/plugin-react": "^4.2.1",
20
+ "@types/react": "^18.2.28",
21
+ "@types/react-dom": "^18.2.11"
22
+ }
23
+ }
client/src/App.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import React from 'react'
2
+ import Scene from './scene/Scene'
3
+ export default function App(){ return <Scene/> }
client/src/api.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export async function generate(prompt:string){
2
+ const res = await fetch('/api/generate',{ method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ prompt }) })
3
+ return res.json()
4
+ }
client/src/main.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import App from './App'
4
+ import './styles.css'
5
+ createRoot(document.getElementById('root')!).render(<App />)
client/src/scene/Scene.tsx ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import * as THREE from 'three'
3
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
4
+ export default function Scene(): JSX.Element {
5
+ const mountRef = useRef<HTMLDivElement|null>(null)
6
+ const [ready,setReady] = useState(false)
7
+ useEffect(()=>{
8
+ const container = mountRef.current!
9
+ const scene = new THREE.Scene()
10
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000)
11
+ camera.position.set(0,0,20)
12
+ const renderer = new THREE.WebGLRenderer({ antialias:true })
13
+ renderer.setSize(window.innerWidth, window.innerHeight)
14
+ renderer.setPixelRatio(window.devicePixelRatio)
15
+ container.appendChild(renderer.domElement)
16
+ const controls = new OrbitControls(camera, renderer.domElement)
17
+ controls.enableDamping = true
18
+ const ambient = new THREE.AmbientLight(0xffffff,0.7); scene.add(ambient)
19
+ const dir = new THREE.DirectionalLight(0xffffff,0.6); dir.position.set(5,10,7.5); scene.add(dir)
20
+ const group = new THREE.Group(); scene.add(group)
21
+ // comet particles system
22
+ const trails: {mesh:THREE.Mesh, life:number}[] = []
23
+ function addComet(word:string){
24
+ const geom = new THREE.SphereGeometry(0.12,8,8)
25
+ const mat = new THREE.MeshStandardMaterial({ color: new THREE.Color(`hsl(${Math.random()*360},80%,60%)`), emissive:0x111111 })
26
+ const m = new THREE.Mesh(geom, mat)
27
+ m.position.set((Math.random()-0.5)*30, (Math.random()-0.5)*20, (Math.random()-0.5)*30)
28
+ (m.userData as any).vel = new THREE.Vector3((Math.random()-0.5)*0.6, (Math.random()-0.5)*0.6, (Math.random()-0.5)*0.6)
29
+ (m.userData as any).word = word
30
+ group.add(m)
31
+ trails.push({mesh:m, life:120})
32
+ }
33
+ // text labels as sprites
34
+ const spriteMaterial = new THREE.SpriteMaterial({ color: 0xffffff })
35
+ function addLabel(text:string, pos:THREE.Vector3){
36
+ const canvas = document.createElement('canvas')
37
+ canvas.width = 256; canvas.height = 64
38
+ const ctx = canvas.getContext('2d')!
39
+ ctx.fillStyle = 'rgba(255,255,255,0.9)'; ctx.font = '30px sans-serif'
40
+ ctx.fillText(text, 8,40)
41
+ const tex = new THREE.CanvasTexture(canvas)
42
+ const mat = new THREE.SpriteMaterial({ map:tex, transparent:true })
43
+ const sp = new THREE.Sprite(mat)
44
+ sp.scale.set(4,1,1); sp.position.copy(pos)
45
+ (sp.userData as any).isLabel = true
46
+ group.add(sp)
47
+ }
48
+ // generate initial galaxy nodes from demo algorithm
49
+ function spawnGalaxy(topic:string, origin:THREE.Vector3){
50
+ const mainCount = Math.max(3, Math.min(12, Math.floor(Math.random()*10)))
51
+ for(let i=0;i<mainCount;i++){
52
+ const theta = Math.random()*Math.PI*2
53
+ const r = 6 + Math.random()*6
54
+ const x = origin.x + Math.cos(theta)*r
55
+ const y = origin.y + (Math.random()-0.5)*4
56
+ const z = origin.z + Math.sin(theta)*r
57
+ const node = new THREE.Mesh(new THREE.SphereGeometry(0.25,10,10), new THREE.MeshStandardMaterial({ color: new THREE.Color(`hsl(${(i/mainCount)*360},80%,60%)`) }))
58
+ node.position.set(x,y,z)
59
+ group.add(node)
60
+ addLabel('n'+i, node.position.clone().add(new THREE.Vector3(0.6,0.6,0)))
61
+ // create comets passing near node
62
+ if(Math.random()<0.6){
63
+ addComet('⭐')
64
+ }
65
+ }
66
+ }
67
+ // add a root
68
+ const root = new THREE.Mesh(new THREE.SphereGeometry(0.5,16,16), new THREE.MeshStandardMaterial({ color: 0x66ffcc }))
69
+ root.position.set(0,0,0); group.add(root); addLabel('ROOT', root.position.clone().add(new THREE.Vector3(0,1,0)))
70
+ spawnGalaxy('example', new THREE.Vector3(0,0,0))
71
+ // comet update and trail rendering (simple)
72
+ const trailGroup = new THREE.Group(); scene.add(trailGroup)
73
+ function animate(){
74
+ requestAnimationFrame(animate)
75
+ controls.update()
76
+ // update comets
77
+ for(let i=trails.length-1;i>=0;i--){
78
+ const t = trails[i]
79
+ t.mesh.position.add((t.mesh.userData as any).vel)
80
+ t.life -= 1
81
+ // spawn small fading dots to simulate trail
82
+ const dot = new THREE.Mesh(new THREE.SphereGeometry(0.04,6,6), new THREE.MeshBasicMaterial({ color: (t.mesh.material as any).color, transparent:true, opacity:0.6 }))
83
+ dot.position.copy(t.mesh.position)
84
+ trailGroup.add(dot)
85
+ // fade dots
86
+ setTimeout(()=>{ try{ trailGroup.remove(dot); dot.geometry.dispose(); (dot.material as any).dispose() }catch(e){} }, 900)
87
+ if(t.life<=0){
88
+ try{ group.remove(t.mesh); t.mesh.geometry.dispose(); (t.mesh.material as any).dispose() }catch(e){}
89
+ trails.splice(i,1)
90
+ }
91
+ }
92
+ renderer.render(scene, camera)
93
+ }
94
+ animate()
95
+ const onResize = ()=>{ camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight) }
96
+ window.addEventListener('resize', onResize)
97
+ setReady(true)
98
+ return ()=>{
99
+ window.removeEventListener('resize', onResize)
100
+ try{ container.removeChild(renderer.domElement) }catch(e){}
101
+ }
102
+ },[])
103
+ return <div style={{width:'100vw',height:'100vh',position:'relative'}} ref={mountRef}>
104
+ <div style={{position:'absolute',left:20,top:20,zIndex:3,background:'rgba(10,10,10,0.6)',padding:12,borderRadius:8}}>
105
+ <input id="topic" placeholder="hashtag or topic" style={{width:300}} />
106
+ </div>
107
+ </div>
108
+ }
client/src/styles.css ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ :root{--bg:#0b1020}
2
+ html,body,#root{height:100%;margin:0;background:var(--bg);color:#fff;font-family:Inter,system-ui}
client/tsconfig.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "jsx": "react-jsx",
6
+ "moduleResolution": "Node",
7
+ "strict": true,
8
+ "esModuleInterop": true
9
+ },
10
+ "include": ["src"]
11
+ }
docker-compose.yml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+ services:
3
+ app:
4
+ build: .
5
+ env_file:
6
+ - server/.env.example
7
+ ports:
8
+ - "4000:4000"
package.json CHANGED
@@ -1,12 +1,12 @@
1
  {
2
- "name": "hf-react-node-starter",
3
  "private": true,
4
- "workspaces": ["client", "server"],
 
 
 
5
  "scripts": {
6
- "dev": "concurrently \"pnpm --filter client dev\" \"pnpm --filter server dev\"",
7
- "install:all": "pnpm install --recursive"
8
  },
9
- "devDependencies": {
10
- "concurrently": "^8.2.0"
11
- }
12
  }
 
1
  {
2
+ "name": "neuro-galaxy-monorepo",
3
  "private": true,
4
+ "workspaces": [
5
+ "client",
6
+ "server"
7
+ ],
8
  "scripts": {
9
+ "start": "echo use workspace scripts"
 
10
  },
11
+ "devDependencies": {}
 
 
12
  }
server/.env.example ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ HF_API_KEY=
2
+ GEMINI_API_KEY=
3
+ MODEL_PROVIDER=hf
4
+ PORT=4000
5
+ FIREBASE_SERVICE_ACCOUNT=
server/package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "neuro-server",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
7
+ "build": "tsc -p .",
8
+ "start": "node dist/index.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "cors": "^2.8.5",
13
+ "dotenv": "^16.3.1",
14
+ "node-fetch": "^3.4.2"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.5.0",
18
+ "ts-node-dev": "^2.0.0",
19
+ "@types/node": "^20.5.6",
20
+ "@types/express": "^4.17.21"
21
+ }
22
+ }
server/src/index.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express'
2
+ import cors from 'cors'
3
+ import dotenv from 'dotenv'
4
+ import apiRouter from './routes/api.js'
5
+ dotenv.config()
6
+ const app = express()
7
+ app.use(cors())
8
+ app.use(express.json())
9
+ app.use('/api', apiRouter)
10
+ const port = Number(process.env.PORT||4000)
11
+ app.listen(port, ()=>{ console.log('server listening', port) })
server/src/routes/api.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router } from 'express'
2
+ import { proxyHF } from '../services/hf.js'
3
+ import { proxyGemini } from '../services/gemini.js'
4
+ const router = Router()
5
+ router.post('/generate', async (req, res) => {
6
+ const { prompt } = req.body
7
+ if (!prompt) return res.status(400).json({ error: 'prompt required' })
8
+ try {
9
+ const provider = process.env.MODEL_PROVIDER || 'hf'
10
+ const out = provider === 'gemini' ? await proxyGemini(prompt) : await proxyHF(prompt)
11
+ res.json({ output: out })
12
+ } catch (e) {
13
+ res.status(500).json({ error: String(e) })
14
+ }
15
+ })
16
+ export default router
server/src/services/gemini.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch'
2
+ const KEY = process.env.GEMINI_API_KEY
3
+ const BASE = 'https://generativelanguage.googleapis.com/v1beta2/models'
4
+ export async function proxyGemini(prompt:string){
5
+ if(!KEY) throw new Error('GEMINI_API_KEY missing')
6
+ const model = process.env.GEMINI_MODEL||'models/text-bison-001'
7
+ const url = `${BASE}/${model}:generate`
8
+ const res = await fetch(url, { method:'POST', headers: { Authorization:`Bearer ${KEY}`, 'Content-Type':'application/json' }, body: JSON.stringify({ prompt }) })
9
+ const j = await res.json()
10
+ if(j?.candidates?.[0]?.content) return j.candidates[0].content
11
+ if(j?.outputText) return j.outputText
12
+ return JSON.stringify(j)
13
+ }
server/src/services/hf.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch'
2
+ const KEY = process.env.HF_API_KEY
3
+ const MODEL = process.env.HF_DEFAULT_MODEL || 'gpt2'
4
+ const URL = `https://api-inference.huggingface.co/models/${MODEL}`
5
+ export async function proxyHF(prompt:string){
6
+ if(!KEY) throw new Error('HF_API_KEY missing')
7
+ const r = await fetch(URL, { method: 'POST', headers: { Authorization: `Bearer ${KEY}`, 'Content-Type':'application/json' }, body: JSON.stringify({ inputs: prompt }) })
8
+ const j = await r.json()
9
+ if(Array.isArray(j) && j[0]?.generated_text) return j[0].generated_text
10
+ return j.generated_text || JSON.stringify(j)
11
+ }
server/tsconfig.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "dist",
4
+ "rootDir": "src",
5
+ "module": "ESNext",
6
+ "target": "ES2022",
7
+ "moduleResolution": "Node",
8
+ "strict": true,
9
+ "esModuleInterop": true
10
+ },
11
+ "include": ["src"]
12
+ }