export const MODEL = { repo:'openbmb/MiniCPM5-2B', source:'openbmb/MiniCPM5-2B-GGUF', weights:'ProCreations/MiniCPM5-2B-Q8_0-WebGPU', revision:'0f58fbf0931ef7634d5171edeb51be11d8b0ea2d', file:'MiniCPM5-2B-Q8_0-00001-of-00006.gguf', bytes:2679710688, shardSizes:[289229824,508111456,501519680,501519744,501519744,377810912], maxContext:131072 }; export const DSPARK={repo:'openbmb/MiniCPM5-2B-DSpark',revision:'114a20fdbf53220712c7fbdd7dccddbf1dedebb4',file:'model.safetensors',bytes:647558522}; export const DEFAULTS = { context:16384, cacheK:'f16', cacheV:'f16', temperature:1, topP:0.95, topK:40, repeatPenalty:1, maxTokens:4096, thinking:false, tools:true, system:'', seed:-1, performance:'fast', saveModel:false,dspark:'auto' }; export const CONTEXTS = [2048,4096,8192,16384,32768,65536,131072]; export const KV_FORMATS = { f32:{name:'FP32 · full',bytes:4},f16:{name:'FP16 · half',bytes:2},q8_0:{name:'Q8 · 8-bit',bytes:36/32},q4_0:{name:'Q4 · 4-bit',bytes:20/32} }; export const kvBytes = (context,k,v) => context*42*2*128*(KV_FORMATS[k].bytes+KV_FORMATS[v].bytes); export const formatBytes = n => n>=1e9 ? `${(n/1e9).toFixed(2)} GB` : n>=1e6?`${(n/1e6).toFixed(0)} MB`:n>=1000?`${(n/1000).toFixed(1)} KB`:`${n} B`; export const modelDownloadBytes=s=>MODEL.bytes+(s.dspark!=='off'?DSPARK.bytes:0); export const gpuMemoryBytes=s=>MODEL.bytes+kvBytes(s.context,s.cacheK,s.cacheV)+450e6+(s.dspark!=='off'?DSPARK.bytes+kvBytes(s.context,s.cacheK,s.cacheV)*5/42+64e6:0); export const escapeHTML = (v='') => String(v).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); export const safeFilename = title => String(title||'Untitled').replace(/[^\p{L}\p{N} _-]/gu,'').trim().replace(/\s+/g,'-').slice(0,90)||'Untitled'; export function validateSettings(s) { const r={...DEFAULTS,...s}; r.saveModel=r.saveModel===true; if(!['auto','on','off'].includes(r.dspark))r.dspark='auto'; if(!CONTEXTS.includes(+r.context)) r.context=DEFAULTS.context; // One precision setting controls both caches; migrate older separate choices. if(!KV_FORMATS[r.cacheK])r.cacheK=DEFAULTS.cacheK; r.cacheV=r.cacheK; for(const [k,min,max] of [['temperature',0,2],['topP',0.05,1],['topK',0,200],['repeatPenalty',0.8,1.5],['maxTokens',128,32768],['seed',-1,2147483647]]) r[k]=Math.max(min,Math.min(max,Number(r[k])||0)); r.maxTokens=Math.min(r.maxTokens,r.context-512); r.system=String(r.system||'').slice(0,12000); return r; } const tool = (name,description,properties,required)=>({type:'function',function:{name,description,parameters:{type:'object',properties,required,additionalProperties:false}}}); const title={type:'string',description:'A short descriptive filename without an extension.'}; const markdown={type:'string',description:'Complete document content as Markdown. Use headings, paragraphs, lists and tables.'}; export const TOOLS = [ tool('create_html','Create a complete, downloadable HTML website or interactive app. The result is rendered in a sandboxed preview. Use a single self-contained HTML file with inline CSS and JavaScript. No network, CDNs, imports, external fonts or images are available in previews. Use CSS, inline SVG, canvas and data URLs for visuals.',{title,html:{type:'string',description:'The complete HTML document including styles and any scripts.'}},['title','html']), tool('create_document','Create a formatted, downloadable Word (.docx) document with a readable preview.',{title,markdown},['title','markdown']), tool('create_spreadsheet','Create a styled Excel (.xlsx) workbook with one or more sheets and a table preview. Formulas start with =. Use numbers for numeric cells. Formulas recalculate in Excel.',{title,sheets:{type:'array',items:{type:'object',properties:{name:{type:'string'},columns:{type:'array',items:{type:'string'}},rows:{type:'array',items:{type:'array',items:{type:['string','number','boolean','null']}}}},required:['name','columns','rows']}}},['title','sheets']), tool('create_pdf','Create a professionally formatted, paginated PDF for immediate preview and download.',{title,markdown},['title','markdown']) ]; export const SYSTEM = `You are MiniCPM5, a capable local assistant created by OpenBMB. You run entirely in the user's browser in a ProCreations playground. Answer clearly and be helpful. When asked to create a website, HTML app, document, spreadsheet, or PDF, CALL the appropriate create_* tool with the COMPLETE content, instead of just writing instructions or a code block. Use thoughtful typography, color, spacing and useful real content in websites. All tools run locally; you cannot browse the web, install packages, access arbitrary files or fetch external resources. Never claim you used a tool unless its result confirms success. Tool results appear as downloadable artifact cards. You can call multiple tools when needed. When a tool returns an error, fix its arguments and retry. After successful creation, briefly describe the result. Do not repeat the artifact's entire content in your response.`; // MiniCPM5 emits XML calls. Native llama.cpp parsers may vary by release; // this lossless fallback supports the model's official XML / CDATA format. export function parseXMLCalls(text) { const calls=[]; const rx=/([\s\S]*?)<\/function>/g; for(const match of text.matchAll(rx)) { const args={}; for(const p of match[2].matchAll(/([\s\S]*?)<\/param>/g)) { let value=p[2].trim(); if(value.startsWith(''))value=value.slice(9,-3); else value=value.replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,'&'); if(p[1]==='sheets') {try{value=JSON.parse(value);}catch{/* validator returns repairable error */}} args[p[1]]=value; } calls.push({id:`call_${calls.length}`,type:'function',function:{name:match[1],arguments:JSON.stringify(args)}}); } return calls; } export function validateArtifact(name,args) { if(!TOOLS.some(t=>t.function.name===name))throw new Error(`Unknown tool: ${name}`); if(!args||typeof args!=='object')throw new Error('Arguments must be an object.'); if(typeof args.title!=='string'||!args.title.trim())throw new Error('A title is required.'); if(name==='create_spreadsheet') { if(typeof args.sheets==='string') {try{args.sheets=JSON.parse(args.sheets);}catch{}} if(!Array.isArray(args.sheets)||!args.sheets.length||args.sheets.length>12)throw new Error('Provide 1–12 sheets.'); for(const sheet of args.sheets) { if(!Array.isArray(sheet.columns)||!sheet.columns.length||sheet.columns.length>100)throw new Error('Each sheet needs 1–100 column headings.'); if(!Array.isArray(sheet.rows)||sheet.rows.length>10000||sheet.rows.some(r=>!Array.isArray(r)||r.length>sheet.columns.length||r.some(v=>v!==null&&!['string','number','boolean'].includes(typeof v))))throw new Error('Rows must be arrays of simple cell values matching the columns.'); } } else { const key=name==='create_html'?'html':'markdown'; if(typeof args[key]!=='string'||!args[key].trim())throw new Error(`Provide complete ${key} content.`); if(name==='create_html'&&/<\/[a-z][a-z0-9-]*\s+[a-z][\w:-]*\s*=/i.test(args.html))throw new Error('Invalid HTML: attributes appear on a closing tag. Put IDs and classes on opening tags, then provide the corrected complete HTML.'); if(args[key].length>2000000)throw new Error('Artifact exceeds the 2 MB text limit.'); } return args; } export const PREVIEW_CSP="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; media-src data: blob:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"; export function sandboxHTML(html) { // CSP precedes all model content. An inner meta policy cannot relax this policy. return `${html}`; }