ProCreations's picture
Dedicated WebGPU runtime, DSpark acceleration, and automatic tool repair
c51a3e7 verified
Raw History Blame Contribute Delete
8.06 kB
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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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=/<function\s+name\s*=\s*["']([^"']+)["']\s*>([\s\S]*?)<\/function>/g;
for(const match of text.matchAll(rx)) {
const args={};
for(const p of match[2].matchAll(/<param\s+name\s*=\s*["']([^"']+)["']\s*>([\s\S]*?)<\/param>/g)) {
let value=p[2].trim();
if(value.startsWith('<![CDATA[')&&value.endsWith(']]>'))value=value.slice(9,-3);
else value=value.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&amp;/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 `<!doctype html><html><head><meta http-equiv="Content-Security-Policy" content="${PREVIEW_CSP}"><meta name="referrer" content="no-referrer"></head><body>${html}</body></html>`;
}