""" Instalira sve funkcije (filters, tools, actions) i kreira Custom Model Preset. Poziva se automatski iz setup_endpoints.sh pri startu Space-a. Koristi: python3 install_functions.py """ import json import urllib.request import urllib.error import sys import os BASE_URL = sys.argv[1] if len(sys.argv) > 1 else 'http://localhost:8080' TOKEN = sys.argv[2] if len(sys.argv) > 2 else '' if not TOKEN: print("❌ No token provided.") sys.exit(1) HEADERS = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {TOKEN}' } APP_DIR = os.path.dirname(os.path.abspath(__file__)) # ───────────────────────────────────────────── # Helpers # ───────────────────────────────────────────── def api(method, path, data=None): url = BASE_URL + path body = json.dumps(data).encode() if data is not None else None req = urllib.request.Request(url, data=body, headers=HEADERS, method=method) try: with urllib.request.urlopen(req) as r: return r.status, json.loads(r.read().decode()) except urllib.error.HTTPError as e: return e.code, e.read().decode() def install_function(func_id, name, func_type, content): """Instaliraj ili ažuriraj funkciju.""" # Provjeri postoji li status, _ = api('GET', f'/api/v1/functions/id/{func_id}') payload = { 'id': func_id, 'name': name, 'type': func_type, 'content': content, 'meta': {'description': name} } if status == 200: s, r = api('POST', f'/api/v1/functions/id/{func_id}/update', payload) label = "ažuriran" if s == 200 else f"greška {s}" else: s, r = api('POST', '/api/v1/functions/create', payload) label = "instaliran" if s == 200 else f"greška {s}" # Aktiviraj api('POST', f'/api/v1/functions/id/{func_id}/toggle') icon = "✅" if s == 200 else "❌" print(f" {icon} [{func_type}] {name} — {label}") return s == 200 def load_json_function(filepath): """Učitaj funkciju iz JSON export fajla.""" with open(filepath) as f: d = json.load(f) item = d[0] if isinstance(d, list) else d # Detektuj tip iz sadržaja content = item.get('content', '') if 'class Filter' in content: ftype = 'filter' elif 'class Pipe' in content: ftype = 'pipe' elif 'class Tools' in content or 'class Action' in content: ftype = 'action' else: ftype = item.get('meta', {}).get('type', 'filter') return item['id'], item['name'], ftype, content # ───────────────────────────────────────────── # 1. Instaliraj sve JSON funkcije # ───────────────────────────────────────────── print("\n📦 Instaliram funkcije iz JSON fajlova...") JSON_FILES = [ 'global_system_prompt_filter.json', '__easysearch_v0_4_3__high-performance_web_search_filter.json', 'token_saver.json', 'context_clip_filter.json', 'auto_disable_native_tools.json', 'auto_memory.json', '__mermaid_doctor_-_heals_broken_mermaid_diagrams_generated_by_small_or_hallucinating_models.json', 'github_simple.json', 'pdf_tools_-_rich_ui_for_in_context_basic_editing_.json', ] installed_filter_ids = [] for fname in JSON_FILES: fpath = os.path.join(APP_DIR, fname) if not os.path.exists(fpath): print(f" ⚠️ {fname} nije pronađen, preskačem") continue func_id, name, ftype, content = load_json_function(fpath) ok = install_function(func_id, name, ftype, content) if ok and ftype == 'filter': installed_filter_ids.append(func_id) # ───────────────────────────────────────────── # 2. Instaliraj naše custom Python funkcije # ───────────────────────────────────────────── print("\n🛠️ Instaliram custom funkcije...") CUSTOM_FUNCTIONS = [ ('context_detector', 'Context Detector', 'filter', 'context_detector.py'), ('token_compressor', 'Token Compressor', 'filter', 'token_compressor.py'), ('coding_fallback', '🔀 Smart Coding Router', 'pipe', 'coding_priority_pipe.py'), ('smart_matcher', 'Smart Matcher', 'tool', 'smart_matcher.py'), ] for func_id, name, ftype, pyfile in CUSTOM_FUNCTIONS: fpath = os.path.join(APP_DIR, pyfile) if not os.path.exists(fpath): print(f" ⚠️ {pyfile} nije pronađen") continue content = open(fpath).read() ok = install_function(func_id, name, ftype, content) if ok and ftype == 'filter': installed_filter_ids.append(func_id) # ───────────────────────────────────────────── # 3. Postavi Global System Prompt # ───────────────────────────────────────────── print("\n📝 Postavljam Global System Prompt...") SYSTEM_PROMPT_FILE = os.path.join(APP_DIR, 'system_prompt.txt') if os.path.exists(SYSTEM_PROMPT_FILE): system_prompt = open(SYSTEM_PROMPT_FILE).read() # Update valve za global_system_prompt_filter valve_payload = { 'system_message': system_prompt, 'timezone': 'Europe/Sarajevo', 'skip_tags': [] } s, r = api('POST', '/api/v1/functions/id/global_system_prompt_filter/valves/update', valve_payload) if s == 200: print(" ✅ System prompt postavljen") else: print(f" ⚠️ Greška pri postavljanju system prompta: {s}") else: print(" ⚠️ system_prompt.txt nije pronađen") # ───────────────────────────────────────────── # 4. Kreiraj Custom Model Preset # ───────────────────────────────────────────── print("\n🤖 Kreiram Custom Model Preset...") # Sve filter ID-ove koji treba biti aktivni na modelu ALL_FILTER_IDS = list(dict.fromkeys([ 'global_system_prompt_filter', 'easysearch', 'token_saver', 'context_clip_filter', 'auto_disable_native_tools', 'auto_memory', 'context_detector', 'token_compressor', 'mermaid_doctor', ] + installed_filter_ids)) model_payload = { 'id': 'nerdur_assistant', 'name': '🧠 Nerdur Assistant (Full)', 'base_model_id': 'coding_fallback.coding_fallback', 'meta': { 'description': 'Smart Coding Router sa svim filterima: web search, memory, token saver, context detector i više.', 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}], 'capabilities': { 'vision': False, 'citations': True, 'usage': True, } }, 'params': { 'system': open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '', 'temperature': 0.2, 'max_tokens': 4096, }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } # Provjeri postoji li model status, _ = api('GET', '/api/v1/models/model?id=nerdur_assistant') if status == 200: s, r = api('POST', '/api/v1/models/model/update?id=nerdur_assistant', model_payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:100]}" else: s, r = api('POST', '/api/v1/models/create', model_payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:100]}" icon = "✅" if s == 200 else "❌" print(f" {icon} Model '🧠 Nerdur Assistant (Full)' — {label}") print("\n✅ Instalacija završena!") # ───────────────────────────────────────────── # 5. Kreiraj Gemini 2.5 Flash Preset # ───────────────────────────────────────────── print("\n⚡ Kreiram Gemini 2.5 Flash Preset...") gemini_payload = { 'id': 'nerdur_gemini_flash', 'name': '⚡ Gemini 2.5 Flash (Coding)', 'base_model_id': 'models/gemini-2.5-flash', 'meta': { 'description': 'Gemini 2.5 Flash sa svim filterima — web search, memory, token saver i više.', 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}, {'name': 'gemini'}], 'capabilities': { 'vision': True, 'citations': True, 'usage': True, } }, 'params': { 'system': open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '', 'temperature': 0.2, 'max_tokens': 8192, }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } status, _ = api('GET', '/api/v1/models/model?id=nerdur_gemini_flash') if status == 200: s, r = api('POST', '/api/v1/models/model/update?id=nerdur_gemini_flash', gemini_payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:100]}" else: s, r = api('POST', '/api/v1/models/create', gemini_payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:100]}" icon = "✅" if s == 200 else "❌" print(f" {icon} Model '⚡ Gemini 2.5 Flash (Coding)' — {label}") print("\n✅ Svi modeli kreirani!") # ───────────────────────────────────────────── # 6. Groq preset # ───────────────────────────────────────────── print("\n🚀 Kreiram Groq preset...") groq_payload = { 'id': 'nerdur_groq_coding', 'name': '🚀 Groq Llama 3.3 70B (Coding)', 'base_model_id': 'groq/llama-3.3-70b-versatile', 'meta': { 'description': 'Kimi K2 na Groq — izuzetno brz, odličan za kodiranje, besplatan.', 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}, {'name': 'groq'}], 'capabilities': {'vision': False, 'citations': True, 'usage': True} }, 'params': { 'system': open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '', 'temperature': 0.2, 'max_tokens': 8192, }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } status, _ = api('GET', '/api/v1/models/model?id=nerdur_groq_coding') if status == 200: s, r = api('POST', '/api/v1/models/model/update?id=nerdur_groq_coding', groq_payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:100]}" else: s, r = api('POST', '/api/v1/models/create', groq_payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:100]}" print(f" {'✅' if s == 200 else '❌'} '🚀 Groq Kimi K2 (Coding)' — {label}") # ───────────────────────────────────────────── # 7. Cerebras preset # ───────────────────────────────────────────── print("\n🧠 Kreiram Cerebras preset...") cerebras_payload = { 'id': 'nerdur_cerebras_coding', 'name': '🧠 Cerebras Llama 4 Scout (Coding)', 'base_model_id': 'cerebras/llama-4-scout-17b-16e-instruct', 'meta': { 'description': 'Llama 3.3 70B na Cerebras — jedan od najbržih inference engine-a, besplatan.', 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}, {'name': 'cerebras'}], 'capabilities': {'vision': False, 'citations': True, 'usage': True} }, 'params': { 'system': open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '', 'temperature': 0.2, 'max_tokens': 8192, }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } status, _ = api('GET', '/api/v1/models/model?id=nerdur_cerebras_coding') if status == 200: s, r = api('POST', '/api/v1/models/model/update?id=nerdur_cerebras_coding', cerebras_payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:100]}" else: s, r = api('POST', '/api/v1/models/create', cerebras_payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:100]}" print(f" {'✅' if s == 200 else '❌'} '🧠 Cerebras Llama 3.3 (Coding)' — {label}") # ───────────────────────────────────────────── # 8. SambaNova preset # ───────────────────────────────────────────── print("\n⚡ Kreiram SambaNova preset...") sambanova_payload = { 'id': 'nerdur_sambanova_coding', 'name': '⚡ SambaNova Llama 3.3 (Coding)', 'base_model_id': 'Meta-Llama-3.3-70B-Instruct', 'meta': { 'description': 'Llama 3.3 70B na SambaNova — brz inference, dobar za kodiranje, besplatan.', 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}, {'name': 'sambanova'}], 'capabilities': {'vision': False, 'citations': True, 'usage': True} }, 'params': { 'system': open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '', 'temperature': 0.2, 'max_tokens': 8192, }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } status, _ = api('GET', '/api/v1/models/model?id=nerdur_sambanova_coding') if status == 200: s, r = api('POST', '/api/v1/models/model/update?id=nerdur_sambanova_coding', sambanova_payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:100]}" else: s, r = api('POST', '/api/v1/models/create', sambanova_payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:100]}" print(f" {'✅' if s == 200 else '❌'} '⚡ SambaNova Llama 3.3 (Coding)' — {label}") print("\n🎉 Svih 5 preseta kreirano!") # ───────────────────────────────────────────── # 9. Dodatni presets za sve aktivne provajdere # ───────────────────────────────────────────── EXTRA_MODELS = [ { 'id': 'nerdur_claude', 'name': '🟣 Claude Sonnet 4.5 (Coding)', 'base_model_id': 'anthropic/claude-sonnet-4-5', 'desc': 'Claude Sonnet 4.5 via Anthropic — odličan za kodiranje i analizu.', 'max_tokens': 8192, 'vision': True, }, { 'id': 'nerdur_gpt4o', 'name': '🟢 GPT-4o (Coding)', 'base_model_id': 'openai/gpt-4o', 'desc': 'OpenAI GPT-4o — multimodalan, odličan za kodiranje.', 'max_tokens': 4096, 'vision': True, }, { 'id': 'nerdur_mistral', 'name': '🔵 Mistral Large (Coding)', 'base_model_id': 'mistral-large-latest', 'desc': 'Mistral Large — odličan europski model za kodiranje.', 'max_tokens': 8192, 'vision': False, }, { 'id': 'nerdur_deepseek', 'name': '🔴 DeepSeek V3 (Coding)', 'base_model_id': 'deepseek/deepseek-chat', 'desc': 'DeepSeek V3 — izvrstan za kodiranje, vrlo jeftin.', 'max_tokens': 8192, 'vision': False, }, { 'id': 'nerdur_xai', 'name': '⚡ Grok 3 (Coding)', 'base_model_id': 'xai/grok-3', 'desc': 'xAI Grok 3 — jak model sa web pristupom.', 'max_tokens': 8192, 'vision': True, }, { 'id': 'nerdur_openrouter_free', 'name': '🆓 OpenRouter Free (Coding)', 'base_model_id': 'openrouter/meta-llama/llama-4-maverick:free', 'desc': 'Llama 4 Maverick besplatno via OpenRouter.', 'max_tokens': 8192, 'vision': True, }, { 'id': 'nerdur_fireworks', 'name': '🔥 Fireworks Llama 4 Scout (Coding)', 'base_model_id': 'fireworks/accounts/fireworks/models/llama4-scout-instruct-basic', 'desc': 'Llama 4 Scout na Fireworks — brz i jeftin.', 'max_tokens': 8192, 'vision': True, }, ] system_prompt_text = open(SYSTEM_PROMPT_FILE).read() if os.path.exists(SYSTEM_PROMPT_FILE) else '' print("\n🌐 Kreiram dodatne provider presete...") for m in EXTRA_MODELS: payload = { 'id': m['id'], 'name': m['name'], 'base_model_id': m['base_model_id'], 'meta': { 'description': m['desc'], 'profile_image_url': '/static/favicon.png', 'tags': [{'name': 'custom'}, {'name': 'coding'}], 'capabilities': { 'vision': m['vision'], 'citations': True, 'usage': True, } }, 'params': { 'system': system_prompt_text, 'temperature': 0.2, 'max_tokens': m['max_tokens'], }, 'filter_ids': ALL_FILTER_IDS, 'tool_ids': ['smart_matcher'], 'action_ids': ['pdf_tools_rich_ui_editor'], } status, _ = api('GET', f'/api/v1/models/model?id={m["id"]}') if status == 200: s, r = api('POST', f'/api/v1/models/model/update?id={m["id"]}', payload) label = "ažuriran" if s == 200 else f"greška {s}: {str(r)[:80]}" else: s, r = api('POST', '/api/v1/models/create', payload) label = "kreiran" if s == 200 else f"greška {s}: {str(r)[:80]}" print(f" {'✅' if s == 200 else '❌'} {m['name']} — {label}") print("\n🎉 Instalacija kompletna!")