File size: 8,058 Bytes
10905e1
c51a3e7
 
a9a3715
c51a3e7
a9a3715
 
c51a3e7
 
a9a3715
 
 
 
54b0b85
c51a3e7
a9a3715
c218615
 
 
a9a3715
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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>`;
}