aipackage/cezen-portal/prompt-studio.html
2026-06-30 10:51:41 +05:30

714 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prompt Studio — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
.ps-layout {
display: grid;
grid-template-columns: 300px 1fr 340px;
height: calc(100vh - 64px);
overflow: hidden;
}
@media(max-width:1100px){ .ps-layout { grid-template-columns: 260px 1fr; } .ps-history-panel { display:none; } }
@media(max-width:720px){ .ps-layout { grid-template-columns: 1fr; } .ps-editor-panel { display:none; } }
/* ── Left: Editor Panel ── */
.ps-editor-panel {
border-right: 1px solid var(--bdr);
background: var(--navy2);
display: flex; flex-direction: column;
overflow: hidden;
}
.ps-panel-header {
padding: 14px 16px 12px;
border-bottom: 1px solid var(--bdr);
display: flex; align-items: center; justify-content: space-between;
}
.ps-panel-title { font-size: 12px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .5px; }
.ps-panel-body { flex: 1; overflow-y: auto; padding: 14px 14px 0; display: flex; flex-direction: column; gap: 14px; }
.ps-panel-footer { padding: 12px 14px; border-top: 1px solid var(--bdr); }
.ps-field label {
display: block; font-size: 11px; font-weight: 700; color: var(--lt);
text-transform: uppercase; letter-spacing: .4px; margin-bottom: 6px;
}
.ps-field textarea, .ps-field select, .ps-field input[type=range], .ps-field input[type=number] {
width: 100%; box-sizing: border-box; font-family: inherit;
border: 1.5px solid var(--bdr); border-radius: 9px;
background: var(--bg); color: var(--ink); font-size: 13px;
transition: border-color .15s;
}
.ps-field textarea { padding: 10px 12px; resize: vertical; min-height: 90px; line-height: 1.55; }
.ps-field textarea:focus, .ps-field select:focus { outline: none; border-color: var(--purple); }
.ps-field select { padding: 8px 10px; cursor: pointer; }
.ps-params-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ps-param { }
.ps-param label { display: block; font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 5px; }
.ps-param-row { display: flex; align-items: center; gap: 8px; }
.ps-param-row input[type=range] { flex: 1; accent-color: var(--purple); }
.ps-param-val { font-size: 12px; font-weight: 700; color: var(--purple); min-width: 30px; text-align: right; }
.ps-run-btn {
width: 100%; padding: 11px; border-radius: 10px;
background: var(--purple); color: white; border: none;
font-family: inherit; font-size: 14px; font-weight: 700;
cursor: pointer; transition: .15s; display: flex; align-items: center; justify-content: center; gap: 8px;
}
.ps-run-btn:hover { background: #6d28d9; }
.ps-run-btn:disabled { opacity: .5; cursor: not-allowed; }
.ps-run-btn .ps-spinner { display: none; width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.3); border-top-color: white; border-radius: 50%; animation: spin .7s linear infinite; }
.ps-run-btn.running .ps-spinner { display: block; }
.ps-run-btn.running .ps-run-icon { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
.ps-save-btn {
width: 100%; padding: 8px; border-radius: 9px; margin-top: 8px;
background: none; border: 1.5px solid var(--bdr); color: var(--ink);
font-family: inherit; font-size: 13px; font-weight: 600;
cursor: pointer; transition: .15s;
}
.ps-save-btn:hover { border-color: var(--purple); color: var(--purple); }
/* ── Middle: Output Panel ── */
.ps-output-panel {
display: flex; flex-direction: column;
overflow: hidden; background: var(--bg);
}
.ps-output-header {
padding: 12px 18px;
border-bottom: 1px solid var(--bdr);
display: flex; align-items: center; gap: 12px;
background: var(--navy2);
}
.ps-output-title { font-size: 13px; font-weight: 700; color: var(--ink); flex: 1; }
.ps-stat-chips { display: flex; gap: 8px; }
.ps-stat-chip {
padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 700;
background: rgba(124,58,237,.1); color: var(--purple);
}
.ps-stat-chip.green { background: rgba(16,185,129,.1); color: #059669; }
.ps-output-actions { display: flex; gap: 6px; }
.ps-icon-btn {
background: none; border: 1.5px solid var(--bdr); border-radius: 8px;
padding: 5px 10px; cursor: pointer; font-size: 12px; color: var(--med);
transition: .15s; font-family: inherit;
}
.ps-icon-btn:hover { border-color: var(--purple); color: var(--purple); }
.ps-output-body { flex: 1; overflow-y: auto; padding: 24px; }
.ps-output-empty {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 100%; gap: 12px; color: var(--lt);
}
.ps-output-empty .ps-empty-icon { font-size: 40px; opacity: .4; }
.ps-output-empty p { font-size: 14px; }
.ps-response-block {
background: var(--navy2); border: 1.5px solid var(--bdr);
border-radius: 14px; padding: 20px 22px;
font-size: 14px; line-height: 1.7; color: var(--ink);
}
.ps-response-block h1,.ps-response-block h2,.ps-response-block h3 { margin:.8em 0 .4em; color:var(--ink); }
.ps-response-block h1 { font-size:1.3em; } .ps-response-block h2 { font-size:1.15em; } .ps-response-block h3 { font-size:1.05em; }
.ps-response-block p { margin:.5em 0; }
.ps-response-block ul,.ps-response-block ol { padding-left:1.5em; margin:.4em 0; }
.ps-response-block li { margin:.3em 0; }
.ps-response-block code { background:rgba(124,58,237,.1); color:var(--purple); padding:2px 6px; border-radius:5px; font-family:monospace; font-size:.9em; }
.ps-response-block pre { background:#160D35; color:#e2d9f3; border-radius:10px; padding:14px 16px; overflow-x:auto; margin:.8em 0; }
.ps-response-block pre code { background:none; color:inherit; padding:0; }
.ps-response-block blockquote { border-left:3px solid var(--purple); padding-left:14px; margin:.6em 0; color:var(--med); font-style:italic; }
.ps-response-block table { border-collapse:collapse; width:100%; margin:.6em 0; font-size:.9em; }
.ps-response-block th { background:rgba(124,58,237,.08); padding:8px 12px; text-align:left; font-weight:700; border-bottom:2px solid var(--bdr); }
.ps-response-block td { padding:7px 12px; border-bottom:1px solid var(--bdr); }
.ps-response-block strong { font-weight:700; }
.ps-response-block em { font-style:italic; }
.ps-streaming-cursor { display:inline-block; width:2px; height:1em; background:var(--purple); margin-left:2px; animation:blink .8s step-end infinite; vertical-align:text-bottom; }
@keyframes blink { 50%{ opacity:0; } }
/* ── Right: History Panel ── */
.ps-history-panel {
border-left: 1px solid var(--bdr);
background: var(--navy2);
display: flex; flex-direction: column;
overflow: hidden;
}
.ps-history-list { flex: 1; overflow-y: auto; padding: 8px; }
.ps-history-item {
padding: 10px 12px; border-radius: 10px; cursor: pointer;
transition: .1s; border: 1.5px solid transparent; margin-bottom: 4px;
}
.ps-history-item:hover { background: rgba(124,58,237,.06); border-color: var(--bdr); }
.ps-history-item.active { border-color: var(--purple); background: rgba(124,58,237,.08); }
.ps-history-item-header { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.ps-history-model { font-size: 11px; font-weight: 700; color: var(--purple); background: rgba(124,58,237,.1); padding: 2px 8px; border-radius: 20px; }
.ps-history-time { font-size: 11px; color: var(--lt); margin-left: auto; }
.ps-history-prompt { font-size: 12px; color: var(--med); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-history-stats { display: flex; gap: 10px; margin-top: 5px; }
.ps-history-stat { font-size: 11px; color: var(--lt); }
.ps-history-empty { padding: 24px 16px; text-align: center; color: var(--lt); font-size: 13px; }
/* Toast */
#ps-toast {
position: fixed; bottom: 24px; right: 24px; z-index: 9999;
background: var(--ink); color: white; padding: 10px 18px;
border-radius: 10px; font-size: 13px; font-weight: 600;
opacity: 0; transform: translateY(8px); transition: .25s; pointer-events: none;
}
#ps-toast.show { opacity: 1; transform: translateY(0); }
/* Diff highlight */
.ps-diff-added { background: rgba(16,185,129,.15); border-radius: 3px; }
.ps-diff-removed { background: rgba(239,68,68,.12); text-decoration: line-through; border-radius: 3px; }
</style>
</head>
<body>
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Audit Log</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Manager</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">Multimodal Chat</a>
<a href="prompt-studio.html" class="active">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="ps-layout">
<!-- ── Left: Editor ── -->
<div class="ps-editor-panel">
<div class="ps-panel-header">
<span class="ps-panel-title">⚗️ Prompt Studio</span>
<button class="ps-icon-btn" onclick="clearAll()" title="Clear all">✕ Clear</button>
</div>
<div class="ps-panel-body" id="ps-editor-body">
<div class="ps-field">
<label>Model</label>
<select id="ps-model">
<option value="llama3.1:8b">llama3.1:8b — General Purpose</option>
<option value="llama3.1:70b">llama3.1:70b — Complex Reasoning</option>
<option value="mistral:7b">mistral:7b — Structured Output</option>
<option value="codellama:34b">codellama:34b — Code</option>
<option value="llava:13b">llava:13b — Vision</option>
<option value="llama3.2:3b">llama3.2:3b — Fast / Lightweight</option>
<option value="gemma2:9b">gemma2:9b — Instruction Following</option>
</select>
</div>
<div class="ps-field">
<label>System Prompt <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--lt)">(sets AI role &amp; behaviour)</span></label>
<textarea id="ps-system" rows="6" placeholder="You are a helpful AI assistant for [Organisation]. Answer questions clearly and concisely. Always cite your sources when referring to documents."></textarea>
</div>
<div class="ps-field">
<label>User Message</label>
<textarea id="ps-user" rows="5" placeholder="Type your test message here…" onkeydown="handleKey(event)"></textarea>
</div>
<div class="ps-params-grid">
<div class="ps-param">
<label>Temperature</label>
<div class="ps-param-row">
<input type="range" id="ps-temp" min="0" max="1" step="0.05" value="0.7" oninput="document.getElementById('ps-temp-val').textContent=this.value">
<span class="ps-param-val" id="ps-temp-val">0.7</span>
</div>
</div>
<div class="ps-param">
<label>Max Tokens</label>
<div class="ps-param-row">
<input type="range" id="ps-maxtok" min="128" max="4096" step="128" value="1024" oninput="document.getElementById('ps-maxtok-val').textContent=this.value">
<span class="ps-param-val" id="ps-maxtok-val">1024</span>
</div>
</div>
</div>
<!-- Quick templates -->
<div class="ps-field">
<label>Quick Templates</label>
<div style="display:flex;flex-wrap:wrap;gap:6px">
<button class="ps-icon-btn" onclick="loadTemplate('qa')">Q&A Bot</button>
<button class="ps-icon-btn" onclick="loadTemplate('summariser')">Summariser</button>
<button class="ps-icon-btn" onclick="loadTemplate('extractor')">Extractor</button>
<button class="ps-icon-btn" onclick="loadTemplate('classifier')">Classifier</button>
<button class="ps-icon-btn" onclick="loadTemplate('coder')">Code Reviewer</button>
<button class="ps-icon-btn" onclick="loadTemplate('drafter')">Email Drafter</button>
</div>
</div>
</div>
<div class="ps-panel-footer">
<button class="ps-run-btn" id="ps-run-btn" onclick="runPrompt()">
<span class="ps-run-icon">▶ Run</span>
<div class="ps-spinner"></div>
</button>
<button class="ps-save-btn" onclick="saveToLibrary()">📚 Save to Prompt Library</button>
</div>
</div>
<!-- ── Middle: Output ── -->
<div class="ps-output-panel">
<div class="ps-output-header">
<span class="ps-output-title" id="ps-output-label">Output</span>
<div class="ps-stat-chips" id="ps-stat-chips" style="display:none">
<span class="ps-stat-chip green" id="ps-stat-time"></span>
<span class="ps-stat-chip" id="ps-stat-tokens"></span>
</div>
<div class="ps-output-actions">
<button class="ps-icon-btn" onclick="copyOutput()" title="Copy response">📋 Copy</button>
<button class="ps-icon-btn" onclick="sendToEval()" title="Send to Eval Suite">🧪 Eval</button>
<button class="ps-icon-btn" onclick="openCompare()" title="Compare with another model">⚖️ Compare</button>
</div>
</div>
<div class="ps-output-body" id="ps-output-body">
<div class="ps-output-empty" id="ps-empty-state">
<div class="ps-empty-icon">⚗️</div>
<p>Write a system prompt and message, then hit <strong>Run</strong>.</p>
<p style="font-size:12px;color:var(--lt)">Results appear here with token count and latency.</p>
</div>
</div>
</div>
<!-- ── Right: History ── -->
<div class="ps-history-panel">
<div class="ps-panel-header">
<span class="ps-panel-title">Run History</span>
<button class="ps-icon-btn" onclick="clearHistory()" style="font-size:11px">Clear</button>
</div>
<div class="ps-history-list" id="ps-history-list">
<div class="ps-history-empty" id="ps-history-empty">No runs yet. Results will appear here after you click Run.</div>
</div>
</div>
</div>
<div id="ps-toast"></div>
<script>
// ── State ───────────────────────────────────────────────────────────────────
let runHistory = JSON.parse(localStorage.getItem('cezen_ps_history') || '[]');
let currentRunId = null;
// ── Templates ────────────────────────────────────────────────────────────────
const TEMPLATES = {
qa: {
system: `You are a knowledgeable assistant for [Organisation]. Answer questions clearly and accurately based on the information provided. If you are unsure, say so rather than guessing. Keep answers concise — use bullet points for lists.`,
user: `What are the key steps in our procurement approval process?`,
model: 'llama3.1:8b', temp: 0.3
},
summariser: {
system: `You are a document summarisation assistant. When given text, produce a structured summary with: 1) A one-sentence overview, 2) Key points (max 5 bullets), 3) Any action items or deadlines mentioned. Be concise.`,
user: `Summarise the following document:\n\n[Paste document text here]`,
model: 'llama3.1:8b', temp: 0.3
},
extractor: {
system: `You are a data extraction assistant. Extract structured information from documents and return it as a clean JSON object. Only include fields explicitly present in the document. If a field is not found, omit it.`,
user: `Extract the following fields from this contract: vendor_name, contract_value, start_date, end_date, payment_terms, penalty_clauses.\n\nDocument:\n[Paste contract text here]`,
model: 'mistral:7b', temp: 0.1
},
classifier: {
system: `You are a document classification assistant. Classify the input into exactly one of these categories: POLICY, CONTRACT, INVOICE, REPORT, CORRESPONDENCE, TENDER, OTHER. Respond with only the category name and a one-sentence justification. Format: CATEGORY: [name]\nReason: [one sentence]`,
user: `Classify this document:\n\n[Paste document text here]`,
model: 'mistral:7b', temp: 0.1
},
coder: {
system: `You are a senior code reviewer. Review the provided code for: bugs, security issues, performance problems, and style violations. Structure your review as: SUMMARY, CRITICAL ISSUES, WARNINGS, SUGGESTIONS. Be specific — include line references where possible.`,
user: `Please review this code:\n\n\`\`\`python\n# Paste your code here\n\`\`\``,
model: 'codellama:34b', temp: 0.2
},
drafter: {
system: `You are a professional email drafting assistant for a government or PSU organisation. Write formal, clear, concise emails. Use polite but direct language. Always include: subject line, greeting, body, and sign-off. Avoid jargon.`,
user: `Draft an email to decline a vendor's proposal for the IT infrastructure upgrade project. The proposal was technically sound but exceeded our budget by 40%. Be polite and leave the door open for future engagement.`,
model: 'llama3.1:8b', temp: 0.5
}
};
function loadTemplate(name) {
const t = TEMPLATES[name];
document.getElementById('ps-system').value = t.system;
document.getElementById('ps-user').value = t.user;
document.getElementById('ps-model').value = t.model;
document.getElementById('ps-temp').value = t.temp;
document.getElementById('ps-temp-val').textContent = t.temp;
showToast('Template loaded');
}
// ── Mock responses ────────────────────────────────────────────────────────────
const MOCK_RESPONSES = [
`## Response
Based on the system prompt and your query, here is a structured response:
**Key Points:**
- The AI model has processed your system prompt and understood the role definition
- Temperature setting of {{temp}} produces {{temp_desc}} output
- Model **{{model}}** is well-suited for this task type
**Analysis:**
This prompt structure follows best practices for instruction-following models. The system prompt clearly defines the role and constraints, while the user message provides a specific, actionable request.
**Recommendation:**
For production use, consider:
1. Adding examples (few-shot) to the system prompt for consistent formatting
2. Specifying the exact output format expected
3. Testing edge cases where the AI might hallucinate
\`\`\`json
{
"prompt_quality": "high",
"clarity": 9,
"specificity": 8,
"suggested_temp": {{temp}}
}
\`\`\``,
`## Extracted Information
| Field | Value |
|-------|-------|
| Model | {{model}} |
| Temperature | {{temp}} |
| Task type | Text Analysis |
| Response quality | High |
**Summary:**
The query has been processed successfully. The system prompt establishes clear boundaries for the assistant's behaviour, and the user message provides sufficient context for an accurate response.
**Action Items:**
- Review the output against your expected format
- Adjust temperature if output is too creative (increase specificity) or too rigid (increase creativity)
- Save this prompt to the library if it performs well`,
`Thank you for this query. Let me provide a thorough response.
**Overview:** This is a demonstration response from the Nexus One AI system running **{{model}}** locally on your hardware. No data has left your network.
The system prompt you've written establishes the AI as a specialised assistant with clear constraints. This is a well-structured prompt that should produce consistent, reliable outputs in production.
**Prompt Quality Assessment:**
*Strengths:*
- Clear role definition
- Specific task scope
- Appropriate tone guidance
*Areas to refine:*
- Consider adding 1-2 example interactions (few-shot prompting)
- Specify output format explicitly (JSON, bullet points, prose)
- Add a fallback instruction for out-of-scope queries
> **Tip:** A temperature of {{temp}} is {{temp_desc}} for this type of task. For factual extraction, try 0.1-0.2. For creative writing, 0.7-0.9.`
];
function getMockResponse(model, temp) {
const idx = Math.floor(Math.random() * MOCK_RESPONSES.length);
const tempDesc = temp < 0.3 ? 'precise and deterministic' : temp > 0.7 ? 'creative and varied' : 'balanced';
return MOCK_RESPONSES[idx]
.replace(/{{model}}/g, model)
.replace(/{{temp}}/g, temp)
.replace(/{{temp_desc}}/g, tempDesc);
}
// ── Markdown renderer ─────────────────────────────────────────────────────────
function renderMd(raw) {
if (!raw) return '';
let html = raw
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/```(\w*)\n([\s\S]*?)```/g, (_,lang,code) =>
`<pre><code class="lang-${lang}">${code.trimEnd()}</code></pre>`)
.replace(/`([^`]+)`/g,'<code>$1</code>')
.replace(/^#{3}\s+(.+)$/gm,'<h3>$1</h3>')
.replace(/^#{2}\s+(.+)$/gm,'<h2>$1</h2>')
.replace(/^#{1}\s+(.+)$/gm,'<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
.replace(/\*(.+?)\*/g,'<em>$1</em>')
.replace(/^>\s(.+)$/gm,'<blockquote>$1</blockquote>')
.replace(/^\|(.+)\|$/gm, row => {
const cells = row.split('|').filter((_,i,a) => i>0 && i<a.length-1);
return '<tr>' + cells.map(c => c.trim().match(/^[-:]+$/) ? '' : `<td>${c.trim()}</td>`).join('') + '</tr>';
})
.replace(/(<tr>.*?<\/tr>\n?)+/gs, t => `<table>${t}</table>`)
.replace(/<table>(<tr><\/tr>\n?)/g,'<table>')
.replace(/^[-*]\s+(.+)$/gm,'<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, l => `<ul>${l}</ul>`)
.replace(/^\d+\.\s+(.+)$/gm,'<li>$1</li>')
.replace(/\n{2,}/g,'</p><p>')
.replace(/\n/g,'<br>');
return `<p>${html}</p>`;
}
// ── Run ───────────────────────────────────────────────────────────────────────
async function runPrompt() {
const system = document.getElementById('ps-system').value.trim();
const user = document.getElementById('ps-user').value.trim();
const model = document.getElementById('ps-model').value;
const temp = parseFloat(document.getElementById('ps-temp').value);
const maxTok = parseInt(document.getElementById('ps-maxtok').value);
if (!user) { showToast('Enter a user message first'); return; }
const btn = document.getElementById('ps-run-btn');
btn.classList.add('running'); btn.disabled = true;
document.getElementById('ps-empty-state')?.remove();
document.getElementById('ps-stat-chips').style.display = 'none';
const block = document.createElement('div');
block.className = 'ps-response-block';
block.innerHTML = '<span class="ps-streaming-cursor"></span>';
const body = document.getElementById('ps-output-body');
body.innerHTML = '';
body.appendChild(block);
document.getElementById('ps-output-label').textContent = `${model} — running…`;
const startTime = Date.now();
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, system_prompt: system, message: user, temperature: temp, max_tokens: maxTok })
});
if (!res.ok) throw new Error('API error');
const data = await res.json();
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const tokens = data.usage?.total_tokens || Math.floor(data.response?.length / 4) || 0;
renderOutput(block, data.response || '', model, elapsed, tokens);
addToHistory({ system, user, model, temp, response: data.response, elapsed, tokens });
} catch {
// Mock fallback
const mockText = getMockResponse(model, temp);
let i = 0;
const interval = setInterval(() => {
i = Math.min(i + Math.floor(Math.random()*8)+4, mockText.length);
block.innerHTML = renderMd(mockText.slice(0, i)) + (i < mockText.length ? '<span class="ps-streaming-cursor"></span>' : '');
body.scrollTop = body.scrollHeight;
if (i >= mockText.length) {
clearInterval(interval);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const tokens = Math.floor(mockText.length / 3.5);
renderOutput(block, mockText, model, elapsed, tokens);
addToHistory({ system, user, model, temp, response: mockText, elapsed, tokens });
btn.classList.remove('running'); btn.disabled = false;
}
}, 18);
return;
}
btn.classList.remove('running'); btn.disabled = false;
}
function renderOutput(block, text, model, elapsed, tokens) {
block.innerHTML = renderMd(text);
document.getElementById('ps-output-label').textContent = `${model}`;
const chips = document.getElementById('ps-stat-chips');
chips.style.display = 'flex';
document.getElementById('ps-stat-time').textContent = `${elapsed}s`;
document.getElementById('ps-stat-tokens').textContent = `~${tokens} tokens`;
}
// ── History ───────────────────────────────────────────────────────────────────
function addToHistory(run) {
run.id = Date.now();
run.ts = new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
runHistory.unshift(run);
if (runHistory.length > 50) runHistory.pop();
localStorage.setItem('cezen_ps_history', JSON.stringify(runHistory));
renderHistory();
}
function renderHistory() {
const list = document.getElementById('ps-history-list');
if (!runHistory.length) {
list.innerHTML = '<div class="ps-history-empty" id="ps-history-empty">No runs yet.</div>';
return;
}
list.innerHTML = runHistory.map(r => `
<div class="ps-history-item ${r.id === currentRunId ? 'active' : ''}" onclick="loadRun(${r.id})">
<div class="ps-history-item-header">
<span class="ps-history-model">${r.model.split(':')[0]}</span>
<span class="ps-history-time">${r.ts}</span>
</div>
<div class="ps-history-prompt">${r.user.slice(0,70)}${r.user.length>70?'…':''}</div>
<div class="ps-history-stats">
<span class="ps-history-stat">🕐 ${r.elapsed}s</span>
<span class="ps-history-stat">🔤 ~${r.tokens} tok</span>
<span class="ps-history-stat">🌡 ${r.temp}</span>
</div>
</div>
`).join('');
}
function loadRun(id) {
const run = runHistory.find(r => r.id === id);
if (!run) return;
currentRunId = id;
document.getElementById('ps-system').value = run.system;
document.getElementById('ps-user').value = run.user;
document.getElementById('ps-model').value = run.model;
document.getElementById('ps-temp').value = run.temp;
document.getElementById('ps-temp-val').textContent = run.temp;
const body = document.getElementById('ps-output-body');
const block = document.createElement('div');
block.className = 'ps-response-block';
block.innerHTML = renderMd(run.response);
body.innerHTML = '';
body.appendChild(block);
document.getElementById('ps-output-label').textContent = run.model;
document.getElementById('ps-stat-chips').style.display = 'flex';
document.getElementById('ps-stat-time').textContent = `${run.elapsed}s`;
document.getElementById('ps-stat-tokens').textContent = `~${run.tokens} tokens`;
renderHistory();
}
function clearHistory() {
runHistory = [];
localStorage.removeItem('cezen_ps_history');
renderHistory();
}
// ── Utilities ─────────────────────────────────────────────────────────────────
function clearAll() {
document.getElementById('ps-system').value = '';
document.getElementById('ps-user').value = '';
document.getElementById('ps-output-body').innerHTML = `
<div class="ps-output-empty" id="ps-empty-state">
<div class="ps-empty-icon">⚗️</div>
<p>Write a system prompt and message, then hit <strong>Run</strong>.</p>
<p style="font-size:12px;color:var(--lt)">Results appear here with token count and latency.</p>
</div>`;
document.getElementById('ps-stat-chips').style.display = 'none';
document.getElementById('ps-output-label').textContent = 'Output';
}
function copyOutput() {
const block = document.querySelector('.ps-response-block');
if (!block) { showToast('Nothing to copy'); return; }
navigator.clipboard.writeText(block.innerText).then(() => showToast('Copied to clipboard'));
}
function sendToEval() {
const user = document.getElementById('ps-user').value;
const model = document.getElementById('ps-model').value;
if (!user) { showToast('Run a prompt first'); return; }
localStorage.setItem('cezen_eval_import', JSON.stringify({ input: user, model }));
window.location.href = 'evals.html';
}
function openCompare() {
const system = document.getElementById('ps-system').value;
const user = document.getElementById('ps-user').value;
if (!user) { showToast('Enter a message first'); return; }
localStorage.setItem('cezen_compare_import', JSON.stringify({ system, user }));
window.location.href = 'model-compare.html';
}
function saveToLibrary() {
const system = document.getElementById('ps-system').value.trim();
const user = document.getElementById('ps-user').value.trim();
if (!system && !user) { showToast('Nothing to save'); return; }
const library = JSON.parse(localStorage.getItem('cezen_prompts_custom') || '[]');
library.push({ id: Date.now(), title: user.slice(0,50) || 'Untitled', system, content: user, saved_from: 'studio', ts: new Date().toISOString() });
localStorage.setItem('cezen_prompts_custom', JSON.stringify(library));
showToast('Saved to Prompt Library');
}
function handleKey(e) {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) runPrompt();
}
function showToast(msg) {
const t = document.getElementById('ps-toast');
t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2200);
}
// ── Init ──────────────────────────────────────────────────────────────────────
renderHistory();
// Load compare import if arriving from model-compare
const compareImport = localStorage.getItem('cezen_studio_import');
if (compareImport) {
const d = JSON.parse(compareImport);
if (d.system) document.getElementById('ps-system').value = d.system;
if (d.user) document.getElementById('ps-user').value = d.user;
if (d.model) document.getElementById('ps-model').value = d.model;
localStorage.removeItem('cezen_studio_import');
}
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
</body>
</html>