Files
ycode/scripts/fixtures/conversation-states.mjs

66 lines
3.6 KiB
JavaScript

// Offline YCode state samples only; these do not establish original-app parity.
const now = Date.now()
const history = Array.from({ length: 14 }, (_, index) => {
const createdAt = now + index * 2
const text = Array.from({ length: 4 }, (__, paragraph) =>
`历史消息 ${index + 1},段落 ${paragraph + 1}。用于让会话时间线形成可滚动的真实内容。`.repeat(3)
).join('\n\n')
return [
{ id: `history-user-${index}`, role: 'user', content: `历史请求 ${index + 1}`, status: 'done', createdAt },
{ id: `history-assistant-${index}`, role: 'assistant', content: text, status: 'done', createdAt: createdAt + 1 }
]
}).flat()
export const conversationStatesFixture = {
sessions: [{ id: 'conversation-states', title: '对话状态回归', createdAt: now, updatedAt: now }],
messages: { 'conversation-states': history }
}
export const conversationStatesBridge = `<script>
(()=>{
const listeners=new Set(),runs=Object.create(null);let sequence=0;
const state={lastRunId:null,runs,responses:[],responsePlans:[],deferredResponses:[],earlyBatches:[],emit(event){
const runId=event.runId||state.lastRunId,run=runs[runId];
if(!run)throw new Error('No fixture run for event '+event.type);
const payload={...event,runId,sessionId:run.sessionId};
run.events.push(payload);listeners.forEach(listener=>listener(payload));
if(payload.type==='interaction')run.interactions[payload.interaction.id]=payload.interaction;
if(['done','cancelled','error'].includes(payload.type))run.finished=true;
return payload;
},queueResponse(mode){state.responsePlans.push(mode)},queueEarlyBatch(events){state.earlyBatches.push(events)},releaseDeferredResponse(){const release=state.deferredResponses.shift();if(!release)return false;release();return true}};
const completeResponse=(runId,id,response)=>{
const run=runs[runId],interaction=run?.interactions[id];
if(!interaction)return{accepted:false};
const option=interaction.options.find(item=>item.optionId===response.optionId);
if(!option)return{accepted:false};
const status=option.kind.startsWith('reject')?'rejected':'answered';
state.emit({runId,type:'interaction',interaction:{...interaction,selectedOptionId:option.optionId,status}});
return{accepted:true};
};
window.__YCODE_CONVERSATION_STATE_TEST__=state;
window.ycode.chat.onEvent=listener=>{listeners.add(listener);return()=>listeners.delete(listener)};
window.ycode.chat.send=async payload=>{
const runId='state-run-'+(++sequence),assistantMessageId=runId+'-assistant';
const run={runId,sessionId:payload.sessionId,assistantMessageId,events:[],interactions:Object.create(null),finished:false};
runs[runId]=run;state.lastRunId=runId;
const userMessage={id:runId+'-user',requestId:runId,role:'user',content:payload.text,attachments:payload.attachments,createdAt:Date.now(),status:'done'};
const earlyEvents=state.earlyBatches.shift()||[];
earlyEvents.forEach(event=>state.emit({...event,runId,...(event.type==='interaction'?{interaction:{...event.interaction,runId}}:{})}));
return{...run,userMessage};
};
window.ycode.chat.cancel=async runId=>{
const run=runs[runId];
if(!run||run.finished)return{cancelled:false};
state.emit({runId,type:'cancelled'});
return{cancelled:true};
};
window.ycode.chat.respond=async(runId,id,response)=>{
state.responses.push({runId,interactionId:id,response});
const mode=state.responsePlans.shift()||'success';
if(mode==='failure')throw new Error('fixture respond failure');
if(mode==='defer')return await new Promise(resolve=>state.deferredResponses.push(()=>resolve(completeResponse(runId,id,response))));
return completeResponse(runId,id,response);
};
})();
</script>`