bahadur3093 commited on
Commit
227e8b7
·
1 Parent(s): 6bc4b86

next js ui

Browse files
Files changed (9) hide show
  1. Dockerfile +14 -4
  2. entrypoint.sh +8 -12
  3. next.config.js +6 -0
  4. package.json +19 -0
  5. pages/_app.tsx +6 -0
  6. pages/api/generate.ts +29 -0
  7. pages/index.tsx +75 -0
  8. styles/globals.css +101 -0
  9. tsconfig.json +20 -0
Dockerfile CHANGED
@@ -1,14 +1,24 @@
1
  FROM ubuntu:22.04
2
 
3
- RUN apt-get update && apt-get install -y curl zstd && \
4
- curl -fsSL https://ollama.com/install.sh | sh
 
 
 
5
 
6
- ENV OLLAMA_HOST=0.0.0.0:7860
7
  ENV OLLAMA_ORIGINS=*
8
  ENV HOME=/tmp
9
  ENV OLLAMA_MODELS=/tmp/.ollama/models
10
 
11
- # This is the line that failed because the file was missing
 
 
 
 
 
 
 
12
  COPY entrypoint.sh /entrypoint.sh
13
  RUN chmod +x /entrypoint.sh
14
 
 
1
  FROM ubuntu:22.04
2
 
3
+ RUN apt-get update && apt-get install -y curl zstd ca-certificates gnupg \
4
+ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
5
+ && apt-get install -y nodejs \
6
+ && curl -fsSL https://ollama.com/install.sh | sh \
7
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
8
 
9
+ ENV OLLAMA_HOST=0.0.0.0:11434
10
  ENV OLLAMA_ORIGINS=*
11
  ENV HOME=/tmp
12
  ENV OLLAMA_MODELS=/tmp/.ollama/models
13
 
14
+ WORKDIR /app
15
+
16
+ COPY package.json tsconfig.json next.config.js ./
17
+ RUN npm install
18
+
19
+ COPY . .
20
+ RUN npm run build
21
+
22
  COPY entrypoint.sh /entrypoint.sh
23
  RUN chmod +x /entrypoint.sh
24
 
entrypoint.sh CHANGED
@@ -1,26 +1,22 @@
1
  #!/bin/bash
2
 
3
- echo "🚀 Starting Ollama server..."
4
  ollama serve &
5
 
6
- # Wait for Ollama to be ready
7
- echo "⏳ Waiting for Ollama to start..."
8
  sleep 8
9
 
10
- # Optional: preload a default model (DO NOT force remove anything)
11
  DEFAULT_MODEL="qwen2.5:1.5b"
12
 
13
- echo "📦 Checking if default model exists..."
14
  if ! ollama list | grep -q "$DEFAULT_MODEL"; then
15
- echo "⬇️ Pulling default model: $DEFAULT_MODEL"
16
  ollama pull $DEFAULT_MODEL
17
  else
18
- echo "✅ Model already exists: $DEFAULT_MODEL"
19
  fi
20
 
21
- # Start your UI (Gradio app)
22
- echo "🎨 Starting UI..."
23
- python app.py
24
 
25
- # Keep container alive
26
- wait
 
1
  #!/bin/bash
2
 
3
+ echo "🚀 Starting Ollama server on :11434..."
4
  ollama serve &
5
 
 
 
6
  sleep 8
7
 
 
8
  DEFAULT_MODEL="qwen2.5:1.5b"
9
 
10
+ echo "📦 Checking model..."
11
  if ! ollama list | grep -q "$DEFAULT_MODEL"; then
12
+ echo "⬇️ Pulling: $DEFAULT_MODEL"
13
  ollama pull $DEFAULT_MODEL
14
  else
15
+ echo "✅ Model ready: $DEFAULT_MODEL"
16
  fi
17
 
18
+ echo "🎨 Starting Next.js UI on :7860..."
19
+ cd /app
20
+ npm start
21
 
22
+ wait
 
