From 5de92a21031258d45cfac13ebd20f0f766ab8505 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 07:27:40 +0000 Subject: [PATCH 1/2] feat: add Venice AI Coding Wizard Acode plugin Adds a full-featured Venice AI coding assistant plugin for Acode with: - Sidebar chat panel with streaming responses and conversation history - Full codebase context: active file, selection, all open tabs - Per-code-block actions: insert at cursor, replace file, new file, copy - Inline system-prompt editor with live preview and reset-to-default - Model picker: Gemma 4 Uncensored, Llama 3.3 70B, DeepSeek R1, Qwen 2.5 Coder, Mistral 3.1, Venice Uncensored, Llama 3.2 3B - Acode settings integration (API key, model, temperature, max tokens) - Context toggles: current file / selection / all open files - Auto file-creation banner when AI mentions a filename - Lightweight markdown renderer with fenced code highlighting - localStorage persistence for all settings across sessions Plugin files live in acode-plugins/venice-ai-wizard/ (source) and acode-plugins/venice-ai-wizard.zip (installable package). Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01KzaGGLg7cz4jMb85KMAXrb --- acode-plugins/venice-ai-wizard.zip | Bin 0 -> 13425 bytes acode-plugins/venice-ai-wizard/icon.png | Bin 0 -> 511 bytes acode-plugins/venice-ai-wizard/main.js | 1448 ++++++++++++++++++++ acode-plugins/venice-ai-wizard/plugin.json | 14 + acode-plugins/venice-ai-wizard/readme.md | 78 ++ 5 files changed, 1540 insertions(+) create mode 100644 acode-plugins/venice-ai-wizard.zip create mode 100644 acode-plugins/venice-ai-wizard/icon.png create mode 100644 acode-plugins/venice-ai-wizard/main.js create mode 100644 acode-plugins/venice-ai-wizard/plugin.json create mode 100644 acode-plugins/venice-ai-wizard/readme.md diff --git a/acode-plugins/venice-ai-wizard.zip b/acode-plugins/venice-ai-wizard.zip new file mode 100644 index 0000000000000000000000000000000000000000..0031b8236c1d08a1e6ba5b74110e6c52efb32b43 GIT binary patch literal 13425 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 literal 0 HcmV?d00001 diff --git a/acode-plugins/venice-ai-wizard/icon.png b/acode-plugins/venice-ai-wizard/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..25ec47f6d9ac28c026233c60b725109c54c4c95c GIT binary patch literal 511 zcmV0005TNklC@W zV6gfCKrI8|g~sznHduWapoD?>N;#eZP>;OSH|e<2qw2$62|7lo6@pvE%nrL%VB?Uy z+M?w53t_gW|K!+O#51>uYjFkN=sM2bb-dFhaRtKk?&;E}V7veT002ovPDHLkV1gH& B?|A?K literal 0 HcmV?d00001 diff --git a/acode-plugins/venice-ai-wizard/main.js b/acode-plugins/venice-ai-wizard/main.js new file mode 100644 index 000000000..160dae3fb --- /dev/null +++ b/acode-plugins/venice-ai-wizard/main.js @@ -0,0 +1,1448 @@ +/** + * Venice AI Coding Wizard — Acode Plugin + * Integrates Venice AI (OpenAI-compatible) into Acode for full-context + * code generation, editing, reading and file creation. + */ +(function () { + "use strict"; + + const PLUGIN_ID = "venice-ai-wizard"; + const STORAGE_KEY = "venice_ai_wizard_settings"; + const VENICE_BASE = "https://api.venice.ai/api/v1"; + + /* ── Available models ─────────────────────────────────────────────────── */ + const MODELS = [ + ["gemma-4-uncensored", "Gemma 4 Uncensored (Default)"], + ["llama-3.3-70b", "Llama 3.3 70B"], + ["deepseek-r1-671b", "DeepSeek R1 671B"], + ["qwen-2.5-coder-32b-instruct", "Qwen 2.5 Coder 32B"], + ["mistral-31-24b", "Mistral 3.1 24B"], + ["venice-uncensored", "Venice Uncensored"], + ["llama-3.2-3b", "Llama 3.2 3B (Fast)"], + ]; + + const DEFAULT_SYSTEM_PROMPT = + "You are an expert coding assistant integrated into Acode, a code editor. " + + "You help users write, read, edit and create code files. " + + "When providing code always include complete, working implementations without placeholders. " + + "Use markdown fenced code blocks with language identifiers. " + + "When asked to create a file, state the filename clearly before the code block."; + + /* ── Settings ─────────────────────────────────────────────────────────── */ + const cfg = loadSettings(); + + function loadSettings() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return Object.assign(defaults(), JSON.parse(raw)); + } catch (_) {} + return defaults(); + } + + function defaults() { + return { + apiKey: "", + model: "gemma-4-uncensored", + temperature: "0.7", + maxTokens: "4096", + systemPrompt: DEFAULT_SYSTEM_PROMPT, + includeCurrentFile: true, + includeSelection: true, + includeOpenFiles: false, + }; + } + + function saveSettings() { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg)); + } catch (_) {} + } + + /* ── Venice AI API ────────────────────────────────────────────────────── */ + async function callVenice(messages, onChunk) { + if (!cfg.apiKey) { + throw new Error( + "No API key configured. Open plugin settings and enter your Venice AI API key.", + ); + } + + const useStream = typeof onChunk === "function"; + + const res = await fetch(`${VENICE_BASE}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${cfg.apiKey}`, + }, + body: JSON.stringify({ + model: cfg.model, + messages, + temperature: parseFloat(cfg.temperature) || 0.7, + max_tokens: parseInt(cfg.maxTokens, 10) || 4096, + stream: useStream, + }), + }); + + if (!res.ok) { + let msg = `Venice AI error ${res.status}`; + try { + const e = await res.json(); + msg = e?.error?.message || msg; + } catch (_) {} + throw new Error(msg); + } + + if (!useStream) { + const data = await res.json(); + return data.choices?.[0]?.message?.content ?? ""; + } + + /* streaming */ + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + let full = ""; + let buf = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buf += decoder.decode(value, { stream: true }); + const lines = buf.split("\n"); + buf = lines.pop(); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed === "data: [DONE]") continue; + if (trimmed.startsWith("data: ")) { + try { + const json = JSON.parse(trimmed.slice(6)); + const delta = json.choices?.[0]?.delta?.content ?? ""; + if (delta) { + full += delta; + onChunk(delta, full); + } + } catch (_) {} + } + } + } + return full; + } + + /* ── Context helpers ──────────────────────────────────────────────────── */ + function getActiveFileContext() { + try { + const em = window.editorManager; + if (!em) return null; + const file = em.activeFile; + if (!file || file.type !== "editor") return null; + const content = em.editor?.getValue?.() ?? ""; + const filename = file.filename || file.name || "untitled"; + return { filename, content, language: guesslang(filename) }; + } catch (_) { + return null; + } + } + + function getSelection() { + try { + const editor = window.editorManager?.editor; + if (!editor) return ""; + const { from, to } = editor.state.selection.main; + return from !== to ? editor.state.doc.sliceString(from, to) : ""; + } catch (_) { + return ""; + } + } + + function getOpenFilesContext() { + try { + const em = window.editorManager; + if (!em) return []; + return (em.files || []) + .filter((f) => f.type === "editor" && f !== em.activeFile) + .slice(0, 4) + .map((f) => ({ + filename: f.filename || f.name || "untitled", + content: f.session?.doc?.toString?.() ?? "", + language: guesslang(f.filename || f.name || ""), + })); + } catch (_) { + return []; + } + } + + function guesslang(filename) { + const ext = filename.split(".").pop().toLowerCase(); + const map = { + js: "javascript", + ts: "typescript", + jsx: "jsx", + tsx: "tsx", + py: "python", + rb: "ruby", + java: "java", + kt: "kotlin", + go: "go", + rs: "rust", + cpp: "cpp", + c: "c", + cs: "csharp", + php: "php", + html: "html", + css: "css", + scss: "scss", + json: "json", + xml: "xml", + md: "markdown", + sh: "bash", + sql: "sql", + yaml: "yaml", + yml: "yaml", + }; + return map[ext] || ext || "text"; + } + + function buildMessages(userPrompt) { + const msgs = [{ role: "system", content: cfg.systemPrompt }]; + + const contextParts = []; + + if (cfg.includeCurrentFile) { + const ctx = getActiveFileContext(); + if (ctx && ctx.content) { + contextParts.push( + `## Current file: ${ctx.filename}\n\`\`\`${ctx.language}\n${ctx.content}\n\`\`\``, + ); + } + } + + if (cfg.includeSelection) { + const sel = getSelection(); + if (sel) { + contextParts.push(`## Selected text\n\`\`\`\n${sel}\n\`\`\``); + } + } + + if (cfg.includeOpenFiles) { + const others = getOpenFilesContext(); + if (others.length) { + const othersText = others + .map( + (f) => + `### ${f.filename}\n\`\`\`${f.language}\n${f.content}\n\`\`\``, + ) + .join("\n\n"); + contextParts.push(`## Other open files\n${othersText}`); + } + } + + let finalPrompt = userPrompt; + if (contextParts.length) { + finalPrompt = contextParts.join("\n\n") + "\n\n---\n\n" + userPrompt; + } + + msgs.push({ role: "user", content: finalPrompt }); + return msgs; + } + + /* ── File operations ──────────────────────────────────────────────────── */ + function insertIntoEditor(text) { + try { + const editor = window.editorManager?.editor; + if (!editor) return false; + editor.insert(text); + return true; + } catch (_) { + return false; + } + } + + function replaceEditorContent(text) { + try { + const em = window.editorManager; + if (!em?.editor) return false; + const state = em.editor.state; + em.editor.dispatch({ + changes: { from: 0, to: state.doc.length, insert: text }, + }); + return true; + } catch (_) { + return false; + } + } + + async function createNewFile(filename, content) { + try { + const fs = acode.require("fs") || acode.require("fsOperation"); + const FileBrowser = acode.require("fileBrowser"); + const helpers = acode.require("helpers"); + const Url = acode.require("Url"); + + const em = window.editorManager; + if (!em) return false; + + /* Use Acode's newEditorFile API */ + acode.newEditorFile(filename, { text: content, isUnsaved: true }); + return true; + } catch (e) { + console.error("[Venice AI] createNewFile error:", e); + return false; + } + } + + /* ── Code block extraction ────────────────────────────────────────────── */ + function extractCodeBlocks(markdown) { + const blocks = []; + const re = /```(\w*)\n?([\s\S]*?)```/g; + let m; + while ((m = re.exec(markdown)) !== null) { + blocks.push({ lang: m[1] || "text", code: m[2].trimEnd() }); + } + return blocks; + } + + function parseFilenameFromResponse(text) { + const patterns = [ + /create(?:d)?\s+(?:a\s+)?(?:new\s+)?file\s*[:`]?\s*[`'"]?([\w./\\-]+\.\w+)/i, + /filename[:`]?\s*[`'"]?([\w./\\-]+\.\w+)/i, + /(?:save|write)\s+(?:to|as)\s*[`'"]?([\w./\\-]+\.\w+)/i, + ]; + for (const p of patterns) { + const m = text.match(p); + if (m) return m[1]; + } + return null; + } + + /* ── Styles ───────────────────────────────────────────────────────────── */ + const STYLES = ` + .vai-panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--secondary-color, #1e1e2e); + color: var(--primary-text-color, #cdd6f4); + font-family: var(--editor-font, monospace); + font-size: 13px; + } + .vai-header { + display: flex; + align-items: center; + padding: 8px 10px; + background: var(--primary-color, #181825); + border-bottom: 1px solid var(--border-color, #313244); + gap: 6px; + flex-shrink: 0; + } + .vai-header-title { + flex: 1; + font-weight: 600; + font-size: 13px; + color: var(--primary-text-color, #cdd6f4); + } + .vai-header-icon { + font-size: 18px; + color: #89b4fa; + user-select: none; + } + .vai-model-badge { + font-size: 10px; + background: #313244; + border-radius: 4px; + padding: 2px 6px; + color: #a6e3a1; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } + .vai-prompt-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + background: #11111b; + border-bottom: 1px solid #313244; + flex-shrink: 0; + min-height: 30px; + } + .vai-prompt-preview { + flex: 1; + font-size: 10px; + color: #6c7086; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + cursor: pointer; + font-style: italic; + } + .vai-prompt-preview:hover { color: #cdd6f4; } + .vai-edit-prompt-btn { + font-size: 10px; + padding: 2px 7px; + background: transparent; + border: 1px solid #45475a; + border-radius: 4px; + color: #89b4fa; + cursor: pointer; + font-family: inherit; + white-space: nowrap; + flex-shrink: 0; + } + .vai-edit-prompt-btn:active { opacity: 0.7; } + /* Inline system-prompt editor */ + .vai-prompt-editor { + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px 8px; + background: #11111b; + border-bottom: 1px solid #313244; + flex-shrink: 0; + } + .vai-prompt-editor-label { + font-size: 10px; + color: #89b4fa; + font-weight: 600; + } + .vai-prompt-editor-textarea { + width: 100%; + min-height: 80px; + max-height: 200px; + resize: vertical; + background: #181825; + border: 1px solid #45475a; + border-radius: 5px; + color: #cdd6f4; + font-family: inherit; + font-size: 12px; + padding: 6px 8px; + box-sizing: border-box; + outline: none; + line-height: 1.4; + } + .vai-prompt-editor-textarea:focus { border-color: #89b4fa; } + .vai-prompt-editor-row { + display: flex; + gap: 4px; + justify-content: flex-end; + } + .vai-messages { + flex: 1; + overflow-y: auto; + padding: 8px; + display: flex; + flex-direction: column; + gap: 10px; + min-height: 0; + } + .vai-message { + border-radius: 8px; + padding: 8px 10px; + line-height: 1.5; + word-break: break-word; + max-width: 100%; + animation: vai-fade-in 0.15s ease; + } + @keyframes vai-fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } + } + .vai-message.user { + background: #313244; + align-self: flex-end; + border-bottom-right-radius: 2px; + max-width: 88%; + white-space: pre-wrap; + } + .vai-message.assistant { + background: #1e1e2e; + border: 1px solid #313244; + align-self: flex-start; + width: 100%; + border-bottom-left-radius: 2px; + } + .vai-message.system-msg { + background: transparent; + border: none; + color: #6c7086; + font-size: 11px; + text-align: center; + align-self: center; + } + .vai-message.error-msg { + background: #45102a; + border: 1px solid #f38ba8; + color: #f38ba8; + } + .vai-pre { + background: #11111b; + border-radius: 6px; + padding: 8px 10px; + overflow-x: auto; + font-size: 12px; + line-height: 1.4; + margin: 6px 0; + position: relative; + } + .vai-pre code { + font-family: var(--editor-font, monospace); + color: #cdd6f4; + display: block; + white-space: pre; + } + .vai-code-lang { + font-size: 10px; + color: #89dceb; + margin-bottom: 4px; + display: block; + } + .vai-code-actions { + display: flex; + gap: 4px; + margin-top: 6px; + flex-wrap: wrap; + } + .vai-btn { + padding: 4px 10px; + border-radius: 5px; + border: none; + cursor: pointer; + font-size: 11px; + font-family: inherit; + transition: opacity 0.15s; + outline: none; + } + .vai-btn:active { opacity: 0.7; } + .vai-btn-primary { + background: #89b4fa; + color: #1e1e2e; + font-weight: 600; + } + .vai-btn-secondary { + background: #313244; + color: #cdd6f4; + } + .vai-btn-success { + background: #a6e3a1; + color: #1e1e2e; + font-weight: 600; + } + .vai-btn-danger { + background: #f38ba8; + color: #1e1e2e; + font-weight: 600; + } + .vai-context-bar { + display: flex; + gap: 4px; + padding: 4px 8px; + background: var(--primary-color, #181825); + border-top: 1px solid #313244; + flex-wrap: wrap; + flex-shrink: 0; + } + .vai-ctx-toggle { + display: flex; + align-items: center; + gap: 3px; + font-size: 10px; + padding: 3px 6px; + border-radius: 4px; + cursor: pointer; + user-select: none; + border: 1px solid #313244; + background: transparent; + color: #6c7086; + transition: all 0.15s; + } + .vai-ctx-toggle.active { + background: #313244; + color: #89b4fa; + border-color: #89b4fa; + } + .vai-input-area { + padding: 8px; + background: var(--primary-color, #181825); + border-top: 1px solid #313244; + display: flex; + flex-direction: column; + gap: 6px; + flex-shrink: 0; + } + .vai-textarea { + width: 100%; + min-height: 64px; + max-height: 160px; + resize: vertical; + background: #11111b; + border: 1px solid #313244; + border-radius: 6px; + color: var(--primary-text-color, #cdd6f4); + font-family: inherit; + font-size: 13px; + padding: 8px; + box-sizing: border-box; + outline: none; + line-height: 1.4; + } + .vai-textarea:focus { + border-color: #89b4fa; + } + .vai-input-row { + display: flex; + gap: 6px; + align-items: center; + } + .vai-send-btn { + padding: 8px 16px; + background: #89b4fa; + color: #1e1e2e; + border: none; + border-radius: 6px; + font-weight: 700; + font-size: 13px; + cursor: pointer; + flex-shrink: 0; + font-family: inherit; + transition: opacity 0.15s; + } + .vai-send-btn:disabled { + opacity: 0.4; + cursor: not-allowed; + } + .vai-send-btn:active:not(:disabled) { opacity: 0.8; } + .vai-clear-btn { + padding: 8px 10px; + background: #313244; + color: #6c7086; + border: none; + border-radius: 6px; + font-size: 12px; + cursor: pointer; + font-family: inherit; + transition: opacity 0.15s; + } + .vai-clear-btn:active { opacity: 0.7; } + .vai-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #313244; + border-top-color: #89b4fa; + border-radius: 50%; + animation: vai-spin 0.8s linear infinite; + flex-shrink: 0; + } + @keyframes vai-spin { + to { transform: rotate(360deg); } + } + .vai-typing-indicator { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border-radius: 8px; + background: #1e1e2e; + border: 1px solid #313244; + align-self: flex-start; + color: #6c7086; + font-size: 12px; + } + .vai-md-p { margin: 4px 0; } + .vai-md-h { font-weight: 700; margin: 8px 0 4px; color: #89b4fa; } + .vai-md-ul { margin: 4px 0 4px 16px; } + .vai-md-li { margin: 2px 0; } + .vai-inline-code { + background: #11111b; + border-radius: 3px; + padding: 1px 4px; + font-family: monospace; + font-size: 12px; + color: #89dceb; + } + .vai-bold { font-weight: 700; } + `; + + /* ── Markdown renderer (lightweight) ──────────────────────────────────── */ + function renderMarkdown(md) { + const frag = document.createDocumentFragment(); + const lines = md.split("\n"); + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + /* fenced code block */ + if (line.startsWith("```")) { + const lang = line.slice(3).trim(); + const codeLines = []; + i++; + while (i < lines.length && !lines[i].startsWith("```")) { + codeLines.push(lines[i]); + i++; + } + i++; + + const code = codeLines.join("\n").trimEnd(); + const pre = document.createElement("div"); + pre.className = "vai-pre"; + if (lang) { + const langBadge = document.createElement("span"); + langBadge.className = "vai-code-lang"; + langBadge.textContent = lang; + pre.appendChild(langBadge); + } + const codeEl = document.createElement("code"); + codeEl.textContent = code; + pre.appendChild(codeEl); + + /* action buttons per code block */ + const actions = document.createElement("div"); + actions.className = "vai-code-actions"; + + const btnInsert = document.createElement("button"); + btnInsert.className = "vai-btn vai-btn-primary"; + btnInsert.textContent = "Insert at cursor"; + btnInsert.onclick = () => { + if (!insertIntoEditor(code)) { + showToast("No active editor"); + } else { + showToast("Inserted ✓"); + } + }; + + const btnReplace = document.createElement("button"); + btnReplace.className = "vai-btn vai-btn-secondary"; + btnReplace.textContent = "Replace file"; + btnReplace.onclick = async () => { + const ok = replaceEditorContent(code); + showToast(ok ? "File replaced ✓" : "No active editor"); + }; + + const btnNew = document.createElement("button"); + btnNew.className = "vai-btn vai-btn-success"; + btnNew.textContent = "New file…"; + btnNew.onclick = async () => { + const name = await acode.prompt("Filename for new file", `untitled.${lang || "txt"}`, "text"); + if (!name) return; + await createNewFile(name, code); + showToast(`Created ${name} ✓`); + }; + + const btnCopy = document.createElement("button"); + btnCopy.className = "vai-btn vai-btn-secondary"; + btnCopy.textContent = "Copy"; + btnCopy.onclick = () => { + navigator.clipboard?.writeText(code).then(() => showToast("Copied ✓")).catch(() => {}); + }; + + actions.append(btnInsert, btnReplace, btnNew, btnCopy); + pre.appendChild(actions); + frag.appendChild(pre); + continue; + } + + /* heading */ + const hMatch = line.match(/^(#{1,6})\s+(.*)/); + if (hMatch) { + const h = document.createElement("div"); + h.className = "vai-md-h"; + h.style.fontSize = `${15 - hMatch[1].length}px`; + h.appendChild(renderInline(hMatch[2])); + frag.appendChild(h); + i++; + continue; + } + + /* bullet list */ + if (/^[-*]\s/.test(line)) { + const ul = document.createElement("ul"); + ul.className = "vai-md-ul"; + while (i < lines.length && /^[-*]\s/.test(lines[i])) { + const li = document.createElement("li"); + li.className = "vai-md-li"; + li.appendChild(renderInline(lines[i].slice(2))); + ul.appendChild(li); + i++; + } + frag.appendChild(ul); + continue; + } + + /* blank line */ + if (!line.trim()) { + i++; + continue; + } + + /* paragraph */ + const p = document.createElement("p"); + p.className = "vai-md-p"; + p.appendChild(renderInline(line)); + frag.appendChild(p); + i++; + } + + return frag; + } + + function renderInline(text) { + const span = document.createElement("span"); + const parts = text.split(/(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g); + for (const part of parts) { + if (part.startsWith("`") && part.endsWith("`")) { + const code = document.createElement("code"); + code.className = "vai-inline-code"; + code.textContent = part.slice(1, -1); + span.appendChild(code); + } else if (part.startsWith("**") && part.endsWith("**")) { + const b = document.createElement("span"); + b.className = "vai-bold"; + b.textContent = part.slice(2, -2); + span.appendChild(b); + } else if (part.startsWith("*") && part.endsWith("*") && part.length > 2) { + const em = document.createElement("em"); + em.textContent = part.slice(1, -1); + span.appendChild(em); + } else { + span.appendChild(document.createTextNode(part)); + } + } + return span; + } + + /* ── Toast helper ─────────────────────────────────────────────────────── */ + function showToast(msg) { + try { + acode.require("toast")(msg, 2000); + } catch (_) {} + } + + /* ── Chat state ───────────────────────────────────────────────────────── */ + let conversationHistory = []; + let isThinking = false; + + /* ── UI construction ──────────────────────────────────────────────────── */ + let $messagesEl = null; + let $textareaEl = null; + let $sendBtn = null; + let $modelBadge = null; + let $ctxCurrentFile = null; + let $ctxSelection = null; + let $ctxOpenFiles = null; + + function buildPanel(container) { + /* inject styles once */ + if (!document.getElementById("vai-styles")) { + const style = document.createElement("style"); + style.id = "vai-styles"; + style.textContent = STYLES; + document.head.appendChild(style); + } + + const panel = document.createElement("div"); + panel.className = "vai-panel"; + + /* ── header ── */ + const header = document.createElement("div"); + header.className = "vai-header"; + + const headerIcon = document.createElement("span"); + headerIcon.className = "vai-header-icon"; + headerIcon.textContent = "✦"; + + const title = document.createElement("span"); + title.className = "vai-header-title"; + title.textContent = "Venice AI Wizard"; + + $modelBadge = document.createElement("span"); + $modelBadge.className = "vai-model-badge"; + $modelBadge.title = "Click to change model"; + $modelBadge.textContent = getModelLabel(cfg.model); + $modelBadge.onclick = openModelPicker; + + const settingsBtn = document.createElement("span"); + settingsBtn.className = "icon settings"; + settingsBtn.title = "Plugin settings"; + settingsBtn.style.cssText = "cursor:pointer;font-size:18px;color:#6c7086;"; + settingsBtn.onclick = openSettings; + + header.append(headerIcon, title, $modelBadge, settingsBtn); + + /* ── messages area ── */ + $messagesEl = document.createElement("div"); + $messagesEl.className = "vai-messages"; + + /* (prompt bar gets inserted after header is appended to panel) */ + + addSystemMessage("Ask me to write, edit, read or create any code. I have full context of your open files."); + + /* ── context toggles ── */ + const ctxBar = document.createElement("div"); + ctxBar.className = "vai-context-bar"; + + $ctxCurrentFile = makeCtxToggle("📄 Current file", cfg.includeCurrentFile, (v) => { + cfg.includeCurrentFile = v; + saveSettings(); + }); + $ctxSelection = makeCtxToggle("✂️ Selection", cfg.includeSelection, (v) => { + cfg.includeSelection = v; + saveSettings(); + }); + $ctxOpenFiles = makeCtxToggle("📂 All open files", cfg.includeOpenFiles, (v) => { + cfg.includeOpenFiles = v; + saveSettings(); + }); + + ctxBar.append($ctxCurrentFile, $ctxSelection, $ctxOpenFiles); + + /* ── input area ── */ + const inputArea = document.createElement("div"); + inputArea.className = "vai-input-area"; + + $textareaEl = document.createElement("textarea"); + $textareaEl.className = "vai-textarea"; + $textareaEl.placeholder = "Ask Venice AI to write, edit or create code…"; + $textareaEl.setAttribute("rows", "3"); + + $textareaEl.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }); + + const inputRow = document.createElement("div"); + inputRow.className = "vai-input-row"; + + $sendBtn = document.createElement("button"); + $sendBtn.className = "vai-send-btn"; + $sendBtn.textContent = "Send"; + $sendBtn.onclick = sendMessage; + + const clearBtn = document.createElement("button"); + clearBtn.className = "vai-clear-btn"; + clearBtn.textContent = "Clear"; + clearBtn.title = "Clear conversation"; + clearBtn.onclick = clearConversation; + + inputRow.append($sendBtn, clearBtn); + inputArea.append($textareaEl, inputRow); + + panel.append(header, $messagesEl, ctxBar, inputArea); + container.appendChild(panel); + + /* insert prompt bar between header and messages (DOM order) */ + buildPromptBar(header); + + return () => { + /* cleanup: remove styles when plugin unloads */ + document.getElementById("vai-styles")?.remove(); + }; + } + + function makeCtxToggle(label, initialValue, onChange) { + const btn = document.createElement("button"); + btn.className = "vai-ctx-toggle" + (initialValue ? " active" : ""); + btn.textContent = label; + btn.onclick = () => { + const next = !btn.classList.contains("active"); + btn.classList.toggle("active", next); + onChange(next); + }; + return btn; + } + + function getModelLabel(modelId) { + const found = MODELS.find(([id]) => id === modelId); + return found ? found[1] : modelId; + } + + /* ── Message rendering ────────────────────────────────────────────────── */ + function addMessage(role, content, streaming = false) { + const div = document.createElement("div"); + div.className = `vai-message ${role}`; + + if (role === "assistant") { + if (streaming) { + div.dataset.streaming = "1"; + div.textContent = ""; + } else { + div.appendChild(renderMarkdown(content)); + addAutoFileCreation(div, content); + } + } else { + div.textContent = content; + } + + $messagesEl.appendChild(div); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + return div; + } + + function addSystemMessage(text) { + const div = document.createElement("div"); + div.className = "vai-message system-msg"; + div.textContent = text; + $messagesEl.appendChild(div); + } + + function addErrorMessage(text) { + const div = document.createElement("div"); + div.className = "vai-message error-msg"; + div.textContent = `⚠ ${text}`; + $messagesEl.appendChild(div); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } + + function addTypingIndicator() { + const div = document.createElement("div"); + div.className = "vai-typing-indicator"; + div.id = "vai-typing"; + const spinner = document.createElement("div"); + spinner.className = "vai-spinner"; + div.append(spinner, "Venice AI is thinking…"); + $messagesEl.appendChild(div); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + return div; + } + + function removeTypingIndicator() { + document.getElementById("vai-typing")?.remove(); + } + + /* Look for "Create file: filename" patterns and offer one-click creation */ + function addAutoFileCreation(div, content) { + const filename = parseFilenameFromResponse(content); + const blocks = extractCodeBlocks(content); + if (!filename || !blocks.length) return; + + const banner = document.createElement("div"); + banner.style.cssText = + "margin-top:8px;padding:6px 8px;background:#1c2a1c;border:1px solid #a6e3a1;border-radius:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;"; + banner.innerHTML = `💾 Create ${filename}?`; + + const btn = document.createElement("button"); + btn.className = "vai-btn vai-btn-success"; + btn.textContent = "Create file"; + btn.onclick = async () => { + await createNewFile(filename, blocks[0].code); + showToast(`${filename} created ✓`); + banner.remove(); + }; + banner.appendChild(btn); + div.appendChild(banner); + } + + /* ── Send message flow ────────────────────────────────────────────────── */ + async function sendMessage() { + if (isThinking) return; + const prompt = $textareaEl.value.trim(); + if (!prompt) return; + + if (!cfg.apiKey) { + addErrorMessage("Please configure your Venice AI API key in settings first."); + openSettings(); + return; + } + + $textareaEl.value = ""; + isThinking = true; + $sendBtn.disabled = true; + + addMessage("user", prompt); + conversationHistory.push({ role: "user", content: prompt }); + + const typingEl = addTypingIndicator(); + + try { + /* Build full messages with context prepended to the FIRST user turn only */ + const contextedHistory = [ + { role: "system", content: cfg.systemPrompt }, + ...buildContextualHistory(), + ]; + + let assistantContent = ""; + let streamingDiv = null; + let rawBuffer = ""; + + try { + /* attempt streaming */ + removeTypingIndicator(); + streamingDiv = document.createElement("div"); + streamingDiv.className = "vai-message assistant"; + $messagesEl.appendChild(streamingDiv); + + await callVenice(contextedHistory, (delta, full) => { + rawBuffer = full; + streamingDiv.textContent = full; + $messagesEl.scrollTop = $messagesEl.scrollHeight; + }); + + assistantContent = rawBuffer; + + /* re-render with markdown after streaming finishes */ + streamingDiv.textContent = ""; + streamingDiv.appendChild(renderMarkdown(assistantContent)); + addAutoFileCreation(streamingDiv, assistantContent); + } catch (streamErr) { + /* streaming failed (browser may not support it) — fall back to non-streaming */ + streamingDiv?.remove(); + removeTypingIndicator(); + + assistantContent = await callVenice(contextedHistory); + addMessage("assistant", assistantContent); + } + + conversationHistory.push({ role: "assistant", content: assistantContent }); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } catch (err) { + removeTypingIndicator(); + addErrorMessage(err.message || String(err)); + } finally { + isThinking = false; + $sendBtn.disabled = false; + $textareaEl.focus(); + } + } + + function buildContextualHistory() { + /* inject file context into the last user message only */ + if (!conversationHistory.length) return []; + const history = [...conversationHistory]; + /* find the last user message and prepend context to it */ + for (let i = history.length - 1; i >= 0; i--) { + if (history[i].role === "user") { + const original = history[i].content; + const contextParts = []; + + if (cfg.includeCurrentFile) { + const ctx = getActiveFileContext(); + if (ctx && ctx.content) { + contextParts.push( + `## Current file: ${ctx.filename}\n\`\`\`${ctx.language}\n${ctx.content}\n\`\`\``, + ); + } + } + + if (cfg.includeSelection) { + const sel = getSelection(); + if (sel) { + contextParts.push(`## Selected text\n\`\`\`\n${sel}\n\`\`\``); + } + } + + if (cfg.includeOpenFiles) { + const others = getOpenFilesContext(); + if (others.length) { + const txt = others + .map( + (f) => `### ${f.filename}\n\`\`\`${f.language}\n${f.content}\n\`\`\``, + ) + .join("\n\n"); + contextParts.push(`## Other open files\n${txt}`); + } + } + + history[i] = { + role: "user", + content: contextParts.length + ? contextParts.join("\n\n") + "\n\n---\n\n" + original + : original, + }; + break; + } + } + return history; + } + + function clearConversation() { + conversationHistory = []; + $messagesEl.innerHTML = ""; + addSystemMessage("Conversation cleared. Ask me anything about your code."); + } + + /* ── Inline system-prompt editor ─────────────────────────────────────── */ + let $promptBar = null; + let $promptPreview = null; + let $promptEditorWrap = null; + + function buildPromptBar(insertAfter) { + $promptBar = document.createElement("div"); + $promptBar.className = "vai-prompt-bar"; + + $promptPreview = document.createElement("span"); + $promptPreview.className = "vai-prompt-preview"; + $promptPreview.title = "Click to edit system prompt"; + refreshPromptPreview(); + $promptPreview.onclick = togglePromptEditor; + + const editBtn = document.createElement("button"); + editBtn.className = "vai-edit-prompt-btn"; + editBtn.textContent = "Edit system prompt"; + editBtn.onclick = togglePromptEditor; + + $promptBar.append($promptPreview, editBtn); + insertAfter.after($promptBar); + + /* build the inline editor (hidden initially) */ + $promptEditorWrap = document.createElement("div"); + $promptEditorWrap.className = "vai-prompt-editor"; + $promptEditorWrap.style.display = "none"; + + const label = document.createElement("div"); + label.className = "vai-prompt-editor-label"; + label.textContent = "System Prompt"; + + const textarea = document.createElement("textarea"); + textarea.className = "vai-prompt-editor-textarea"; + textarea.value = cfg.systemPrompt; + + const row = document.createElement("div"); + row.className = "vai-prompt-editor-row"; + + const resetBtn = document.createElement("button"); + resetBtn.className = "vai-btn vai-btn-secondary"; + resetBtn.textContent = "Reset to default"; + resetBtn.onclick = () => { + textarea.value = DEFAULT_SYSTEM_PROMPT; + }; + + const saveBtn = document.createElement("button"); + saveBtn.className = "vai-btn vai-btn-primary"; + saveBtn.textContent = "Save"; + saveBtn.onclick = () => { + cfg.systemPrompt = textarea.value.trim() || DEFAULT_SYSTEM_PROMPT; + saveSettings(); + refreshPromptPreview(); + togglePromptEditor(); + showToast("System prompt saved ✓"); + }; + + const cancelBtn = document.createElement("button"); + cancelBtn.className = "vai-btn vai-btn-secondary"; + cancelBtn.textContent = "Cancel"; + cancelBtn.onclick = () => { + textarea.value = cfg.systemPrompt; + togglePromptEditor(); + }; + + row.append(resetBtn, cancelBtn, saveBtn); + $promptEditorWrap.append(label, textarea, row); + $promptBar.after($promptEditorWrap); + + return textarea; + } + + function refreshPromptPreview() { + if ($promptPreview) { + $promptPreview.textContent = "System: " + cfg.systemPrompt.slice(0, 70) + (cfg.systemPrompt.length > 70 ? "…" : ""); + } + } + + function togglePromptEditor() { + if (!$promptEditorWrap) return; + const hidden = $promptEditorWrap.style.display === "none"; + $promptEditorWrap.style.display = hidden ? "flex" : "none"; + if (hidden) { + /* sync textarea when opening */ + const ta = $promptEditorWrap.querySelector(".vai-prompt-editor-textarea"); + if (ta) ta.value = cfg.systemPrompt; + } + } + + /* ── Settings ─────────────────────────────────────────────────────────── */ + async function openSettings() { + try { + const settingsPage = acode.require("settings"); + if (settingsPage?.uiSettings?.[`plugin-${PLUGIN_ID}`]) { + settingsPage.uiSettings[`plugin-${PLUGIN_ID}`].show(); + return; + } + } catch (_) {} + + /* fallback: custom settings dialog */ + showCustomSettingsDialog(); + } + + async function showCustomSettingsDialog() { + const values = await acode.multiPrompt("Venice AI Wizard — Settings", [ + { + type: "text", + id: "apiKey", + placeholder: "Venice AI API Key", + value: cfg.apiKey, + required: false, + }, + { + type: "text", + id: "systemPrompt", + placeholder: "System Prompt", + value: cfg.systemPrompt, + required: false, + }, + { + type: "text", + id: "temperature", + placeholder: "Temperature (0.0 – 1.0)", + value: String(cfg.temperature), + required: false, + }, + { + type: "text", + id: "maxTokens", + placeholder: "Max Tokens", + value: String(cfg.maxTokens), + required: false, + }, + ]); + + if (!values) return; + + if (values.apiKey !== undefined) cfg.apiKey = values.apiKey.trim(); + if (values.systemPrompt !== undefined) cfg.systemPrompt = values.systemPrompt || DEFAULT_SYSTEM_PROMPT; + if (values.temperature !== undefined) cfg.temperature = values.temperature || "0.7"; + if (values.maxTokens !== undefined) cfg.maxTokens = values.maxTokens || "4096"; + + saveSettings(); + showToast("Settings saved ✓"); + } + + async function openModelPicker() { + try { + const select = acode.require("select"); + const chosen = await select("Select Venice AI Model", MODELS, { + default: cfg.model, + }); + if (chosen) { + cfg.model = chosen; + $modelBadge.textContent = getModelLabel(chosen); + saveSettings(); + showToast(`Model: ${getModelLabel(chosen)}`); + } + } catch (e) { + console.error("[Venice AI] model picker error:", e); + } + } + + /* ── Acode settings integration ───────────────────────────────────────── */ + const settingsList = [ + { + key: "apiKey", + text: "Venice AI API Key", + icon: "vpn_key", + info: "Your Venice AI API key from venice.ai", + value: cfg.apiKey, + prompt: "Enter Venice AI API key", + promptType: "password", + }, + { + key: "model", + text: "AI Model", + icon: "smart_toy", + info: "Which Venice AI model to use", + value: cfg.model, + select: MODELS, + valueText(v) { + return getModelLabel(v); + }, + }, + { + key: "temperature", + text: "Temperature", + icon: "thermostat", + info: "Creativity level (0.0 = deterministic, 1.0 = creative)", + value: cfg.temperature, + prompt: "Temperature (0.0 – 1.0)", + promptType: "number", + }, + { + key: "maxTokens", + text: "Max Tokens", + icon: "token", + info: "Maximum tokens in each response", + value: cfg.maxTokens, + prompt: "Max tokens", + promptType: "number", + }, + { + key: "systemPrompt", + text: "System Prompt", + icon: "edit_note", + info: "Custom instructions for the AI assistant", + value: cfg.systemPrompt, + prompt: "System prompt", + promptType: "textarea", + }, + { + key: "includeCurrentFile", + text: "Include Current File", + icon: "description", + info: "Send the active file content to the AI", + checkbox: cfg.includeCurrentFile, + value: cfg.includeCurrentFile, + }, + { + key: "includeSelection", + text: "Include Selection", + icon: "select_all", + info: "Send selected text as context", + checkbox: cfg.includeSelection, + value: cfg.includeSelection, + }, + { + key: "includeOpenFiles", + text: "Include All Open Files", + icon: "folder_open", + info: "Include up to 4 other open files as context", + checkbox: cfg.includeOpenFiles, + value: cfg.includeOpenFiles, + }, + ]; + + function onSettingChange(key, value) { + cfg[key] = value; + saveSettings(); + + if (key === "model" && $modelBadge) { + $modelBadge.textContent = getModelLabel(value); + } + if (key === "includeCurrentFile" && $ctxCurrentFile) { + $ctxCurrentFile.classList.toggle("active", !!value); + } + if (key === "includeSelection" && $ctxSelection) { + $ctxSelection.classList.toggle("active", !!value); + } + if (key === "includeOpenFiles" && $ctxOpenFiles) { + $ctxOpenFiles.classList.toggle("active", !!value); + } + } + + /* ── Plugin lifecycle ─────────────────────────────────────────────────── */ + function init(baseUrl, $page, options) { + const sidebarApps = acode.require("sidebarApps"); + + sidebarApps.add( + "auto_fix_high", /* Acode built-in icon class */ + PLUGIN_ID, + "Venice AI Wizard", + (container) => buildPanel(container), + ); + } + + function destroy() { + const sidebarApps = acode.require("sidebarApps"); + sidebarApps.remove(PLUGIN_ID); + document.getElementById("vai-styles")?.remove(); + } + + /* register */ + if (typeof acode !== "undefined") { + acode.setPluginInit(PLUGIN_ID, init, { + list: settingsList, + cb: onSettingChange, + }); + acode.setPluginUnmount(PLUGIN_ID, destroy); + } +})(); diff --git a/acode-plugins/venice-ai-wizard/plugin.json b/acode-plugins/venice-ai-wizard/plugin.json new file mode 100644 index 000000000..30b8fd41e --- /dev/null +++ b/acode-plugins/venice-ai-wizard/plugin.json @@ -0,0 +1,14 @@ +{ + "id": "venice-ai-wizard", + "name": "Venice AI Coding Wizard", + "version": "1.0.0", + "main": "main.js", + "icon": "icon.png", + "readme": "readme.md", + "author": { + "name": "DUptain1993", + "email": "daniel.r.uptain93@gmail.com" + }, + "description": "An AI coding wizard powered by Venice AI. Generate, edit, read and create code using uncensored models like Gemma 4. Full codebase context awareness.", + "minVersionCode": 290 +} diff --git a/acode-plugins/venice-ai-wizard/readme.md b/acode-plugins/venice-ai-wizard/readme.md new file mode 100644 index 000000000..ba4060372 --- /dev/null +++ b/acode-plugins/venice-ai-wizard/readme.md @@ -0,0 +1,78 @@ +# Venice AI Coding Wizard + +An AI coding assistant for Acode powered by [Venice AI](https://venice.ai) — the privacy-first, uncensored AI platform. + +## Features + +- **Full codebase context** — automatically sends your active file, selected text, or all open files to the AI +- **Write, edit, read & create** — generate code, refactor files, create new files from AI output +- **Streaming responses** — see the AI think in real-time +- **Model selection** — choose from Gemma 4 Uncensored, Llama 3.3 70B, DeepSeek R1, Qwen 2.5 Coder and more +- **Editable system prompt** — customize the AI's personality and instructions directly from the panel +- **Code block actions** — each code block has "Insert at cursor", "Replace file", "New file…" and "Copy" buttons +- **Conversation history** — multi-turn conversations with full context + +## Setup + +1. Install the plugin in Acode +2. Open the Venice AI Wizard sidebar panel (click the ✦ icon in the sidebar) +3. Click the ⚙ settings icon and enter your **Venice AI API key** + - Get a free key at [venice.ai](https://venice.ai) +4. Select your preferred model +5. Start chatting! + +## System Prompt + +The AI behavior is governed by a **system prompt** that you can customize at any time. Look for the italic preview bar just below the header — click it or the "Edit system prompt" button to expand an inline editor where you can: + +- Write any instructions you want the AI to follow +- Reset to the built-in default with the "Reset to default" button +- Save changes immediately + +## Context Options + +Three toggles below the chat control what context is sent with each message: + +| Toggle | What it sends | +|--------|--------------| +| 📄 Current file | The full content of the file you have open | +| ✂️ Selection | Only the text you have highlighted | +| 📂 All open files | Up to 4 other open editor tabs | + +## Models + +| Model ID | Notes | +|----------|-------| +| `gemma-4-uncensored` | Default — capable & uncensored | +| `llama-3.3-70b` | Strong general-purpose | +| `deepseek-r1-671b` | Deep reasoning | +| `qwen-2.5-coder-32b-instruct` | Specialized for code | +| `mistral-31-24b` | Fast, balanced | +| `venice-uncensored` | Venice's own uncensored model | +| `llama-3.2-3b` | Lightweight & fast | + +## File Operations + +When the AI suggests code, every fenced code block gets action buttons: + +- **Insert at cursor** — insert the code at the current editor cursor position +- **Replace file** — replace the entire active file with the generated code +- **New file…** — prompts for a filename and opens a new unsaved editor tab with the code +- **Copy** — copy to clipboard + +If the AI mentions creating a specific filename (e.g. `Create file: index.js`), a quick-create banner appears automatically. + +## Settings + +All settings are accessible via the ⚙ icon in the panel header or through Acode's plugin settings: + +- **Venice AI API Key** — required for all requests +- **AI Model** — which Venice AI model to use +- **Temperature** — randomness (0.0 = precise, 1.0 = creative) +- **Max Tokens** — response length limit +- **System Prompt** — full AI personality / instruction control +- **Context toggles** — what to include with each request + +## Privacy + +Venice AI does not train on your data and offers uncensored models. See [venice.ai/privacy](https://venice.ai/privacy) for details. From b8633e7627f1b7439b62b04a83016e41abeefb7c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 09:16:24 +0000 Subject: [PATCH 2/2] fix: rewrite plugin v1.1.0 to fix crash on older Android WebViews Replace Element.after() DOM calls (unsupported on some Android WebViews) with strict appendChild ordering. Wrap init() in try/catch so any failure logs to console instead of marking the plugin broken. Add inline system prompt editor in the sidebar panel. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01KzaGGLg7cz4jMb85KMAXrb --- acode-plugins/venice-ai-wizard.zip | Bin 13425 -> 12472 bytes acode-plugins/venice-ai-wizard/main.js | 1621 +++++++------------- acode-plugins/venice-ai-wizard/plugin.json | 2 +- 3 files changed, 576 insertions(+), 1047 deletions(-) diff --git a/acode-plugins/venice-ai-wizard.zip b/acode-plugins/venice-ai-wizard.zip index 0031b8236c1d08a1e6ba5b74110e6c52efb32b43..7bb30be9be140f2d71e7277476ba1604e7304db7 100644 GIT binary patch delta 10156 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 delta 11154 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 R4$CJgfrS*Bg#F*m{1<8yINksN diff --git a/acode-plugins/venice-ai-wizard/main.js b/acode-plugins/venice-ai-wizard/main.js index 160dae3fb..141260356 100644 --- a/acode-plugins/venice-ai-wizard/main.js +++ b/acode-plugins/venice-ai-wizard/main.js @@ -1,7 +1,15 @@ /** - * Venice AI Coding Wizard — Acode Plugin + * Venice AI Coding Wizard — Acode Plugin v1.1.0 * Integrates Venice AI (OpenAI-compatible) into Acode for full-context * code generation, editing, reading and file creation. + * + * Fixes in v1.1.0: + * - Removed Element.after() (unsupported on older Android WebViews); + * replaced with insertBefore / appendChild ordering + * - Wrapped init & buildPanel in try/catch so failures don't mark the + * plugin broken in Acode's BROKEN_PLUGINS registry + * - Simplified settings list value types + * - Added defensive null-checks throughout */ (function () { "use strict"; @@ -29,16 +37,6 @@ "When asked to create a file, state the filename clearly before the code block."; /* ── Settings ─────────────────────────────────────────────────────────── */ - const cfg = loadSettings(); - - function loadSettings() { - try { - const raw = localStorage.getItem(STORAGE_KEY); - if (raw) return Object.assign(defaults(), JSON.parse(raw)); - } catch (_) {} - return defaults(); - } - function defaults() { return { apiKey: "", @@ -52,6 +50,16 @@ }; } + function loadSettings() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return Object.assign(defaults(), JSON.parse(raw)); + } catch (_) {} + return defaults(); + } + + const cfg = loadSettings(); + function saveSettings() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg)); @@ -68,15 +76,15 @@ const useStream = typeof onChunk === "function"; - const res = await fetch(`${VENICE_BASE}/chat/completions`, { + const res = await fetch(VENICE_BASE + "/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${cfg.apiKey}`, + Authorization: "Bearer " + cfg.apiKey, }, body: JSON.stringify({ model: cfg.model, - messages, + messages: messages, temperature: parseFloat(cfg.temperature) || 0.7, max_tokens: parseInt(cfg.maxTokens, 10) || 4096, stream: useStream, @@ -84,38 +92,38 @@ }); if (!res.ok) { - let msg = `Venice AI error ${res.status}`; + var msg = "Venice AI error " + res.status; try { - const e = await res.json(); - msg = e?.error?.message || msg; + var e = await res.json(); + msg = (e && e.error && e.error.message) || msg; } catch (_) {} throw new Error(msg); } if (!useStream) { - const data = await res.json(); - return data.choices?.[0]?.message?.content ?? ""; + var data = await res.json(); + return (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) || ""; } /* streaming */ - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - let full = ""; - let buf = ""; + var reader = res.body.getReader(); + var decoder = new TextDecoder(); + var full = ""; + var buf = ""; while (true) { - const { done, value } = await reader.read(); - if (done) break; - buf += decoder.decode(value, { stream: true }); - const lines = buf.split("\n"); + var chunk = await reader.read(); + if (chunk.done) break; + buf += decoder.decode(chunk.value, { stream: true }); + var lines = buf.split("\n"); buf = lines.pop(); - for (const line of lines) { - const trimmed = line.trim(); + for (var i = 0; i < lines.length; i++) { + var trimmed = lines[i].trim(); if (!trimmed || trimmed === "data: [DONE]") continue; - if (trimmed.startsWith("data: ")) { + if (trimmed.indexOf("data: ") === 0) { try { - const json = JSON.parse(trimmed.slice(6)); - const delta = json.choices?.[0]?.delta?.content ?? ""; + var json = JSON.parse(trimmed.slice(6)); + var delta = (json.choices && json.choices[0] && json.choices[0].delta && json.choices[0].delta.content) || ""; if (delta) { full += delta; onChunk(delta, full); @@ -130,13 +138,13 @@ /* ── Context helpers ──────────────────────────────────────────────────── */ function getActiveFileContext() { try { - const em = window.editorManager; + var em = window.editorManager; if (!em) return null; - const file = em.activeFile; + var file = em.activeFile; if (!file || file.type !== "editor") return null; - const content = em.editor?.getValue?.() ?? ""; - const filename = file.filename || file.name || "untitled"; - return { filename, content, language: guesslang(filename) }; + var content = (em.editor && em.editor.getValue && em.editor.getValue()) || ""; + var filename = file.filename || file.name || "untitled"; + return { filename: filename, content: content, language: guesslang(filename) }; } catch (_) { return null; } @@ -144,10 +152,10 @@ function getSelection() { try { - const editor = window.editorManager?.editor; - if (!editor) return ""; - const { from, to } = editor.state.selection.main; - return from !== to ? editor.state.doc.sliceString(from, to) : ""; + var editor = window.editorManager && window.editorManager.editor; + if (!editor || !editor.state) return ""; + var sel = editor.state.selection.main; + return sel.from !== sel.to ? editor.state.doc.sliceString(sel.from, sel.to) : ""; } catch (_) { return ""; } @@ -155,101 +163,82 @@ function getOpenFilesContext() { try { - const em = window.editorManager; - if (!em) return []; - return (em.files || []) - .filter((f) => f.type === "editor" && f !== em.activeFile) + var em = window.editorManager; + if (!em || !em.files) return []; + return em.files + .filter(function (f) { return f.type === "editor" && f !== em.activeFile; }) .slice(0, 4) - .map((f) => ({ - filename: f.filename || f.name || "untitled", - content: f.session?.doc?.toString?.() ?? "", - language: guesslang(f.filename || f.name || ""), - })); + .map(function (f) { + return { + filename: f.filename || f.name || "untitled", + content: (f.session && f.session.doc && f.session.doc.toString && f.session.doc.toString()) || "", + language: guesslang(f.filename || f.name || ""), + }; + }); } catch (_) { return []; } } function guesslang(filename) { - const ext = filename.split(".").pop().toLowerCase(); - const map = { - js: "javascript", - ts: "typescript", - jsx: "jsx", - tsx: "tsx", - py: "python", - rb: "ruby", - java: "java", - kt: "kotlin", - go: "go", - rs: "rust", - cpp: "cpp", - c: "c", - cs: "csharp", - php: "php", - html: "html", - css: "css", - scss: "scss", - json: "json", - xml: "xml", - md: "markdown", - sh: "bash", - sql: "sql", - yaml: "yaml", - yml: "yaml", + if (!filename) return "text"; + var ext = filename.split(".").pop().toLowerCase(); + var map = { + js: "javascript", ts: "typescript", jsx: "jsx", tsx: "tsx", + py: "python", rb: "ruby", java: "java", kt: "kotlin", + go: "go", rs: "rust", cpp: "cpp", c: "c", cs: "csharp", + php: "php", html: "html", css: "css", scss: "scss", + json: "json", xml: "xml", md: "markdown", sh: "bash", + sql: "sql", yaml: "yaml", yml: "yaml", }; return map[ext] || ext || "text"; } - function buildMessages(userPrompt) { - const msgs = [{ role: "system", content: cfg.systemPrompt }]; - - const contextParts = []; + function buildContextualHistory() { + if (!conversationHistory.length) return []; + var history = conversationHistory.map(function (m) { return { role: m.role, content: m.content }; }); - if (cfg.includeCurrentFile) { - const ctx = getActiveFileContext(); - if (ctx && ctx.content) { - contextParts.push( - `## Current file: ${ctx.filename}\n\`\`\`${ctx.language}\n${ctx.content}\n\`\`\``, - ); - } - } + for (var i = history.length - 1; i >= 0; i--) { + if (history[i].role === "user") { + var original = history[i].content; + var parts = []; - if (cfg.includeSelection) { - const sel = getSelection(); - if (sel) { - contextParts.push(`## Selected text\n\`\`\`\n${sel}\n\`\`\``); - } - } + if (cfg.includeCurrentFile) { + var ctx = getActiveFileContext(); + if (ctx && ctx.content) { + parts.push("## Current file: " + ctx.filename + "\n```" + ctx.language + "\n" + ctx.content + "\n```"); + } + } + if (cfg.includeSelection) { + var sel = getSelection(); + if (sel) parts.push("## Selected text\n```\n" + sel + "\n```"); + } + if (cfg.includeOpenFiles) { + var others = getOpenFilesContext(); + if (others.length) { + var txt = others.map(function (f) { + return "### " + f.filename + "\n```" + f.language + "\n" + f.content + "\n```"; + }).join("\n\n"); + parts.push("## Other open files\n" + txt); + } + } - if (cfg.includeOpenFiles) { - const others = getOpenFilesContext(); - if (others.length) { - const othersText = others - .map( - (f) => - `### ${f.filename}\n\`\`\`${f.language}\n${f.content}\n\`\`\``, - ) - .join("\n\n"); - contextParts.push(`## Other open files\n${othersText}`); + history[i] = { + role: "user", + content: parts.length ? parts.join("\n\n") + "\n\n---\n\n" + original : original, + }; + break; } } - - let finalPrompt = userPrompt; - if (contextParts.length) { - finalPrompt = contextParts.join("\n\n") + "\n\n---\n\n" + userPrompt; - } - - msgs.push({ role: "user", content: finalPrompt }); - return msgs; + return history; } /* ── File operations ──────────────────────────────────────────────────── */ function insertIntoEditor(text) { try { - const editor = window.editorManager?.editor; + var editor = window.editorManager && window.editorManager.editor; if (!editor) return false; - editor.insert(text); + editor.insert(String(text)); return true; } catch (_) { return false; @@ -258,12 +247,10 @@ function replaceEditorContent(text) { try { - const em = window.editorManager; - if (!em?.editor) return false; - const state = em.editor.state; - em.editor.dispatch({ - changes: { from: 0, to: state.doc.length, insert: text }, - }); + var em = window.editorManager; + if (!em || !em.editor) return false; + var state = em.editor.state; + em.editor.dispatch({ changes: { from: 0, to: state.doc.length, insert: String(text) } }); return true; } catch (_) { return false; @@ -272,15 +259,6 @@ async function createNewFile(filename, content) { try { - const fs = acode.require("fs") || acode.require("fsOperation"); - const FileBrowser = acode.require("fileBrowser"); - const helpers = acode.require("helpers"); - const Url = acode.require("Url"); - - const em = window.editorManager; - if (!em) return false; - - /* Use Acode's newEditorFile API */ acode.newEditorFile(filename, { text: content, isUnsaved: true }); return true; } catch (e) { @@ -291,477 +269,130 @@ /* ── Code block extraction ────────────────────────────────────────────── */ function extractCodeBlocks(markdown) { - const blocks = []; - const re = /```(\w*)\n?([\s\S]*?)```/g; - let m; + var blocks = []; + var re = /```(\w*)\n?([\s\S]*?)```/g; + var m; while ((m = re.exec(markdown)) !== null) { - blocks.push({ lang: m[1] || "text", code: m[2].trimEnd() }); + blocks.push({ lang: m[1] || "text", code: m[2].replace(/\s+$/, "") }); } return blocks; } function parseFilenameFromResponse(text) { - const patterns = [ + var patterns = [ /create(?:d)?\s+(?:a\s+)?(?:new\s+)?file\s*[:`]?\s*[`'"]?([\w./\\-]+\.\w+)/i, /filename[:`]?\s*[`'"]?([\w./\\-]+\.\w+)/i, /(?:save|write)\s+(?:to|as)\s*[`'"]?([\w./\\-]+\.\w+)/i, ]; - for (const p of patterns) { - const m = text.match(p); + for (var i = 0; i < patterns.length; i++) { + var m = text.match(patterns[i]); if (m) return m[1]; } return null; } - /* ── Styles ───────────────────────────────────────────────────────────── */ - const STYLES = ` - .vai-panel { - display: flex; - flex-direction: column; - height: 100%; - background: var(--secondary-color, #1e1e2e); - color: var(--primary-text-color, #cdd6f4); - font-family: var(--editor-font, monospace); - font-size: 13px; - } - .vai-header { - display: flex; - align-items: center; - padding: 8px 10px; - background: var(--primary-color, #181825); - border-bottom: 1px solid var(--border-color, #313244); - gap: 6px; - flex-shrink: 0; - } - .vai-header-title { - flex: 1; - font-weight: 600; - font-size: 13px; - color: var(--primary-text-color, #cdd6f4); - } - .vai-header-icon { - font-size: 18px; - color: #89b4fa; - user-select: none; - } - .vai-model-badge { - font-size: 10px; - background: #313244; - border-radius: 4px; - padding: 2px 6px; - color: #a6e3a1; - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; - } - .vai-prompt-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - background: #11111b; - border-bottom: 1px solid #313244; - flex-shrink: 0; - min-height: 30px; - } - .vai-prompt-preview { - flex: 1; - font-size: 10px; - color: #6c7086; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - font-style: italic; - } - .vai-prompt-preview:hover { color: #cdd6f4; } - .vai-edit-prompt-btn { - font-size: 10px; - padding: 2px 7px; - background: transparent; - border: 1px solid #45475a; - border-radius: 4px; - color: #89b4fa; - cursor: pointer; - font-family: inherit; - white-space: nowrap; - flex-shrink: 0; - } - .vai-edit-prompt-btn:active { opacity: 0.7; } - /* Inline system-prompt editor */ - .vai-prompt-editor { - display: flex; - flex-direction: column; - gap: 4px; - padding: 6px 8px; - background: #11111b; - border-bottom: 1px solid #313244; - flex-shrink: 0; - } - .vai-prompt-editor-label { - font-size: 10px; - color: #89b4fa; - font-weight: 600; - } - .vai-prompt-editor-textarea { - width: 100%; - min-height: 80px; - max-height: 200px; - resize: vertical; - background: #181825; - border: 1px solid #45475a; - border-radius: 5px; - color: #cdd6f4; - font-family: inherit; - font-size: 12px; - padding: 6px 8px; - box-sizing: border-box; - outline: none; - line-height: 1.4; - } - .vai-prompt-editor-textarea:focus { border-color: #89b4fa; } - .vai-prompt-editor-row { - display: flex; - gap: 4px; - justify-content: flex-end; - } - .vai-messages { - flex: 1; - overflow-y: auto; - padding: 8px; - display: flex; - flex-direction: column; - gap: 10px; - min-height: 0; - } - .vai-message { - border-radius: 8px; - padding: 8px 10px; - line-height: 1.5; - word-break: break-word; - max-width: 100%; - animation: vai-fade-in 0.15s ease; - } - @keyframes vai-fade-in { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } - } - .vai-message.user { - background: #313244; - align-self: flex-end; - border-bottom-right-radius: 2px; - max-width: 88%; - white-space: pre-wrap; - } - .vai-message.assistant { - background: #1e1e2e; - border: 1px solid #313244; - align-self: flex-start; - width: 100%; - border-bottom-left-radius: 2px; - } - .vai-message.system-msg { - background: transparent; - border: none; - color: #6c7086; - font-size: 11px; - text-align: center; - align-self: center; - } - .vai-message.error-msg { - background: #45102a; - border: 1px solid #f38ba8; - color: #f38ba8; - } - .vai-pre { - background: #11111b; - border-radius: 6px; - padding: 8px 10px; - overflow-x: auto; - font-size: 12px; - line-height: 1.4; - margin: 6px 0; - position: relative; - } - .vai-pre code { - font-family: var(--editor-font, monospace); - color: #cdd6f4; - display: block; - white-space: pre; - } - .vai-code-lang { - font-size: 10px; - color: #89dceb; - margin-bottom: 4px; - display: block; - } - .vai-code-actions { - display: flex; - gap: 4px; - margin-top: 6px; - flex-wrap: wrap; - } - .vai-btn { - padding: 4px 10px; - border-radius: 5px; - border: none; - cursor: pointer; - font-size: 11px; - font-family: inherit; - transition: opacity 0.15s; - outline: none; - } - .vai-btn:active { opacity: 0.7; } - .vai-btn-primary { - background: #89b4fa; - color: #1e1e2e; - font-weight: 600; - } - .vai-btn-secondary { - background: #313244; - color: #cdd6f4; - } - .vai-btn-success { - background: #a6e3a1; - color: #1e1e2e; - font-weight: 600; - } - .vai-btn-danger { - background: #f38ba8; - color: #1e1e2e; - font-weight: 600; - } - .vai-context-bar { - display: flex; - gap: 4px; - padding: 4px 8px; - background: var(--primary-color, #181825); - border-top: 1px solid #313244; - flex-wrap: wrap; - flex-shrink: 0; - } - .vai-ctx-toggle { - display: flex; - align-items: center; - gap: 3px; - font-size: 10px; - padding: 3px 6px; - border-radius: 4px; - cursor: pointer; - user-select: none; - border: 1px solid #313244; - background: transparent; - color: #6c7086; - transition: all 0.15s; - } - .vai-ctx-toggle.active { - background: #313244; - color: #89b4fa; - border-color: #89b4fa; - } - .vai-input-area { - padding: 8px; - background: var(--primary-color, #181825); - border-top: 1px solid #313244; - display: flex; - flex-direction: column; - gap: 6px; - flex-shrink: 0; - } - .vai-textarea { - width: 100%; - min-height: 64px; - max-height: 160px; - resize: vertical; - background: #11111b; - border: 1px solid #313244; - border-radius: 6px; - color: var(--primary-text-color, #cdd6f4); - font-family: inherit; - font-size: 13px; - padding: 8px; - box-sizing: border-box; - outline: none; - line-height: 1.4; - } - .vai-textarea:focus { - border-color: #89b4fa; - } - .vai-input-row { - display: flex; - gap: 6px; - align-items: center; - } - .vai-send-btn { - padding: 8px 16px; - background: #89b4fa; - color: #1e1e2e; - border: none; - border-radius: 6px; - font-weight: 700; - font-size: 13px; - cursor: pointer; - flex-shrink: 0; - font-family: inherit; - transition: opacity 0.15s; - } - .vai-send-btn:disabled { - opacity: 0.4; - cursor: not-allowed; - } - .vai-send-btn:active:not(:disabled) { opacity: 0.8; } - .vai-clear-btn { - padding: 8px 10px; - background: #313244; - color: #6c7086; - border: none; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - font-family: inherit; - transition: opacity 0.15s; - } - .vai-clear-btn:active { opacity: 0.7; } - .vai-spinner { - display: inline-block; - width: 16px; - height: 16px; - border: 2px solid #313244; - border-top-color: #89b4fa; - border-radius: 50%; - animation: vai-spin 0.8s linear infinite; - flex-shrink: 0; - } - @keyframes vai-spin { - to { transform: rotate(360deg); } - } - .vai-typing-indicator { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border-radius: 8px; - background: #1e1e2e; - border: 1px solid #313244; - align-self: flex-start; - color: #6c7086; - font-size: 12px; - } - .vai-md-p { margin: 4px 0; } - .vai-md-h { font-weight: 700; margin: 8px 0 4px; color: #89b4fa; } - .vai-md-ul { margin: 4px 0 4px 16px; } - .vai-md-li { margin: 2px 0; } - .vai-inline-code { - background: #11111b; - border-radius: 3px; - padding: 1px 4px; - font-family: monospace; - font-size: 12px; - color: #89dceb; + /* ── Toast helper ─────────────────────────────────────────────────────── */ + function showToast(msg) { + try { acode.require("toast")(msg, 2000); } catch (_) {} } - .vai-bold { font-weight: 700; } - `; - /* ── Markdown renderer (lightweight) ──────────────────────────────────── */ + /* ── Styles ───────────────────────────────────────────────────────────── */ + var STYLES = [ + ".vai-panel{display:flex;flex-direction:column;height:100%;background:var(--secondary-color,#1e1e2e);color:var(--primary-text-color,#cdd6f4);font-size:13px;}", + ".vai-header{display:flex;align-items:center;padding:8px 10px;background:var(--primary-color,#181825);border-bottom:1px solid #313244;gap:6px;flex-shrink:0;}", + ".vai-header-title{flex:1;font-weight:600;font-size:13px;}", + ".vai-header-icon{font-size:18px;color:#89b4fa;user-select:none;}", + ".vai-model-badge{font-size:10px;background:#313244;border-radius:4px;padding:2px 6px;color:#a6e3a1;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;}", + ".vai-prompt-bar{display:flex;align-items:center;gap:6px;padding:4px 8px;background:#11111b;border-bottom:1px solid #313244;flex-shrink:0;min-height:30px;}", + ".vai-prompt-preview{flex:1;font-size:10px;color:#6c7086;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;font-style:italic;}", + ".vai-prompt-preview:hover{color:#cdd6f4;}", + ".vai-edit-prompt-btn{font-size:10px;padding:2px 7px;background:transparent;border:1px solid #45475a;border-radius:4px;color:#89b4fa;cursor:pointer;white-space:nowrap;flex-shrink:0;}", + ".vai-prompt-editor{flex-direction:column;gap:4px;padding:6px 8px;background:#11111b;border-bottom:1px solid #313244;flex-shrink:0;}", + ".vai-prompt-editor-label{font-size:10px;color:#89b4fa;font-weight:600;}", + ".vai-prompt-editor-textarea{width:100%;min-height:80px;max-height:200px;resize:vertical;background:#181825;border:1px solid #45475a;border-radius:5px;color:#cdd6f4;font-size:12px;padding:6px 8px;box-sizing:border-box;outline:none;line-height:1.4;}", + ".vai-prompt-editor-textarea:focus{border-color:#89b4fa;}", + ".vai-prompt-editor-row{display:flex;gap:4px;justify-content:flex-end;}", + ".vai-messages{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:10px;min-height:0;}", + ".vai-message{border-radius:8px;padding:8px 10px;line-height:1.5;word-break:break-word;max-width:100%;}", + ".vai-message.user{background:#313244;align-self:flex-end;border-bottom-right-radius:2px;max-width:88%;white-space:pre-wrap;}", + ".vai-message.assistant{background:#1e1e2e;border:1px solid #313244;align-self:flex-start;width:100%;border-bottom-left-radius:2px;}", + ".vai-message.sys-msg{background:transparent;border:none;color:#6c7086;font-size:11px;text-align:center;align-self:center;}", + ".vai-message.err-msg{background:#45102a;border:1px solid #f38ba8;color:#f38ba8;}", + ".vai-pre{background:#11111b;border-radius:6px;padding:8px 10px;overflow-x:auto;font-size:12px;line-height:1.4;margin:6px 0;}", + ".vai-pre code{color:#cdd6f4;display:block;white-space:pre;}", + ".vai-code-lang{font-size:10px;color:#89dceb;margin-bottom:4px;display:block;}", + ".vai-code-actions{display:flex;gap:4px;margin-top:6px;flex-wrap:wrap;}", + ".vai-btn{padding:4px 10px;border-radius:5px;border:none;cursor:pointer;font-size:11px;transition:opacity .15s;outline:none;}", + ".vai-btn:active{opacity:.7;}", + ".vai-btn-primary{background:#89b4fa;color:#1e1e2e;font-weight:600;}", + ".vai-btn-secondary{background:#313244;color:#cdd6f4;}", + ".vai-btn-success{background:#a6e3a1;color:#1e1e2e;font-weight:600;}", + ".vai-ctx-bar{display:flex;gap:4px;padding:4px 8px;background:var(--primary-color,#181825);border-top:1px solid #313244;flex-wrap:wrap;flex-shrink:0;}", + ".vai-ctx-btn{display:flex;align-items:center;gap:3px;font-size:10px;padding:3px 6px;border-radius:4px;cursor:pointer;user-select:none;border:1px solid #313244;background:transparent;color:#6c7086;}", + ".vai-ctx-btn.on{background:#313244;color:#89b4fa;border-color:#89b4fa;}", + ".vai-input-area{padding:8px;background:var(--primary-color,#181825);border-top:1px solid #313244;display:flex;flex-direction:column;gap:6px;flex-shrink:0;}", + ".vai-textarea{width:100%;min-height:64px;max-height:160px;resize:vertical;background:#11111b;border:1px solid #313244;border-radius:6px;color:var(--primary-text-color,#cdd6f4);font-size:13px;padding:8px;box-sizing:border-box;outline:none;line-height:1.4;}", + ".vai-textarea:focus{border-color:#89b4fa;}", + ".vai-input-row{display:flex;gap:6px;align-items:center;}", + ".vai-send-btn{padding:8px 16px;background:#89b4fa;color:#1e1e2e;border:none;border-radius:6px;font-weight:700;font-size:13px;cursor:pointer;flex-shrink:0;}", + ".vai-send-btn:disabled{opacity:.4;cursor:not-allowed;}", + ".vai-clear-btn{padding:8px 10px;background:#313244;color:#6c7086;border:none;border-radius:6px;font-size:12px;cursor:pointer;}", + ".vai-spinner{display:inline-block;width:14px;height:14px;border:2px solid #313244;border-top-color:#89b4fa;border-radius:50%;animation:vai-spin .8s linear infinite;}", + "@keyframes vai-spin{to{transform:rotate(360deg)}}", + ".vai-typing{display:flex;align-items:center;gap:6px;padding:8px 10px;border-radius:8px;background:#1e1e2e;border:1px solid #313244;align-self:flex-start;color:#6c7086;font-size:12px;}", + ".vai-md-p{margin:4px 0;}", + ".vai-md-h{font-weight:700;margin:8px 0 4px;color:#89b4fa;}", + ".vai-md-ul{margin:4px 0 4px 16px;}", + ".vai-md-li{margin:2px 0;}", + ".vai-ic{background:#11111b;border-radius:3px;padding:1px 4px;font-size:12px;color:#89dceb;}", + ".vai-bold{font-weight:700;}", + ].join(""); + + function injectStyles() { + if (document.getElementById("vai-styles")) return; + var style = document.createElement("style"); + style.id = "vai-styles"; + style.textContent = STYLES; + document.head.appendChild(style); + } + + /* ── Markdown renderer ────────────────────────────────────────────────── */ function renderMarkdown(md) { - const frag = document.createDocumentFragment(); - const lines = md.split("\n"); - let i = 0; + var frag = document.createDocumentFragment(); + var lines = String(md || "").split("\n"); + var i = 0; while (i < lines.length) { - const line = lines[i]; + var line = lines[i]; - /* fenced code block */ - if (line.startsWith("```")) { - const lang = line.slice(3).trim(); - const codeLines = []; + if (line.indexOf("```") === 0) { + var lang = line.slice(3).trim(); + var codeLines = []; i++; - while (i < lines.length && !lines[i].startsWith("```")) { + while (i < lines.length && lines[i].indexOf("```") !== 0) { codeLines.push(lines[i]); i++; } i++; - - const code = codeLines.join("\n").trimEnd(); - const pre = document.createElement("div"); - pre.className = "vai-pre"; - if (lang) { - const langBadge = document.createElement("span"); - langBadge.className = "vai-code-lang"; - langBadge.textContent = lang; - pre.appendChild(langBadge); - } - const codeEl = document.createElement("code"); - codeEl.textContent = code; - pre.appendChild(codeEl); - - /* action buttons per code block */ - const actions = document.createElement("div"); - actions.className = "vai-code-actions"; - - const btnInsert = document.createElement("button"); - btnInsert.className = "vai-btn vai-btn-primary"; - btnInsert.textContent = "Insert at cursor"; - btnInsert.onclick = () => { - if (!insertIntoEditor(code)) { - showToast("No active editor"); - } else { - showToast("Inserted ✓"); - } - }; - - const btnReplace = document.createElement("button"); - btnReplace.className = "vai-btn vai-btn-secondary"; - btnReplace.textContent = "Replace file"; - btnReplace.onclick = async () => { - const ok = replaceEditorContent(code); - showToast(ok ? "File replaced ✓" : "No active editor"); - }; - - const btnNew = document.createElement("button"); - btnNew.className = "vai-btn vai-btn-success"; - btnNew.textContent = "New file…"; - btnNew.onclick = async () => { - const name = await acode.prompt("Filename for new file", `untitled.${lang || "txt"}`, "text"); - if (!name) return; - await createNewFile(name, code); - showToast(`Created ${name} ✓`); - }; - - const btnCopy = document.createElement("button"); - btnCopy.className = "vai-btn vai-btn-secondary"; - btnCopy.textContent = "Copy"; - btnCopy.onclick = () => { - navigator.clipboard?.writeText(code).then(() => showToast("Copied ✓")).catch(() => {}); - }; - - actions.append(btnInsert, btnReplace, btnNew, btnCopy); - pre.appendChild(actions); - frag.appendChild(pre); + var code = codeLines.join("\n").replace(/\s+$/, ""); + frag.appendChild(makeCodeBlock(lang, code)); continue; } - /* heading */ - const hMatch = line.match(/^(#{1,6})\s+(.*)/); - if (hMatch) { - const h = document.createElement("div"); - h.className = "vai-md-h"; - h.style.fontSize = `${15 - hMatch[1].length}px`; - h.appendChild(renderInline(hMatch[2])); + var hm = line.match(/^(#{1,6})\s+(.*)/); + if (hm) { + var h = el("div", "vai-md-h"); + h.style.fontSize = (15 - hm[1].length) + "px"; + h.appendChild(renderInline(hm[2])); frag.appendChild(h); i++; continue; } - /* bullet list */ if (/^[-*]\s/.test(line)) { - const ul = document.createElement("ul"); - ul.className = "vai-md-ul"; + var ul = el("ul", "vai-md-ul"); while (i < lines.length && /^[-*]\s/.test(lines[i])) { - const li = document.createElement("li"); - li.className = "vai-md-li"; + var li = el("li", "vai-md-li"); li.appendChild(renderInline(lines[i].slice(2))); ul.appendChild(li); i++; @@ -770,15 +401,9 @@ continue; } - /* blank line */ - if (!line.trim()) { - i++; - continue; - } + if (!line.trim()) { i++; continue; } - /* paragraph */ - const p = document.createElement("p"); - p.className = "vai-md-p"; + var p = el("p", "vai-md-p"); p.appendChild(renderInline(line)); frag.appendChild(p); i++; @@ -787,572 +412,465 @@ return frag; } + function el(tag, cls) { + var e = document.createElement(tag); + if (cls) e.className = cls; + return e; + } + function renderInline(text) { - const span = document.createElement("span"); - const parts = text.split(/(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g); - for (const part of parts) { - if (part.startsWith("`") && part.endsWith("`")) { - const code = document.createElement("code"); - code.className = "vai-inline-code"; - code.textContent = part.slice(1, -1); - span.appendChild(code); - } else if (part.startsWith("**") && part.endsWith("**")) { - const b = document.createElement("span"); - b.className = "vai-bold"; - b.textContent = part.slice(2, -2); + var span = el("span"); + var parts = String(text).split(/(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*)/g); + for (var i = 0; i < parts.length; i++) { + var p = parts[i]; + if (p.charAt(0) === "`" && p.charAt(p.length - 1) === "`" && p.length > 2) { + var c = el("code", "vai-ic"); + c.textContent = p.slice(1, -1); + span.appendChild(c); + } else if (p.indexOf("**") === 0 && p.lastIndexOf("**") === p.length - 2 && p.length > 4) { + var b = el("span", "vai-bold"); + b.textContent = p.slice(2, -2); span.appendChild(b); - } else if (part.startsWith("*") && part.endsWith("*") && part.length > 2) { - const em = document.createElement("em"); - em.textContent = part.slice(1, -1); + } else if (p.charAt(0) === "*" && p.charAt(p.length - 1) === "*" && p.length > 2) { + var em = el("em"); + em.textContent = p.slice(1, -1); span.appendChild(em); } else { - span.appendChild(document.createTextNode(part)); + span.appendChild(document.createTextNode(p)); } } return span; } - /* ── Toast helper ─────────────────────────────────────────────────────── */ - function showToast(msg) { - try { - acode.require("toast")(msg, 2000); - } catch (_) {} + function makeCodeBlock(lang, code) { + var pre = el("div", "vai-pre"); + if (lang) { + var badge = el("span", "vai-code-lang"); + badge.textContent = lang; + pre.appendChild(badge); + } + var codeEl = el("code"); + codeEl.textContent = code; + pre.appendChild(codeEl); + + var actions = el("div", "vai-code-actions"); + + var btnI = mkBtn("Insert at cursor", "vai-btn vai-btn-primary", function () { + if (!insertIntoEditor(code)) showToast("No active editor"); + else showToast("Inserted ✓"); + }); + var btnR = mkBtn("Replace file", "vai-btn vai-btn-secondary", function () { + showToast(replaceEditorContent(code) ? "File replaced ✓" : "No active editor"); + }); + var btnN = mkBtn("New file…", "vai-btn vai-btn-success", function () { + var name = prompt("Filename", "untitled." + (lang || "txt")) || ""; + if (!name.trim()) return; + createNewFile(name.trim(), code).then(function () { showToast("Created " + name + " ✓"); }); + }); + var btnC = mkBtn("Copy", "vai-btn vai-btn-secondary", function () { + try { + navigator.clipboard.writeText(code).then(function () { showToast("Copied ✓"); }); + } catch (_) {} + }); + + actions.appendChild(btnI); + actions.appendChild(btnR); + actions.appendChild(btnN); + actions.appendChild(btnC); + pre.appendChild(actions); + return pre; } - /* ── Chat state ───────────────────────────────────────────────────────── */ - let conversationHistory = []; - let isThinking = false; - - /* ── UI construction ──────────────────────────────────────────────────── */ - let $messagesEl = null; - let $textareaEl = null; - let $sendBtn = null; - let $modelBadge = null; - let $ctxCurrentFile = null; - let $ctxSelection = null; - let $ctxOpenFiles = null; + function mkBtn(text, cls, onclick) { + var b = el("button", cls); + b.textContent = text; + b.onclick = onclick; + return b; + } + /* ── Chat state ───────────────────────────────────────────────────────── */ + var conversationHistory = []; + var isThinking = false; + var $messagesEl = null; + var $textareaEl = null; + var $sendBtn = null; + var $modelBadge = null; + var $ctxCurrentFile = null; + var $ctxSelection = null; + var $ctxOpenFiles = null; + var $promptPreview = null; + var $promptEditorWrap = null; + + /* ── Panel builder ────────────────────────────────────────────────────── */ function buildPanel(container) { - /* inject styles once */ - if (!document.getElementById("vai-styles")) { - const style = document.createElement("style"); - style.id = "vai-styles"; - style.textContent = STYLES; - document.head.appendChild(style); - } - - const panel = document.createElement("div"); - panel.className = "vai-panel"; + injectStyles(); - /* ── header ── */ - const header = document.createElement("div"); - header.className = "vai-header"; + var panel = el("div", "vai-panel"); - const headerIcon = document.createElement("span"); - headerIcon.className = "vai-header-icon"; + /* header */ + var header = el("div", "vai-header"); + var headerIcon = el("span", "vai-header-icon"); headerIcon.textContent = "✦"; + var titleEl = el("span", "vai-header-title"); + titleEl.textContent = "Venice AI Wizard"; - const title = document.createElement("span"); - title.className = "vai-header-title"; - title.textContent = "Venice AI Wizard"; - - $modelBadge = document.createElement("span"); - $modelBadge.className = "vai-model-badge"; + $modelBadge = el("span", "vai-model-badge"); $modelBadge.title = "Click to change model"; $modelBadge.textContent = getModelLabel(cfg.model); $modelBadge.onclick = openModelPicker; - const settingsBtn = document.createElement("span"); - settingsBtn.className = "icon settings"; - settingsBtn.title = "Plugin settings"; + var settingsBtn = el("span", "icon settings"); + settingsBtn.title = "Settings"; settingsBtn.style.cssText = "cursor:pointer;font-size:18px;color:#6c7086;"; settingsBtn.onclick = openSettings; - header.append(headerIcon, title, $modelBadge, settingsBtn); - - /* ── messages area ── */ - $messagesEl = document.createElement("div"); - $messagesEl.className = "vai-messages"; - - /* (prompt bar gets inserted after header is appended to panel) */ + header.appendChild(headerIcon); + header.appendChild(titleEl); + header.appendChild($modelBadge); + header.appendChild(settingsBtn); - addSystemMessage("Ask me to write, edit, read or create any code. I have full context of your open files."); - - /* ── context toggles ── */ - const ctxBar = document.createElement("div"); - ctxBar.className = "vai-context-bar"; - - $ctxCurrentFile = makeCtxToggle("📄 Current file", cfg.includeCurrentFile, (v) => { - cfg.includeCurrentFile = v; - saveSettings(); + /* system-prompt bar */ + var promptBar = el("div", "vai-prompt-bar"); + $promptPreview = el("span", "vai-prompt-preview"); + $promptPreview.title = "Click to edit system prompt"; + refreshPromptPreview(); + $promptPreview.onclick = togglePromptEditor; + var editPromptBtn = el("button", "vai-edit-prompt-btn"); + editPromptBtn.textContent = "Edit system prompt"; + editPromptBtn.onclick = togglePromptEditor; + promptBar.appendChild($promptPreview); + promptBar.appendChild(editPromptBtn); + + /* inline prompt editor (hidden) */ + $promptEditorWrap = el("div", "vai-prompt-editor"); + $promptEditorWrap.style.display = "none"; + var promptLabel = el("div", "vai-prompt-editor-label"); + promptLabel.textContent = "System Prompt"; + var promptTA = el("textarea", "vai-prompt-editor-textarea"); + promptTA.value = cfg.systemPrompt; + var promptRow = el("div", "vai-prompt-editor-row"); + var resetBtn = mkBtn("Reset to default", "vai-btn vai-btn-secondary", function () { + promptTA.value = DEFAULT_SYSTEM_PROMPT; }); - $ctxSelection = makeCtxToggle("✂️ Selection", cfg.includeSelection, (v) => { - cfg.includeSelection = v; - saveSettings(); + var cancelPBtn = mkBtn("Cancel", "vai-btn vai-btn-secondary", function () { + promptTA.value = cfg.systemPrompt; + $promptEditorWrap.style.display = "none"; }); - $ctxOpenFiles = makeCtxToggle("📂 All open files", cfg.includeOpenFiles, (v) => { - cfg.includeOpenFiles = v; + var savePBtn = mkBtn("Save", "vai-btn vai-btn-primary", function () { + cfg.systemPrompt = promptTA.value.trim() || DEFAULT_SYSTEM_PROMPT; saveSettings(); + refreshPromptPreview(); + $promptEditorWrap.style.display = "none"; + showToast("System prompt saved ✓"); }); - - ctxBar.append($ctxCurrentFile, $ctxSelection, $ctxOpenFiles); - - /* ── input area ── */ - const inputArea = document.createElement("div"); - inputArea.className = "vai-input-area"; - - $textareaEl = document.createElement("textarea"); - $textareaEl.className = "vai-textarea"; - $textareaEl.placeholder = "Ask Venice AI to write, edit or create code…"; + promptRow.appendChild(resetBtn); + promptRow.appendChild(cancelPBtn); + promptRow.appendChild(savePBtn); + $promptEditorWrap.appendChild(promptLabel); + $promptEditorWrap.appendChild(promptTA); + $promptEditorWrap.appendChild(promptRow); + + /* messages */ + $messagesEl = el("div", "vai-messages"); + + /* context bar */ + var ctxBar = el("div", "vai-ctx-bar"); + $ctxCurrentFile = makeCtxBtn("📄 Current file", cfg.includeCurrentFile, function (v) { + cfg.includeCurrentFile = v; saveSettings(); + }); + $ctxSelection = makeCtxBtn("✂️ Selection", cfg.includeSelection, function (v) { + cfg.includeSelection = v; saveSettings(); + }); + $ctxOpenFiles = makeCtxBtn("📂 All open", cfg.includeOpenFiles, function (v) { + cfg.includeOpenFiles = v; saveSettings(); + }); + ctxBar.appendChild($ctxCurrentFile); + ctxBar.appendChild($ctxSelection); + ctxBar.appendChild($ctxOpenFiles); + + /* input area */ + var inputArea = el("div", "vai-input-area"); + $textareaEl = el("textarea", "vai-textarea"); + $textareaEl.setAttribute("placeholder", "Ask Venice AI to write, edit or create code… (Enter to send, Shift+Enter for newline)"); $textareaEl.setAttribute("rows", "3"); - - $textareaEl.addEventListener("keydown", (e) => { + $textareaEl.addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); - const inputRow = document.createElement("div"); - inputRow.className = "vai-input-row"; - - $sendBtn = document.createElement("button"); - $sendBtn.className = "vai-send-btn"; + var inputRow = el("div", "vai-input-row"); + $sendBtn = el("button", "vai-send-btn"); $sendBtn.textContent = "Send"; $sendBtn.onclick = sendMessage; - - const clearBtn = document.createElement("button"); - clearBtn.className = "vai-clear-btn"; + var clearBtn = el("button", "vai-clear-btn"); clearBtn.textContent = "Clear"; clearBtn.title = "Clear conversation"; clearBtn.onclick = clearConversation; + inputRow.appendChild($sendBtn); + inputRow.appendChild(clearBtn); + inputArea.appendChild($textareaEl); + inputArea.appendChild(inputRow); + + /* assemble — strict appendChild order, no Element.after() */ + panel.appendChild(header); + panel.appendChild(promptBar); + panel.appendChild($promptEditorWrap); + panel.appendChild($messagesEl); + panel.appendChild(ctxBar); + panel.appendChild(inputArea); - inputRow.append($sendBtn, clearBtn); - inputArea.append($textareaEl, inputRow); - - panel.append(header, $messagesEl, ctxBar, inputArea); container.appendChild(panel); - /* insert prompt bar between header and messages (DOM order) */ - buildPromptBar(header); + addSysMsg("Ask me to write, edit, read or create any code. I have full context of your open files."); - return () => { - /* cleanup: remove styles when plugin unloads */ - document.getElementById("vai-styles")?.remove(); + return function cleanup() { + var s = document.getElementById("vai-styles"); + if (s) s.parentNode.removeChild(s); }; } - function makeCtxToggle(label, initialValue, onChange) { - const btn = document.createElement("button"); - btn.className = "vai-ctx-toggle" + (initialValue ? " active" : ""); - btn.textContent = label; - btn.onclick = () => { - const next = !btn.classList.contains("active"); - btn.classList.toggle("active", next); + function makeCtxBtn(label, active, onChange) { + var b = el("button", "vai-ctx-btn" + (active ? " on" : "")); + b.textContent = label; + b.onclick = function () { + var next = b.className.indexOf(" on") === -1; + b.className = "vai-ctx-btn" + (next ? " on" : ""); onChange(next); }; - return btn; + return b; } - function getModelLabel(modelId) { - const found = MODELS.find(([id]) => id === modelId); - return found ? found[1] : modelId; + function getModelLabel(id) { + for (var i = 0; i < MODELS.length; i++) { + if (MODELS[i][0] === id) return MODELS[i][1]; + } + return id; } - /* ── Message rendering ────────────────────────────────────────────────── */ - function addMessage(role, content, streaming = false) { - const div = document.createElement("div"); - div.className = `vai-message ${role}`; + /* ── System prompt UI ─────────────────────────────────────────────────── */ + function refreshPromptPreview() { + if (!$promptPreview) return; + var s = cfg.systemPrompt; + $promptPreview.textContent = "System: " + (s.length > 70 ? s.slice(0, 70) + "…" : s); + } - if (role === "assistant") { - if (streaming) { - div.dataset.streaming = "1"; - div.textContent = ""; - } else { - div.appendChild(renderMarkdown(content)); - addAutoFileCreation(div, content); - } - } else { - div.textContent = content; + function togglePromptEditor() { + if (!$promptEditorWrap) return; + var hidden = $promptEditorWrap.style.display === "none"; + $promptEditorWrap.style.display = hidden ? "flex" : "none"; + if (hidden) { + var ta = $promptEditorWrap.querySelector(".vai-prompt-editor-textarea"); + if (ta) ta.value = cfg.systemPrompt; } + } - $messagesEl.appendChild(div); - $messagesEl.scrollTop = $messagesEl.scrollHeight; - return div; + /* ── Messages ─────────────────────────────────────────────────────────── */ + function addSysMsg(text) { + var d = el("div", "vai-message sys-msg"); + d.textContent = text; + if ($messagesEl) $messagesEl.appendChild(d); } - function addSystemMessage(text) { - const div = document.createElement("div"); - div.className = "vai-message system-msg"; - div.textContent = text; - $messagesEl.appendChild(div); + function addErrMsg(text) { + var d = el("div", "vai-message err-msg"); + d.textContent = "⚠ " + text; + if ($messagesEl) { + $messagesEl.appendChild(d); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } } - function addErrorMessage(text) { - const div = document.createElement("div"); - div.className = "vai-message error-msg"; - div.textContent = `⚠ ${text}`; - $messagesEl.appendChild(div); - $messagesEl.scrollTop = $messagesEl.scrollHeight; + function addUserMsg(text) { + var d = el("div", "vai-message user"); + d.textContent = text; + if ($messagesEl) { + $messagesEl.appendChild(d); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } } - function addTypingIndicator() { - const div = document.createElement("div"); - div.className = "vai-typing-indicator"; - div.id = "vai-typing"; - const spinner = document.createElement("div"); - spinner.className = "vai-spinner"; - div.append(spinner, "Venice AI is thinking…"); - $messagesEl.appendChild(div); - $messagesEl.scrollTop = $messagesEl.scrollHeight; - return div; + function addAssistantMsg(text) { + var d = el("div", "vai-message assistant"); + d.appendChild(renderMarkdown(text)); + maybeAddFileCreation(d, text); + if ($messagesEl) { + $messagesEl.appendChild(d); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } + return d; } - function removeTypingIndicator() { - document.getElementById("vai-typing")?.remove(); + function addTypingEl() { + var d = el("div", "vai-typing"); + d.id = "vai-typing"; + var spinner = el("div", "vai-spinner"); + d.appendChild(spinner); + d.appendChild(document.createTextNode(" Venice AI is thinking…")); + if ($messagesEl) { + $messagesEl.appendChild(d); + $messagesEl.scrollTop = $messagesEl.scrollHeight; + } + return d; } - /* Look for "Create file: filename" patterns and offer one-click creation */ - function addAutoFileCreation(div, content) { - const filename = parseFilenameFromResponse(content); - const blocks = extractCodeBlocks(content); - if (!filename || !blocks.length) return; - - const banner = document.createElement("div"); - banner.style.cssText = - "margin-top:8px;padding:6px 8px;background:#1c2a1c;border:1px solid #a6e3a1;border-radius:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;"; - banner.innerHTML = `💾 Create ${filename}?`; - - const btn = document.createElement("button"); - btn.className = "vai-btn vai-btn-success"; - btn.textContent = "Create file"; - btn.onclick = async () => { - await createNewFile(filename, blocks[0].code); - showToast(`${filename} created ✓`); - banner.remove(); - }; - banner.appendChild(btn); - div.appendChild(banner); + function removeTypingEl() { + var el = document.getElementById("vai-typing"); + if (el && el.parentNode) el.parentNode.removeChild(el); + } + + function maybeAddFileCreation(div, content) { + try { + var fn = parseFilenameFromResponse(content); + var blocks = extractCodeBlocks(content); + if (!fn || !blocks.length) return; + var banner = el("div"); + banner.style.cssText = "margin-top:8px;padding:6px 8px;background:#1c2a1c;border:1px solid #a6e3a1;border-radius:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;"; + var lbl = el("span"); + lbl.style.cssText = "color:#a6e3a1;font-size:11px;"; + lbl.textContent = "💾 Create " + fn + "?"; + var btn = mkBtn("Create file", "vai-btn vai-btn-success", function () { + createNewFile(fn, blocks[0].code).then(function () { showToast(fn + " created ✓"); }); + if (banner.parentNode) banner.parentNode.removeChild(banner); + }); + banner.appendChild(lbl); + banner.appendChild(btn); + div.appendChild(banner); + } catch (_) {} } - /* ── Send message flow ────────────────────────────────────────────────── */ + function clearConversation() { + conversationHistory = []; + if ($messagesEl) $messagesEl.innerHTML = ""; + addSysMsg("Conversation cleared. Ask me anything about your code."); + } + + /* ── Send flow ────────────────────────────────────────────────────────── */ async function sendMessage() { - if (isThinking) return; - const prompt = $textareaEl.value.trim(); - if (!prompt) return; + if (isThinking || !$textareaEl) return; + var userText = $textareaEl.value.trim(); + if (!userText) return; if (!cfg.apiKey) { - addErrorMessage("Please configure your Venice AI API key in settings first."); + addErrMsg("Please configure your Venice AI API key in settings first."); openSettings(); return; } $textareaEl.value = ""; isThinking = true; - $sendBtn.disabled = true; + if ($sendBtn) $sendBtn.disabled = true; - addMessage("user", prompt); - conversationHistory.push({ role: "user", content: prompt }); + addUserMsg(userText); + conversationHistory.push({ role: "user", content: userText }); - const typingEl = addTypingIndicator(); + addTypingEl(); try { - /* Build full messages with context prepended to the FIRST user turn only */ - const contextedHistory = [ - { role: "system", content: cfg.systemPrompt }, - ...buildContextualHistory(), - ]; - - let assistantContent = ""; - let streamingDiv = null; - let rawBuffer = ""; + var messages = [{ role: "system", content: cfg.systemPrompt }].concat(buildContextualHistory()); + var assistantContent = ""; + var streamDiv = null; + var rawBuf = ""; try { - /* attempt streaming */ - removeTypingIndicator(); - streamingDiv = document.createElement("div"); - streamingDiv.className = "vai-message assistant"; - $messagesEl.appendChild(streamingDiv); - - await callVenice(contextedHistory, (delta, full) => { - rawBuffer = full; - streamingDiv.textContent = full; - $messagesEl.scrollTop = $messagesEl.scrollHeight; + removeTypingEl(); + streamDiv = el("div", "vai-message assistant"); + if ($messagesEl) $messagesEl.appendChild(streamDiv); + + await callVenice(messages, function (delta, full) { + rawBuf = full; + if (streamDiv) streamDiv.textContent = full; + if ($messagesEl) $messagesEl.scrollTop = $messagesEl.scrollHeight; }); - assistantContent = rawBuffer; - - /* re-render with markdown after streaming finishes */ - streamingDiv.textContent = ""; - streamingDiv.appendChild(renderMarkdown(assistantContent)); - addAutoFileCreation(streamingDiv, assistantContent); + assistantContent = rawBuf; + if (streamDiv) { + streamDiv.textContent = ""; + streamDiv.appendChild(renderMarkdown(assistantContent)); + maybeAddFileCreation(streamDiv, assistantContent); + } } catch (streamErr) { - /* streaming failed (browser may not support it) — fall back to non-streaming */ - streamingDiv?.remove(); - removeTypingIndicator(); - - assistantContent = await callVenice(contextedHistory); - addMessage("assistant", assistantContent); + if (streamDiv && streamDiv.parentNode) streamDiv.parentNode.removeChild(streamDiv); + removeTypingEl(); + assistantContent = await callVenice(messages); + addAssistantMsg(assistantContent); } conversationHistory.push({ role: "assistant", content: assistantContent }); - $messagesEl.scrollTop = $messagesEl.scrollHeight; + if ($messagesEl) $messagesEl.scrollTop = $messagesEl.scrollHeight; } catch (err) { - removeTypingIndicator(); - addErrorMessage(err.message || String(err)); + removeTypingEl(); + addErrMsg(err && err.message ? err.message : String(err)); } finally { isThinking = false; - $sendBtn.disabled = false; - $textareaEl.focus(); - } - } - - function buildContextualHistory() { - /* inject file context into the last user message only */ - if (!conversationHistory.length) return []; - const history = [...conversationHistory]; - /* find the last user message and prepend context to it */ - for (let i = history.length - 1; i >= 0; i--) { - if (history[i].role === "user") { - const original = history[i].content; - const contextParts = []; - - if (cfg.includeCurrentFile) { - const ctx = getActiveFileContext(); - if (ctx && ctx.content) { - contextParts.push( - `## Current file: ${ctx.filename}\n\`\`\`${ctx.language}\n${ctx.content}\n\`\`\``, - ); - } - } - - if (cfg.includeSelection) { - const sel = getSelection(); - if (sel) { - contextParts.push(`## Selected text\n\`\`\`\n${sel}\n\`\`\``); - } - } - - if (cfg.includeOpenFiles) { - const others = getOpenFilesContext(); - if (others.length) { - const txt = others - .map( - (f) => `### ${f.filename}\n\`\`\`${f.language}\n${f.content}\n\`\`\``, - ) - .join("\n\n"); - contextParts.push(`## Other open files\n${txt}`); - } - } - - history[i] = { - role: "user", - content: contextParts.length - ? contextParts.join("\n\n") + "\n\n---\n\n" + original - : original, - }; - break; - } - } - return history; - } - - function clearConversation() { - conversationHistory = []; - $messagesEl.innerHTML = ""; - addSystemMessage("Conversation cleared. Ask me anything about your code."); - } - - /* ── Inline system-prompt editor ─────────────────────────────────────── */ - let $promptBar = null; - let $promptPreview = null; - let $promptEditorWrap = null; - - function buildPromptBar(insertAfter) { - $promptBar = document.createElement("div"); - $promptBar.className = "vai-prompt-bar"; - - $promptPreview = document.createElement("span"); - $promptPreview.className = "vai-prompt-preview"; - $promptPreview.title = "Click to edit system prompt"; - refreshPromptPreview(); - $promptPreview.onclick = togglePromptEditor; - - const editBtn = document.createElement("button"); - editBtn.className = "vai-edit-prompt-btn"; - editBtn.textContent = "Edit system prompt"; - editBtn.onclick = togglePromptEditor; - - $promptBar.append($promptPreview, editBtn); - insertAfter.after($promptBar); - - /* build the inline editor (hidden initially) */ - $promptEditorWrap = document.createElement("div"); - $promptEditorWrap.className = "vai-prompt-editor"; - $promptEditorWrap.style.display = "none"; - - const label = document.createElement("div"); - label.className = "vai-prompt-editor-label"; - label.textContent = "System Prompt"; - - const textarea = document.createElement("textarea"); - textarea.className = "vai-prompt-editor-textarea"; - textarea.value = cfg.systemPrompt; - - const row = document.createElement("div"); - row.className = "vai-prompt-editor-row"; - - const resetBtn = document.createElement("button"); - resetBtn.className = "vai-btn vai-btn-secondary"; - resetBtn.textContent = "Reset to default"; - resetBtn.onclick = () => { - textarea.value = DEFAULT_SYSTEM_PROMPT; - }; - - const saveBtn = document.createElement("button"); - saveBtn.className = "vai-btn vai-btn-primary"; - saveBtn.textContent = "Save"; - saveBtn.onclick = () => { - cfg.systemPrompt = textarea.value.trim() || DEFAULT_SYSTEM_PROMPT; - saveSettings(); - refreshPromptPreview(); - togglePromptEditor(); - showToast("System prompt saved ✓"); - }; - - const cancelBtn = document.createElement("button"); - cancelBtn.className = "vai-btn vai-btn-secondary"; - cancelBtn.textContent = "Cancel"; - cancelBtn.onclick = () => { - textarea.value = cfg.systemPrompt; - togglePromptEditor(); - }; - - row.append(resetBtn, cancelBtn, saveBtn); - $promptEditorWrap.append(label, textarea, row); - $promptBar.after($promptEditorWrap); - - return textarea; - } - - function refreshPromptPreview() { - if ($promptPreview) { - $promptPreview.textContent = "System: " + cfg.systemPrompt.slice(0, 70) + (cfg.systemPrompt.length > 70 ? "…" : ""); + if ($sendBtn) $sendBtn.disabled = false; + if ($textareaEl) $textareaEl.focus(); } } - function togglePromptEditor() { - if (!$promptEditorWrap) return; - const hidden = $promptEditorWrap.style.display === "none"; - $promptEditorWrap.style.display = hidden ? "flex" : "none"; - if (hidden) { - /* sync textarea when opening */ - const ta = $promptEditorWrap.querySelector(".vai-prompt-editor-textarea"); - if (ta) ta.value = cfg.systemPrompt; - } - } - - /* ── Settings ─────────────────────────────────────────────────────────── */ + /* ── Settings dialogs ─────────────────────────────────────────────────── */ async function openSettings() { try { - const settingsPage = acode.require("settings"); - if (settingsPage?.uiSettings?.[`plugin-${PLUGIN_ID}`]) { - settingsPage.uiSettings[`plugin-${PLUGIN_ID}`].show(); + var appSettings = acode.require("settings"); + if (appSettings && appSettings.uiSettings && appSettings.uiSettings["plugin-" + PLUGIN_ID]) { + appSettings.uiSettings["plugin-" + PLUGIN_ID].show(); return; } } catch (_) {} - - /* fallback: custom settings dialog */ - showCustomSettingsDialog(); - } - - async function showCustomSettingsDialog() { - const values = await acode.multiPrompt("Venice AI Wizard — Settings", [ - { - type: "text", - id: "apiKey", - placeholder: "Venice AI API Key", - value: cfg.apiKey, - required: false, - }, - { - type: "text", - id: "systemPrompt", - placeholder: "System Prompt", - value: cfg.systemPrompt, - required: false, - }, - { - type: "text", - id: "temperature", - placeholder: "Temperature (0.0 – 1.0)", - value: String(cfg.temperature), - required: false, - }, - { - type: "text", - id: "maxTokens", - placeholder: "Max Tokens", - value: String(cfg.maxTokens), - required: false, - }, - ]); - - if (!values) return; - - if (values.apiKey !== undefined) cfg.apiKey = values.apiKey.trim(); - if (values.systemPrompt !== undefined) cfg.systemPrompt = values.systemPrompt || DEFAULT_SYSTEM_PROMPT; - if (values.temperature !== undefined) cfg.temperature = values.temperature || "0.7"; - if (values.maxTokens !== undefined) cfg.maxTokens = values.maxTokens || "4096"; - - saveSettings(); - showToast("Settings saved ✓"); + /* fallback */ + try { + var vals = await acode.multiPrompt("Venice AI — Settings", [ + { type: "text", id: "apiKey", placeholder: "API Key", value: cfg.apiKey }, + { type: "text", id: "temperature", placeholder: "Temperature (0-1)", value: cfg.temperature }, + { type: "text", id: "maxTokens", placeholder: "Max Tokens", value: cfg.maxTokens }, + ]); + if (!vals) return; + if (vals.apiKey !== undefined) cfg.apiKey = vals.apiKey.trim(); + if (vals.temperature !== undefined) cfg.temperature = vals.temperature; + if (vals.maxTokens !== undefined) cfg.maxTokens = vals.maxTokens; + saveSettings(); + showToast("Settings saved ✓"); + } catch (_) {} } async function openModelPicker() { try { - const select = acode.require("select"); - const chosen = await select("Select Venice AI Model", MODELS, { - default: cfg.model, - }); + var select = acode.require("select"); + var chosen = await select("Select Venice AI Model", MODELS, { default: cfg.model }); if (chosen) { cfg.model = chosen; - $modelBadge.textContent = getModelLabel(chosen); + if ($modelBadge) $modelBadge.textContent = getModelLabel(chosen); saveSettings(); - showToast(`Model: ${getModelLabel(chosen)}`); + showToast("Model: " + getModelLabel(chosen)); } } catch (e) { console.error("[Venice AI] model picker error:", e); } } - /* ── Acode settings integration ───────────────────────────────────────── */ - const settingsList = [ + /* ── Acode settings list ──────────────────────────────────────────────── */ + var settingsList = [ { key: "apiKey", text: "Venice AI API Key", - icon: "vpn_key", - info: "Your Venice AI API key from venice.ai", + info: "Your API key from venice.ai", value: cfg.apiKey, prompt: "Enter Venice AI API key", - promptType: "password", + promptType: "text", }, { key: "model", text: "AI Model", - icon: "smart_toy", info: "Which Venice AI model to use", value: cfg.model, select: MODELS, - valueText(v) { - return getModelLabel(v); - }, + valueText: function (v) { return getModelLabel(v); }, }, { key: "temperature", text: "Temperature", - icon: "thermostat", - info: "Creativity level (0.0 = deterministic, 1.0 = creative)", + info: "Creativity: 0.0 = precise, 1.0 = creative", value: cfg.temperature, prompt: "Temperature (0.0 – 1.0)", promptType: "number", @@ -1360,8 +878,7 @@ { key: "maxTokens", text: "Max Tokens", - icon: "token", - info: "Maximum tokens in each response", + info: "Maximum response length", value: cfg.maxTokens, prompt: "Max tokens", promptType: "number", @@ -1369,80 +886,92 @@ { key: "systemPrompt", text: "System Prompt", - icon: "edit_note", - info: "Custom instructions for the AI assistant", - value: cfg.systemPrompt, - prompt: "System prompt", - promptType: "textarea", + info: "Edit directly in the sidebar panel", + value: "tap panel to edit", + chevron: true, }, { key: "includeCurrentFile", text: "Include Current File", - icon: "description", - info: "Send the active file content to the AI", + info: "Send the active file as context", checkbox: cfg.includeCurrentFile, value: cfg.includeCurrentFile, }, { key: "includeSelection", text: "Include Selection", - icon: "select_all", - info: "Send selected text as context", + info: "Send highlighted text as context", checkbox: cfg.includeSelection, value: cfg.includeSelection, }, { key: "includeOpenFiles", text: "Include All Open Files", - icon: "folder_open", - info: "Include up to 4 other open files as context", + info: "Include up to 4 other open tabs", checkbox: cfg.includeOpenFiles, value: cfg.includeOpenFiles, }, ]; function onSettingChange(key, value) { + if (key === "systemPrompt") { + togglePromptEditor(); + return; + } cfg[key] = value; saveSettings(); - - if (key === "model" && $modelBadge) { - $modelBadge.textContent = getModelLabel(value); - } + if (key === "model" && $modelBadge) $modelBadge.textContent = getModelLabel(value); if (key === "includeCurrentFile" && $ctxCurrentFile) { - $ctxCurrentFile.classList.toggle("active", !!value); + $ctxCurrentFile.className = "vai-ctx-btn" + (value ? " on" : ""); } if (key === "includeSelection" && $ctxSelection) { - $ctxSelection.classList.toggle("active", !!value); + $ctxSelection.className = "vai-ctx-btn" + (value ? " on" : ""); } if (key === "includeOpenFiles" && $ctxOpenFiles) { - $ctxOpenFiles.classList.toggle("active", !!value); + $ctxOpenFiles.className = "vai-ctx-btn" + (value ? " on" : ""); } } - /* ── Plugin lifecycle ─────────────────────────────────────────────────── */ + /* ── Lifecycle ────────────────────────────────────────────────────────── */ function init(baseUrl, $page, options) { - const sidebarApps = acode.require("sidebarApps"); - - sidebarApps.add( - "auto_fix_high", /* Acode built-in icon class */ - PLUGIN_ID, - "Venice AI Wizard", - (container) => buildPanel(container), - ); + try { + var sidebarApps = acode.require("sidebarApps"); + if (!sidebarApps) { + console.error("[Venice AI] sidebarApps module not available"); + return; + } + sidebarApps.add( + "smart_toy", + PLUGIN_ID, + "Venice AI Wizard", + buildPanel + ); + } catch (e) { + console.error("[Venice AI] init error:", e); + } } function destroy() { - const sidebarApps = acode.require("sidebarApps"); - sidebarApps.remove(PLUGIN_ID); - document.getElementById("vai-styles")?.remove(); + try { + var sidebarApps = acode.require("sidebarApps"); + if (sidebarApps) sidebarApps.remove(PLUGIN_ID); + } catch (_) {} + try { + var s = document.getElementById("vai-styles"); + if (s && s.parentNode) s.parentNode.removeChild(s); + } catch (_) {} } /* register */ - if (typeof acode !== "undefined") { - acode.setPluginInit(PLUGIN_ID, init, { - list: settingsList, - cb: onSettingChange, - }); - acode.setPluginUnmount(PLUGIN_ID, destroy); + try { + if (typeof acode !== "undefined") { + acode.setPluginInit(PLUGIN_ID, init, { + list: settingsList, + cb: onSettingChange, + }); + acode.setPluginUnmount(PLUGIN_ID, destroy); + } + } catch (e) { + console.error("[Venice AI] registration error:", e); } })(); diff --git a/acode-plugins/venice-ai-wizard/plugin.json b/acode-plugins/venice-ai-wizard/plugin.json index 30b8fd41e..318ecb2c7 100644 --- a/acode-plugins/venice-ai-wizard/plugin.json +++ b/acode-plugins/venice-ai-wizard/plugin.json @@ -1,7 +1,7 @@ { "id": "venice-ai-wizard", "name": "Venice AI Coding Wizard", - "version": "1.0.0", + "version": "1.1.0", "main": "main.js", "icon": "icon.png", "readme": "readme.md",