github-actions[bot] commited on
Commit ·
fc2f017
1
Parent(s): b9a1550
Auto-deploy from GitHub: 141b37810ab063d3984b529a3bda3b8633f874c3
Browse files- Dockerfile +5 -0
- app/api/routes.py +3 -1
- app/db/crud.py +5 -5
- app/db/database.py +1 -0
- app/services/worker.py +44 -17
- index.html +2 -2
Dockerfile
CHANGED
|
@@ -14,11 +14,16 @@ RUN apt-get update && apt-get install -y \
|
|
| 14 |
build-essential \
|
| 15 |
zstd \
|
| 16 |
procps \
|
|
|
|
|
|
|
| 17 |
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
|
| 19 |
# Install Ollama
|
| 20 |
RUN curl -fsSL https://ollama.com/install.sh | sh
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
# Copy project files
|
| 23 |
COPY pyproject.toml .
|
| 24 |
COPY . .
|
|
|
|
| 14 |
build-essential \
|
| 15 |
zstd \
|
| 16 |
procps \
|
| 17 |
+
nodejs \
|
| 18 |
+
npm \
|
| 19 |
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
| 21 |
# Install Ollama
|
| 22 |
RUN curl -fsSL https://ollama.com/install.sh | sh
|
| 23 |
|
| 24 |
+
# Install opencode CLI
|
| 25 |
+
RUN npm install -g opencode-ai
|
| 26 |
+
|
| 27 |
# Copy project files
|
| 28 |
COPY pyproject.toml .
|
| 29 |
COPY . .
|
app/api/routes.py
CHANGED
|
@@ -22,8 +22,9 @@ async def submit_task(request: Request):
|
|
| 22 |
input_text = data['text'].strip()
|
| 23 |
system_prompt = data.get('system_prompt', '').strip() or None
|
| 24 |
hide_from_ui = 1 if data.get('hide_from_ui') else 0
|
|
|
|
| 25 |
|
| 26 |
-
await crud.insert_task(task_id, input_text, system_prompt, 'not_started', hide_from_ui)
|
| 27 |
|
| 28 |
await start_worker()
|
| 29 |
|
|
@@ -31,6 +32,7 @@ async def submit_task(request: Request):
|
|
| 31 |
'id': task_id,
|
| 32 |
'filename': input_text[:50] + ("..." if len(input_text) > 50 else ""),
|
| 33 |
'status': 'not_started',
|
|
|
|
| 34 |
'message': 'Task submitted successfully'
|
| 35 |
})
|
| 36 |
|
|
|
|
| 22 |
input_text = data['text'].strip()
|
| 23 |
system_prompt = data.get('system_prompt', '').strip() or None
|
| 24 |
hide_from_ui = 1 if data.get('hide_from_ui') else 0
|
| 25 |
+
model = (data.get('model') or 'qwen').strip().lower()
|
| 26 |
|
| 27 |
+
await crud.insert_task(task_id, input_text, system_prompt, 'not_started', hide_from_ui, model)
|
| 28 |
|
| 29 |
await start_worker()
|
| 30 |
|
|
|
|
| 32 |
'id': task_id,
|
| 33 |
'filename': input_text[:50] + ("..." if len(input_text) > 50 else ""),
|
| 34 |
'status': 'not_started',
|
| 35 |
+
'model': model,
|
| 36 |
'message': 'Task submitted successfully'
|
| 37 |
})
|
| 38 |
|
app/db/crud.py
CHANGED
|
@@ -3,14 +3,14 @@ from datetime import datetime, timedelta
|
|
| 3 |
from app.core.config import settings
|
| 4 |
from custom_logger import logger_config as logger
|
| 5 |
|
| 6 |
-
async def insert_task(task_id: str, input_text: str, system_prompt: str, status: str, hide_from_ui: int):
|
| 7 |
async with aiosqlite.connect(settings.DATABASE_FILE) as db:
|
| 8 |
await db.execute('''INSERT INTO text_tasks
|
| 9 |
-
(id, input_text, system_prompt, status, created_at, hide_from_ui)
|
| 10 |
-
VALUES (?, ?, ?, ?, ?, ?)''',
|
| 11 |
-
(task_id, input_text, system_prompt, status, datetime.now().isoformat(), hide_from_ui))
|
| 12 |
await db.commit()
|
| 13 |
-
logger.debug(f"Inserted task (ID: {task_id}) into database.")
|
| 14 |
|
| 15 |
async def update_status(task_id: str, status: str, result: str = None, error: str = None):
|
| 16 |
async with aiosqlite.connect(settings.DATABASE_FILE) as db:
|
|
|
|
| 3 |
from app.core.config import settings
|
| 4 |
from custom_logger import logger_config as logger
|
| 5 |
|
| 6 |
+
async def insert_task(task_id: str, input_text: str, system_prompt: str, status: str, hide_from_ui: int, model: str = 'qwen'):
|
| 7 |
async with aiosqlite.connect(settings.DATABASE_FILE) as db:
|
| 8 |
await db.execute('''INSERT INTO text_tasks
|
| 9 |
+
(id, input_text, system_prompt, model, status, created_at, hide_from_ui)
|
| 10 |
+
VALUES (?, ?, ?, ?, ?, ?, ?)''',
|
| 11 |
+
(task_id, input_text, system_prompt, model, status, datetime.now().isoformat(), hide_from_ui))
|
| 12 |
await db.commit()
|
| 13 |
+
logger.debug(f"Inserted task (ID: {task_id}, model: {model}) into database.")
|
| 14 |
|
| 15 |
async def update_status(task_id: str, status: str, result: str = None, error: str = None):
|
| 16 |
async with aiosqlite.connect(settings.DATABASE_FILE) as db:
|
app/db/database.py
CHANGED
|
@@ -9,6 +9,7 @@ async def init_db():
|
|
| 9 |
(id TEXT PRIMARY KEY,
|
| 10 |
input_text TEXT NOT NULL,
|
| 11 |
system_prompt TEXT,
|
|
|
|
| 12 |
status TEXT NOT NULL,
|
| 13 |
result TEXT,
|
| 14 |
created_at TEXT NOT NULL,
|
|
|
|
| 9 |
(id TEXT PRIMARY KEY,
|
| 10 |
input_text TEXT NOT NULL,
|
| 11 |
system_prompt TEXT,
|
| 12 |
+
model TEXT DEFAULT 'qwen',
|
| 13 |
status TEXT NOT NULL,
|
| 14 |
result TEXT,
|
| 15 |
created_at TEXT NOT NULL,
|
app/services/worker.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import asyncio
|
| 2 |
import json
|
|
|
|
|
|
|
| 3 |
from app.core.config import settings
|
| 4 |
from custom_logger import logger_config as logger
|
| 5 |
from app.db import crud
|
|
@@ -33,9 +35,7 @@ async def worker_loop():
|
|
| 33 |
await loop.run_in_executor(None, lambda: initiate({'text': 'Hi', 'model': 'qwen', 'max_new_tokens': 1}))
|
| 34 |
logger.info("✅ Qwen model ready. Monitoring for new tasks...")
|
| 35 |
except Exception as e:
|
| 36 |
-
logger.
|
| 37 |
-
worker_running = False
|
| 38 |
-
return
|
| 39 |
|
| 40 |
while worker_running:
|
| 41 |
logger.debug("Worker loop iteration, checking for files...")
|
|
@@ -48,8 +48,9 @@ async def worker_loop():
|
|
| 48 |
task_id = row['id']
|
| 49 |
input_text = row['input_text']
|
| 50 |
system_prompt = row['system_prompt'] or "You are a helpful assistant."
|
|
|
|
| 51 |
|
| 52 |
-
logger.info(f"\n{'='*60}\nProcessing task: {task_id}\n📌 Input: {input_text[:100]}...\n{'='*60}")
|
| 53 |
|
| 54 |
await crud.update_status(task_id, 'processing')
|
| 55 |
|
|
@@ -63,21 +64,31 @@ async def worker_loop():
|
|
| 63 |
|
| 64 |
try:
|
| 65 |
await crud.update_progress(task_id, 5, "Starting...")
|
| 66 |
-
|
| 67 |
-
result = await loop.run_in_executor(None, lambda: initiate(
|
| 68 |
-
{
|
| 69 |
-
'text': input_text,
|
| 70 |
-
'system_prompt': system_prompt,
|
| 71 |
-
'model': 'qwen',
|
| 72 |
-
},
|
| 73 |
-
progress_callback=progress_cb
|
| 74 |
-
))
|
| 75 |
|
| 76 |
-
if
|
| 77 |
-
|
| 78 |
-
await
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
else:
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
except Exception as e:
|
| 83 |
logger.error(f"Failed to process {task_id}: {str(e)}")
|
|
@@ -89,3 +100,19 @@ async def worker_loop():
|
|
| 89 |
except Exception as e:
|
| 90 |
logger.error(f"Worker error: {str(e)}")
|
| 91 |
await asyncio.sleep(settings.POLL_INTERVAL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import json
|
| 3 |
+
import subprocess
|
| 4 |
+
import shutil
|
| 5 |
from app.core.config import settings
|
| 6 |
from custom_logger import logger_config as logger
|
| 7 |
from app.db import crud
|
|
|
|
| 35 |
await loop.run_in_executor(None, lambda: initiate({'text': 'Hi', 'model': 'qwen', 'max_new_tokens': 1}))
|
| 36 |
logger.info("✅ Qwen model ready. Monitoring for new tasks...")
|
| 37 |
except Exception as e:
|
| 38 |
+
logger.warning(f"⚠️ Qwen model not available (opencode-only tasks will still work): {e}")
|
|
|
|
|
|
|
| 39 |
|
| 40 |
while worker_running:
|
| 41 |
logger.debug("Worker loop iteration, checking for files...")
|
|
|
|
| 48 |
task_id = row['id']
|
| 49 |
input_text = row['input_text']
|
| 50 |
system_prompt = row['system_prompt'] or "You are a helpful assistant."
|
| 51 |
+
model = row.get('model', 'qwen')
|
| 52 |
|
| 53 |
+
logger.info(f"\n{'='*60}\nProcessing task: {task_id} (model: {model})\n📌 Input: {input_text[:100]}...\n{'='*60}")
|
| 54 |
|
| 55 |
await crud.update_status(task_id, 'processing')
|
| 56 |
|
|
|
|
| 64 |
|
| 65 |
try:
|
| 66 |
await crud.update_progress(task_id, 5, "Starting...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
if model == 'opencode':
|
| 69 |
+
await crud.update_progress(task_id, 10, "Running opencode...")
|
| 70 |
+
result = await loop.run_in_executor(None, lambda: _run_opencode(input_text))
|
| 71 |
+
if result:
|
| 72 |
+
logger.success(f"Successfully processed (opencode): {task_id}")
|
| 73 |
+
await crud.update_progress(task_id, 100, "Completed")
|
| 74 |
+
await crud.update_status(task_id, 'completed', result=json.dumps({"response": result}))
|
| 75 |
+
else:
|
| 76 |
+
raise Exception("opencode returned empty result")
|
| 77 |
else:
|
| 78 |
+
result = await loop.run_in_executor(None, lambda: initiate(
|
| 79 |
+
{
|
| 80 |
+
'text': input_text,
|
| 81 |
+
'system_prompt': system_prompt,
|
| 82 |
+
'model': 'qwen',
|
| 83 |
+
},
|
| 84 |
+
progress_callback=progress_cb
|
| 85 |
+
))
|
| 86 |
+
|
| 87 |
+
if result:
|
| 88 |
+
logger.success(f"Successfully processed: {task_id}")
|
| 89 |
+
await crud.update_status(task_id, 'completed', result=json.dumps(result))
|
| 90 |
+
else:
|
| 91 |
+
raise Exception("initiate() returned empty result")
|
| 92 |
|
| 93 |
except Exception as e:
|
| 94 |
logger.error(f"Failed to process {task_id}: {str(e)}")
|
|
|
|
| 100 |
except Exception as e:
|
| 101 |
logger.error(f"Worker error: {str(e)}")
|
| 102 |
await asyncio.sleep(settings.POLL_INTERVAL)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _run_opencode(text: str) -> str:
|
| 106 |
+
if not shutil.which('opencode'):
|
| 107 |
+
raise FileNotFoundError(
|
| 108 |
+
"opencode CLI not found. Install it from https://opencode.ai"
|
| 109 |
+
)
|
| 110 |
+
result = subprocess.run(
|
| 111 |
+
['opencode', 'run', text],
|
| 112 |
+
capture_output=True,
|
| 113 |
+
text=True,
|
| 114 |
+
timeout=300
|
| 115 |
+
)
|
| 116 |
+
if result.returncode != 0:
|
| 117 |
+
raise RuntimeError(f"opencode failed: {result.stderr.strip()}")
|
| 118 |
+
return result.stdout.strip()
|
index.html
CHANGED
|
@@ -783,12 +783,12 @@
|
|
| 783 |
const doc = {
|
| 784 |
base_url: window.location.origin,
|
| 785 |
endpoints: [
|
| 786 |
-
{ method: "POST", path: "/api/tasks/upload", desc: "Submit text generation task", body: "{ text: string, system_prompt?: string }" },
|
| 787 |
{ method: "GET", path: "/api/tasks", desc: "List all tasks" },
|
| 788 |
{ method: "GET", path: "/api/tasks/{task_id}", desc: "Get task details & result" },
|
| 789 |
{ method: "GET", path: "/health", desc: "Service health" }
|
| 790 |
],
|
| 791 |
-
example_usage: `curl -X POST -H 'Content-Type: application/json' -d '{"text": "Hello"}' ${window.location.origin}/api/tasks/upload`
|
| 792 |
};
|
| 793 |
UI.resultText.innerText = JSON.stringify(doc, null, 2);
|
| 794 |
UI.modalTitle.innerText = "API Documentation";
|
|
|
|
| 783 |
const doc = {
|
| 784 |
base_url: window.location.origin,
|
| 785 |
endpoints: [
|
| 786 |
+
{ method: "POST", path: "/api/tasks/upload", desc: "Submit text generation task", body: "{ text: string, system_prompt?: string, model?: 'qwen' | 'opencode' }" },
|
| 787 |
{ method: "GET", path: "/api/tasks", desc: "List all tasks" },
|
| 788 |
{ method: "GET", path: "/api/tasks/{task_id}", desc: "Get task details & result" },
|
| 789 |
{ method: "GET", path: "/health", desc: "Service health" }
|
| 790 |
],
|
| 791 |
+
example_usage: `curl -X POST -H 'Content-Type: application/json' -d '{"text": "Hello"}' ${window.location.origin}/api/tasks/upload` + '\n' + `curl -X POST -H 'Content-Type: application/json' -d '{"text": "list files", "model": "opencode"}' ${window.location.origin}/api/tasks/upload`
|
| 792 |
};
|
| 793 |
UI.resultText.innerText = JSON.stringify(doc, null, 2);
|
| 794 |
UI.modalTitle.innerText = "API Documentation";
|