next.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ }
5
+
6
+ module.exports = nextConfig
package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ollama-ui",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "dev": "next dev -p 7860 -H 0.0.0.0",
6
+ "build": "next build",
7
+ "start": "next start -p 7860 -H 0.0.0.0"
8
+ },
9
+ "dependencies": {
10
+ "next": "14.2.3",
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.12.0",
16
+ "@types/react": "^18.3.0",
17
+ "typescript": "^5.4.0"
18
+ }
19
+ }
pages/_app.tsx ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import type { AppProps } from "next/app"
2
+ import "../styles/globals.css"
3
+
4
+ export default function App({ Component, pageProps }: AppProps) {
5
+ return <Component {...pageProps} />
6
+ }
pages/api/generate.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextApiRequest, NextApiResponse } from "next"
2
+
3
+ export default async function handler(
4
+ req: NextApiRequest,
5
+ res: NextApiResponse
6
+ ) {
7
+ if (req.method !== "POST") {
8
+ return res.status(405).json({ error: "Method not allowed" })
9
+ }
10
+
11
+ const { model, prompt } = req.body
12
+
13
+ try {
14
+ const ollamaRes = await fetch("http://localhost:11434/api/generate", {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ body: JSON.stringify({
18
+ model,
19
+ prompt,
20
+ stream: false,
21
+ }),
22
+ })
23
+
24
+ const data = await ollamaRes.json()
25
+ res.status(200).json(data)
26
+ } catch (err) {
27
+ res.status(500).json({ error: "Failed to connect to Ollama" })
28
+ }
29
+ }
pages/index.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react"
2
+
3
+ const models = [
4
+ "qwen2.5:1.5b",
5
+ "mistral",
6
+ "tinyllama",
7
+ "hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF",
8
+ ]
9
+
10
+ export default function Home() {
11
+ const [model, setModel] = useState(models[0])
12
+ const [prompt, setPrompt] = useState("")
13
+ const [response, setResponse] = useState("")
14
+ const [loading, setLoading] = useState(false)
15
+
16
+ const generate = async () => {
17
+ if (!prompt.trim()) return
18
+ setLoading(true)
19
+ setResponse("")
20
+
21
+ try {
22
+ const res = await fetch("/api/generate", {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify({ model, prompt }),
26
+ })
27
+ const data = await res.json()
28
+ setResponse(data.response || data.error || "No response")
29
+ } catch {
30
+ setResponse("❌ Failed to connect to server")
31
+ }
32
+
33
+ setLoading(false)
34
+ }
35
+
36
+ return (
37
+ <main className="container">
38
+ <h1>⚡ Ollama UI</h1>
39
+ <p className="subtitle">Self-hosted LLM on Hugging Face</p>
40
+
41
+ <div className="field">
42
+ <label>Model</label>
43
+ <select value={model} onChange={(e) => setModel(e.target.value)}>
44
+ {models.map((m) => (
45
+ <option key={m} value={m}>{m}</option>
46
+ ))}
47
+ </select>
48
+ </div>
49
+
50
+ <div className="field">
51
+ <label>Prompt</label>
52
+ <textarea
53
+ rows={5}
54
+ placeholder="Type your prompt here..."
55
+ value={prompt}
56
+ onChange={(e) => setPrompt(e.target.value)}
57
+ onKeyDown={(e) => {
58
+ if (e.key === "Enter" && e.metaKey) generate()
59
+ }}
60
+ />
61
+ </div>
62
+
63
+ <button onClick={generate} disabled={loading}>
64
+ {loading ? "⏳ Generating..." : "🚀 Generate"}
65
+ </button>
66
+
67
+ <div className="response">
68
+ <label>Response</label>
69
+ <div className="response-box">
70
+ {response || "Response will appear here..."}
71
+ </div>
72
+ </div>
73
+ </main>
74
+ )
75
+ }
styles/globals.css ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ body {
8
+ background: #0a0a0a;
9
+ color: #e5e5e5;
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11
+ }
12
+
13
+ .container {
14
+ max-width: 720px;
15
+ margin: 0 auto;
16
+ padding: 48px 24px;
17
+ }
18
+
19
+ h1 {
20
+ font-size: 28px;
21
+ margin-bottom: 4px;
22
+ }
23
+
24
+ .subtitle {
25
+ color: #888;
26
+ font-size: 14px;
27
+ margin-bottom: 32px;
28
+ }
29
+
30
+ .field {
31
+ margin-bottom: 20px;
32
+ }
33
+
34
+ label {
35
+ display: block;
36
+ font-size: 13px;
37
+ color: #999;
38
+ margin-bottom: 6px;
39
+ text-transform: uppercase;
40
+ letter-spacing: 0.5px;
41
+ }
42
+
43
+ select,
44
+ textarea {
45
+ width: 100%;
46
+ background: #141414;
47
+ color: #e5e5e5;
48
+ border: 1px solid #2a2a2a;
49
+ border-radius: 8px;
50
+ padding: 12px;
51
+ font-size: 15px;
52
+ outline: none;
53
+ transition: border 0.2s;
54
+ }
55
+
56
+ select:focus,
57
+ textarea:focus {
58
+ border-color: #555;
59
+ }
60
+
61
+ textarea {
62
+ resize: vertical;
63
+ font-family: inherit;
64
+ }
65
+
66
+ button {
67
+ background: #fff;
68
+ color: #0a0a0a;
69
+ border: none;
70
+ border-radius: 8px;
71
+ padding: 12px 24px;
72
+ font-size: 15px;
73
+ font-weight: 600;
74
+ cursor: pointer;
75
+ transition: opacity 0.2s;
76
+ }
77
+
78
+ button:hover {
79
+ opacity: 0.85;
80
+ }
81
+
82
+ button:disabled {
83
+ opacity: 0.4;
84
+ cursor: not-allowed;
85
+ }
86
+
87
+ .response {
88
+ margin-top: 28px;
89
+ }
90
+
91
+ .response-box {
92
+ background: #111;
93
+ border: 1px solid #2a2a2a;
94
+ border-radius: 8px;
95
+ padding: 16px;
96
+ min-height: 120px;
97
+ font-size: 15px;
98
+ line-height: 1.6;
99
+ white-space: pre-wrap;
100
+ color: #ccc;
101
+ }
tsconfig.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "es2017"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": false,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true
17
+ },
18
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19
+ "exclude": ["node_modules"]
20
+ }