diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..84e5089 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Copy to .env and fill in your values — never commit .env +HF_TOKEN=your_huggingface_write_token_here +HF_DEFAULT_MODEL=Qwen/Qwen2.5-72B-Instruct diff --git a/.gitignore b/.gitignore index 0c40a49..c699ee6 100755 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ tools/test_batch*.json # Secrets / credentials .env .env.* +!.env.example *.key *.pem credentials* diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..3e07047 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: python prompt_expert_enhance.py web --port $PORT --no-browser diff --git a/prompt_expert_enhance.py b/prompt_expert_enhance.py index 20d5c84..f4f2a4c 100755 --- a/prompt_expert_enhance.py +++ b/prompt_expert_enhance.py @@ -65,6 +65,13 @@ OLLAMA_URL = "http://localhost:11434/api/generate" BASE_DIR = Path(__file__).resolve().parent +# Cloud deploy: set HF_TOKEN env var to switch to HF Inference API backend. +# All Ollama checks are skipped automatically when this is set. +_HF_TOKEN: str = os.environ.get("HF_TOKEN", "") +_CLOUD_MODE: bool = bool(_HF_TOKEN) +_HF_INFERENCE_URL = "https://api-inference.huggingface.co/v1/chat/completions" +_HF_DEFAULT_MODEL = os.environ.get("HF_DEFAULT_MODEL", "Qwen/Qwen2.5-72B-Instruct") + def _is_frozen() -> bool: """True when running inside a PyInstaller-compiled app, not from source.""" @@ -324,6 +331,8 @@ def is_ollama_installed() -> bool: def is_ollama_running() -> bool: + if _CLOUD_MODE: + return True try: requests.get(f"{OLLAMA_API_BASE}/api/tags", timeout=3) return True @@ -439,7 +448,20 @@ def start_ollama_serve(): print(f" [!] Could not start Ollama: {e}") +_HF_AVAILABLE_MODELS = [ + "Qwen/Qwen2.5-72B-Instruct", + "Qwen/Qwen2.5-Coder-32B-Instruct", + "meta-llama/Llama-3.3-70B-Instruct", + "mistralai/Mistral-7B-Instruct-v0.3", + "mistralai/Mixtral-8x7B-Instruct-v0.1", + "google/gemma-3-27b-it", + "microsoft/Phi-4-reasoning", +] + + def list_local_models() -> List[Dict[str, str]]: + if _CLOUD_MODE: + return [{"name": m, "size": "cloud", "modified": ""} for m in _HF_AVAILABLE_MODELS] base = _BACKEND_API_BASE or OLLAMA_API_BASE if _BACKEND_TYPE == "openai_compatible": try: @@ -554,6 +576,8 @@ def pick_model_interactive(label: str, current: str) -> str: def ensure_ollama_ready(): + if _CLOUD_MODE: + return if not is_ollama_installed(): if not install_ollama_interactive(): print("\n [!] Ollama is not available. Generation will fail.") @@ -1283,8 +1307,9 @@ def query_ollama( if _BACKEND_TYPE == "openai_compatible": is_chat = _is_chat_endpoint(ollama_url) payload = _openai_compatible_payload(model, prompt, temperature, num_predict, ollama_url, stream=False) + headers = {"Authorization": f"Bearer {_HF_TOKEN}"} if _HF_TOKEN else {} try: - resp = requests.post(ollama_url, json=payload, timeout=timeout) + resp = requests.post(ollama_url, json=payload, headers=headers, timeout=timeout) resp.raise_for_status() data = resp.json() result = _openai_compatible_extract_text(data["choices"][0], is_chat) or "[Empty response]" @@ -1353,9 +1378,10 @@ def query_ollama_stream( if _BACKEND_TYPE == "openai_compatible": is_chat = _is_chat_endpoint(ollama_url) payload = _openai_compatible_payload(model, prompt, temperature, num_predict, ollama_url, stream=True) + headers = {"Authorization": f"Bearer {_HF_TOKEN}"} if _HF_TOKEN else {} full_text = [] try: - with requests.post(ollama_url, json=payload, timeout=timeout, stream=True) as resp: + with requests.post(ollama_url, json=payload, headers=headers, timeout=timeout, stream=True) as resp: resp.raise_for_status() for raw_line in resp.iter_lines(): if not raw_line: diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..cf8dbbb --- /dev/null +++ b/render.yaml @@ -0,0 +1,12 @@ +services: + - type: web + name: wild-root-prompt + runtime: python + buildCommand: pip install -r requirements.txt + startCommand: python prompt_expert_enhance.py web --port $PORT --no-browser + envVars: + - key: HF_TOKEN + sync: false # set this in Render dashboard — never commit the value + - key: HF_DEFAULT_MODEL + value: Qwen/Qwen2.5-72B-Instruct + healthCheckPath: /api/status diff --git a/web_server.py b/web_server.py index e599b7d..69e66f0 100644 --- a/web_server.py +++ b/web_server.py @@ -30,6 +30,7 @@ load_settings, save_settings, PRE_PROCESSOR_TIMEOUT, is_ollama_running, ensure_ollama_ready, set_backend_type, set_backend_api_base, + _CLOUD_MODE, _HF_INFERENCE_URL, PROMPT_TEMPLATES, ) @@ -458,7 +459,9 @@ const r = await fetch('/api/status') const d = await r.json() $('ollama-dot').className = 'dot ' + (d.ollama ? 'green' : 'red') - $('ollama-status').textContent = d.ollama ? 'Ollama running — ready' : 'Ollama not running — start it first' + $('ollama-status').textContent = d.ollama + ? (d.cloud ? 'HF Inference API — ready' : 'Ollama running — ready') + : 'Ollama not running — start it first' } catch { $('ollama-dot').className = 'dot red'; $('ollama-status').textContent = 'Server error' } } @@ -918,7 +921,7 @@ def index(): @app.route("/api/status") def api_status(): - return jsonify({"ollama": is_ollama_running()}) + return jsonify({"ollama": is_ollama_running(), "cloud": _CLOUD_MODE}) @app.route("/api/models") @@ -1096,8 +1099,12 @@ def generate_sse(): def run_web_server(port: int = 7860, open_browser: bool = True): startup_settings = load_settings() - set_backend_type(startup_settings.get("backend_type", "ollama")) - set_backend_api_base(startup_settings.get("ollama_url", OLLAMA_URL)) + if _CLOUD_MODE: + set_backend_type("openai_compatible") + set_backend_api_base(_HF_INFERENCE_URL) + else: + set_backend_type(startup_settings.get("backend_type", "ollama")) + set_backend_api_base(startup_settings.get("ollama_url", OLLAMA_URL)) print(f"\n Wild_Root_Prompt Web UI") print(f" ─────────────────────────────────────")