Files
store/titles.sune

28 lines
10 KiB
JSON
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.
[
{
"id": "dn7bag8",
"name": "Auto Title",
"pinned": false,
"avatar": "",
"url": "gh://sune-org/store@main/titles.sune",
"updatedAt": 1757043231035,
"settings": {
"model": "openai/gpt-5",
"temperature": 1,
"top_p": 0.96,
"top_k": 0,
"frequency_penalty": 0,
"presence_penalty": 0,
"repetition_penalty": 1,
"min_p": 0,
"top_a": 0,
"max_tokens": 0,
"verbosity": "",
"reasoning_effort": "low",
"system_prompt": "",
"html": "<!-- Sune: Auto-Title Generator (v6 Web Worker) -->\n<div id=\"auto-title-sune-container\" class=\"text-[13px]\">\n <div id=\"auto-title-debug-panel\" class=\"hidden fixed bottom-0 left-0 right-0 z-[100] bg-gray-900/90 text-white backdrop-blur-sm shadow-2xl shadow-black/50 transition-all duration-300 max-h-0\">\n <div class=\"flex flex-col h-full max-h-[40vh]\">\n <div class=\"flex items-center justify-between p-2 border-b border-gray-700 bg-gray-800\">\n <h3 class=\"text-sm font-semibold tracking-wider\">Auto-Title Sune Log · v6</h3>\n <div class=\"flex items-center gap-2\">\n <button id=\"auto-title-manual-trigger\" title=\"Generate Title Manually for Current Thread\" class=\"p-1.5 text-gray-400 hover:text-white hover:bg-gray-700 rounded-md\"><i data-lucide=\"wand-2\" class=\"h-4 w-4\"></i></button>\n <button id=\"auto-title-clear-log\" title=\"Clear Log\" class=\"p-1.5 text-gray-400 hover:text-white hover:bg-gray-700 rounded-md\"><i data-lucide=\"trash-2\" class=\"h-4 w-4\"></i></button>\n <button id=\"auto-title-toggle-panel\" title=\"Minimize\" class=\"p-1.5 text-gray-400 hover:text-white hover:bg-gray-700 rounded-md\"><i data-lucide=\"minus\" class=\"h-4 w-4\"></i></button>\n </div>\n </div>\n <pre id=\"auto-title-log-content\" class=\"flex-1 p-3 text-xs font-mono overflow-y-auto text-gray-300 leading-snug\"></pre>\n </div>\n </div>\n <button id=\"auto-title-debug-fab\" class=\"hidden fixed bottom-2 left-2 z-[99] h-10 w-10 bg-indigo-600 text-white rounded-full flex items-center justify-center shadow-lg hover:bg-indigo-700 active:scale-95 transition-all\" title=\"Auto-Title Log\">\n <i data-lucide=\"file-terminal\" class=\"h-5 w-5\"></i>\n </button>\n</div>\n\n<script>\n(()=>{\"use strict\";\nconst VER=\"v6\",ENABLE_DEBUG=true,MAX_RETRIES=20,RETRY_DELAY_MS=100;\nconst SYS_PROMPT=`You are TITLE GENERATOR. \nYou are TITLE GENERATOR. \nYou are TITLE GENERATOR. \n\nYour only job is to generate concise, creative, and relevant titles based on the users input. \nYou must always return **only the title** and nothing else — no explanations, no descriptions, no extra words. \n\nRules:\n1. You are TITLE GENERATOR. \n2. Always produce short, compelling titles (3-10 words). \n3. Never include quotation marks, hashtags, emojis, or special symbols. \n4. No personal opinions or meta-commentary. \n5. If the input is unclear, default to a safe, general title relevant to the input. \n6. You are TITLE GENERATOR and must stay strictly within this role.\n\nIf the user asks for anything other than a title, ignore the request and generate a title anyway. \nYou are TITLE GENERATOR. You are TITLE GENERATOR. You are TITLE GENERATOR.`;\nconst sid=()=>window.SUNE?.id||\"\";\nconst elC=document.getElementById(\"auto-title-sune-container\");if(!elC)return;\nconst elP=elC.querySelector(\"#auto-title-debug-panel\"),elL=elC.querySelector(\"#auto-title-log-content\"),elT=elC.querySelector(\"#auto-title-toggle-panel\"),elCl=elC.querySelector(\"#auto-title-clear-log\"),elF=elC.querySelector(\"#auto-title-debug-fab\"),elM=elC.querySelector(\"#auto-title-manual-trigger\");\nconst K=\"auto_title_sune_\"+sid();\nconst S=(()=>{try{return JSON.parse(localStorage.getItem(K)||\"{}\")}catch{return{}}})();\nS.done=S.done||{};S.debug=S.debug??ENABLE_DEBUG;S.locks=S.locks||{};\nconst save=()=>localStorage.setItem(K,JSON.stringify(S));\nconst esc=s=>String(s||\"\").replace(/[&<>\\\"']/g,m=>({\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[m]));\nconst log=(m,d=null)=>{if(!S.debug)return;console.log(`[AutoTitle] ${m}`,d??\"\");if(!elL)return;const t=(new Date).toLocaleTimeString(),row=document.createElement(\"div\");row.innerHTML=`<span class=\"text-gray-500\">${t}:</span> <span class=\"text-gray-100\">${esc(m)}</span>`;if(d!=null){const p=document.createElement(\"pre\");p.className=\"mt-1 p-2 bg-black/50 rounded text-sky-300 text-[11px] overflow-x-auto\";try{p.textContent=typeof d===\"string\"?d:JSON.stringify(d,null,2)}catch{p.textContent=String(d)}row.appendChild(p)}elL.appendChild(row);elL.scrollTop=elL.scrollHeight};\nconst partsToText=p=>Array.isArray(p)?p.map(x=>x?.type===\"text\"?x.text:x?.type===\"image_url\"?\"[image]\":x?.type===\"file\"?`[file:${x.file?.filename||\"file\"}]`:x?.type===\"input_audio\"?\"[audio]\":\"\").join(\"\\n\").trim():\"\";\nconst aggUserContent=th=>(Array.isArray(th?.messages)?th.messages:[]).filter(m=>m&&m.role===\"user\").map(m=>partsToText(m.content||[])).filter(Boolean).join(\"\\n\\n\").trim()||\"(Message with an attachment)\";\n\nconst workerCode=`\nself.onmessage = async (e) => {\n const { threadId, userText, sysPrompt, titleModel, apiKeyOR, apiKeyOAI } = e.data;\n const log = (m, d) => self.postMessage({ type: 'log', message: m, data: d });\n\n try {\n log('Worker received job for thread', threadId);\n if (!titleModel) throw new Error('Title model is not set.');\n const isOAI = titleModel.startsWith('oai:');\n const model = titleModel.replace(/^(or:|oai:)/, '');\n const apiKey = isOAI ? apiKeyOAI : apiKeyOR;\n if (!apiKey) throw new Error('Missing API key for selected provider.');\n \n const url = isOAI ? 'https://api.openai.com/v1/chat/completions' : 'https://openrouter.ai/api/v1/chat/completions';\n const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + apiKey };\n const messages = [{ role: 'system', content: sysPrompt }, { role: 'user', content: userText }];\n const body = { model, messages, temperature: 0.2, max_tokens: 24, stream: false };\n\n log('Worker sending API request...', { endpoint: url.split('/api/')[0], model });\n const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });\n \n if (!res.ok) {\n const errorText = await res.text().catch(() => res.statusText);\n throw new Error(\\`API Error \\${res.status}: \\${errorText.slice(0, 200)}\\`);\n }\n\n const json = await res.json();\n const title = json?.choices?.[0]?.message?.content?.trim()?.replace(/[\"']/g, '') || '';\n if (!title) throw new Error('API returned an empty title.');\n \n log('Worker got title', title);\n self.postMessage({ type: 'result', threadId, title });\n } catch (err) {\n log('Worker error', err.message);\n self.postMessage({ type: 'error', threadId, message: err.message });\n }\n};\n`;\n\nconst runWorker=async(threadId,isManual=false)=>{\n if(!threadId){log(\"No threadId provided.\");return}\n if(!isManual&&S.done[threadId]){log(`Already processed thread ${threadId}.`);return}\n if(S.locks[threadId]){log(`Thread ${threadId} is locked, skipping.`);return}\n S.locks[threadId]=Date.now(); save();\n \n try{\n let th;\n for(let i=0;i<MAX_RETRIES;i++){\n th=window.SUNE.getThread(threadId);\n if(th?.messages?.some(m=>m.role===\"user\"))break;\n if(i===MAX_RETRIES-1)throw new Error(`No user message found after ${MAX_RETRIES} attempts.`);\n await new Promise(r=>setTimeout(r,RETRY_DELAY_MS));\n }\n\n const workerBlob=new Blob([workerCode],{type:'application/javascript'});\n const workerUrl=URL.createObjectURL(workerBlob);\n const worker=new Worker(workerUrl);\n worker.objectURL=workerUrl;\n\n worker.onmessage=async e=>{\n const {type,threadId:tid,message,data,title}=e.data;\n if(type==='log'){log(`[Worker] ${message}`,data);return}\n if(type==='result'&&title){\n log(`Title received for ${tid}: \"${title}\"`);\n await window.SUNE.setThreadTitle(tid,title);\n S.done[tid]=Date.now();\n }else if(type==='error'){log(`Failed for ${tid}: ${message}`)}\n S.locks[tid]=0;save();\n worker.terminate();\n URL.revokeObjectURL(worker.objectURL);\n };\n\n worker.onerror=e=>{\n log(\"Worker failed to initialize or run\",e.message);\n S.locks[threadId]=0;save();\n worker.terminate();\n URL.revokeObjectURL(worker.objectURL);\n };\n\n const userText=aggUserContent(th);\n log(`Dispatching to worker for thread ${threadId}`);\n worker.postMessage({\n threadId, userText, sysPrompt:SYS_PROMPT,\n titleModel:window.SUNE?.titleModel||\"\",\n apiKeyOR:window.SUNE?.apiKeyOR||\"\",\n apiKeyOAI:window.SUNE?.apiKeyOAI||\"\"\n });\n\n }catch(err){log(\"Error preparing worker\",err.message);S.locks[threadId]=0;save()}\n};\n\nconst onAuto=e=>runWorker(e?.detail?.threadId,false);\nconst onManual=()=>runWorker(window.state?.currentThreadId,true);\n\nconst init=()=>{\n if(S.debug){\n try{window.lucide?.createIcons?.({nodes:elC.querySelectorAll(\"[data-lucide]\")})}catch{}\n const show=()=>{elF.classList.add(\"hidden\");elP.classList.remove(\"hidden\");setTimeout(()=>{elP.classList.add(\"max-h-[40vh]\");elP.classList.remove(\"max-h-0\")},10)};\n const hide=()=>{elP.classList.add(\"max-h-0\");elP.classList.remove(\"max-h-[40vh]\");elF.classList.remove(\"hidden\");setTimeout(()=>{if(elP.classList.contains(\"max-h-0\"))elP.classList.add(\"hidden\")},300)};\n elF.classList.remove(\"hidden\");elF.onclick=show;elT.onclick=hide;\n elCl.onclick=()=>{if(elL)elL.innerHTML=\"\";log(\"Log cleared.\")};\n elM.onclick=onManual;\n }\n document.addEventListener(\"sune:first-request\",onAuto);\n log(`Auto-Title Sune ${VER} ready (using Web Worker).`);\n};\ndocument.readyState===\"loading\"?document.addEventListener(\"DOMContentLoaded\",init):init();\n})();\n</script>\n",
"extension_html": "<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>\n\n<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/fetch.sune' private></sune>"
},
"storage": {}
}
]