serve-avatar-reference.mjs 4.4 KB

12345678910111213141516171819202122232425262728293031
  1. import {createServer} from 'vite';
  2. import vue from '@vitejs/plugin-vue';
  3. import {mkdirSync,mkdtempSync,readFileSync,writeFileSync} from 'node:fs';
  4. import {resolve,join} from 'node:path';
  5. import {tmpdir} from 'node:os';
  6. import {fileURLToPath} from 'node:url';
  7. const target=resolve(fileURLToPath(new URL('..',import.meta.url))), source=process.env.OPENMAUS_REFERENCE_ROOT || resolve(target,'../OpenMausBot'), root=mkdtempSync(join(tmpdir(),'yaoyao-avatar-reference-'));
  8. mkdirSync(root+'/reference',{recursive:true});mkdirSync(root+'/ported',{recursive:true});
  9. const shapes=['circle','ellipse','square','capsule','triangle','hexagon','cloud','droplet'];
  10. const bodies=['cursor','blob','circle','squircle','capsule','drop','shield','hexagon','diamond','star'];
  11. const expressions=['idle','happy','curious','drowsy','working','thinking','listening','sleeping','suspicious','proud'];
  12. const cases=[...shapes.map(shape=>({id:'shape-'+shape,shape,color:'green',hex:'#00c875',expression:'idle'})),...bodies.map(bodyId=>({id:'body-'+bodyId,shape:'circle',bodyId,color:'blue',hex:'#1488ff',expression:'happy'})),...expressions.map(expression=>({id:'face-'+expression,shape:'circle',color:'pink',hex:'#f52ba5',expression}))];
  13. const html='<html><head><style>body{margin:0;background:white;font:12px Arial}.grid{display:grid;grid-template-columns:repeat(7,128px);gap:12px;padding:12px}.case{display:flex;flex-direction:column;align-items:center;gap:6px;width:128px;height:128px}.inline-flex{display:inline-flex}</style></head><body><div id="app"></div><script type="module" src="/main.tsx"></script></body></html>';
  14. for(const sub of ['reference','ported'])writeFileSync(root+'/'+sub+'/index.html',html);
  15. let cursor=readFileSync(source+'/src/components/CursorAvatar.tsx','utf8');
  16. cursor=cursor.replace('"../../shared/mascot-bodies"',JSON.stringify(source+'/shared/mascot-bodies.ts')).replace('"./cursor-face-data"',JSON.stringify(source+'/src/components/cursor-face-data.ts'));
  17. // Canonical resting-frame capture: settle the spring and hold elapsed time at 0.
  18. // This only controls the test clock; all original geometry/drawing code is unchanged.
  19. cursor=cursor.replace('if (p.paused) {','if (p.paused) { e.morph = 1; e.stateStart = now;');
  20. writeFileSync(root+'/reference/CursorAvatar.tsx',cursor);
  21. let avatar=readFileSync(source+'/src/components/Avatar.tsx','utf8').replace('"../../shared/bot-avatar"',JSON.stringify(source+'/shared/bot-avatar.ts')).replace('"../../shared/mascot-bodies"',JSON.stringify(source+'/shared/mascot-bodies.ts'));
  22. writeFileSync(root+'/reference/Avatar.tsx',avatar);
  23. writeFileSync(root+'/reference/main.tsx',`import React from 'react';import{createRoot}from'react-dom/client';import{MausAvatar}from'./Avatar';const cases=${JSON.stringify(cases)};createRoot(document.getElementById('app')!).render(<div className="grid">{cases.map(c=><div className="case" data-testid={c.id} key={c.id}><MausAvatar color={c.color as any} shape={(c.shape==='ellipse'?'oval':c.shape) as any} bodyId={c.bodyId as any} state={c.expression as any} size={96} animated={false}/><span>{c.id}</span></div>)}</div>);`);
  24. writeFileSync(root+'/ported/main.tsx',`import{createApp,h}from'vue';import AgentAvatar from '${target}/src/client/components/common/AgentAvatar.vue';import{defaultAgentIdentity,encodeAgentAvatar}from'${target}/src/shared/agentIdentity.ts';const cases=${JSON.stringify(cases)};createApp({render:()=>h('div',{class:'grid'},cases.map(c=>h('div',{class:'case','data-testid':c.id},[h(AgentAvatar,{name:c.id,avatar:encodeAgentAvatar({...defaultAgentIdentity(c.id),shape:c.shape,bodyId:c.bodyId??null,color:c.hex,expression:c.expression}),size:96,animated:false,fixedTime:0}),h('span',c.id)])))}).mount('#app');`);
  25. writeFileSync(root+'/cases.json',JSON.stringify(cases));
  26. const servers=[];
  27. for(const [sub,port]of[['reference',18806],['ported',18807]]){
  28. const server=await createServer({configFile:false,root:root+'/'+sub,plugins:sub==='ported'?[vue()]:[],resolve:{alias:{'@':sub==='reference'?source+'/src':target+'/src/client','@shared':target+'/src/shared',react:source+'/node_modules/react','react-dom':source+'/node_modules/react-dom',vue:target+'/node_modules/vue'}},server:{host:'127.0.0.1',port,strictPort:true,fs:{allow:[root,source,target]}}});
  29. await server.listen();servers.push(server);console.log(sub,port);
  30. }
  31. for(const signal of ['SIGINT','SIGTERM'])process.once(signal,async()=>{for(const server of servers)await server.close();process.exit()});