feat: simplify SOP to immediate-effect configuration

This commit is contained in:
Eric 1549169735@qq.com
2026-08-18 16:27:02 +08:00
parent c5ab886b70
commit 8de48fb05e
150 changed files with 6764 additions and 1626 deletions

37
sdk/src/index.ts Normal file
View File

@@ -0,0 +1,37 @@
export type ScenarioInput = Record<string, unknown>
export type OutputField = { key:string; name:string; type:string; source:string; value:unknown }
export type ResultField = { key:string; name:string; type:string; required?:boolean; options?:unknown[]; fields?:ResultField[]; items?:Omit<ResultField,'key'|'name'> }
export type ResultSchema = { fields:ResultField[] }
export type NodeField = { key:string; name:string; type:string; required:boolean; options:unknown[]; validation:Record<string,unknown> }
export type PresentationField = { key:string; label:string; kind:'text'|'image'; value:unknown }
export type NodePresentation = { summary:PresentationField[]; items:Array<{fields:PresentationField[]}>; opening?:{title:string; content:string} }
export type KnowledgeCollectionOption = { value:string; label:string; parents?:string[]; suggested?:boolean }
export type KnowledgeCollectionStep = { field_key:string; name:string; required:boolean; multiple:boolean; from_field?:string; options:KnowledgeCollectionOption[] }
export type KnowledgeCollection = { context_fields:NodeField[]; context_title?:string; context_hint?:string; selection_title?:string; selection_hint?:string; steps:KnowledgeCollectionStep[] }
export type NodeView = { node_key:string; type:string; title:string; content:string; fields?:NodeField[]; presentation?:NodePresentation; outputs?:unknown[]; collection?:KnowledgeCollection }
export type ScenarioState = { run_id:number; external_ref:string; status:string; final_result?:Record<string, unknown>|null; result_schema:ResultSchema; session_token?:string; node:NodeView; outputs:OutputField[] }
export type SDKEvent = 'ready'|'node_change'|'form_submit'|'finish'|'error'
export type CreateOptions = { baseURL?:string; publicKey:string; scenarioKey:string; sopId?:number; input?:ScenarioInput; initialValues?:ScenarioInput; externalRef?:string }
export type FinishOptions = { result?:string; finalResult?:Record<string, unknown>|null }
export class SalesScenario {
private token=''
private state?:ScenarioState
private handlers=new Map<SDKEvent,Set<(payload:unknown)=>void>>()
constructor(private readonly options:CreateOptions){}
async start(){const state=await this.request<ScenarioState>(`/public/scenarios/${encodeURIComponent(this.options.scenarioKey)}/runs`,{method:'POST',body:JSON.stringify({public_key:this.options.publicKey,sop_id:this.options.sopId,input:this.options.input||{},initial_values:this.options.initialValues||{},external_ref:this.options.externalRef||''})});if(state.session_token)this.token=state.session_token;this.setState(state);this.emit('ready',state);return state}
getState(){if(!this.state)throw new Error('SDK has not started');return this.state}
async getCurrentNode(){return this.update(`/public/runs/${this.getState().run_id}/current`)}
async submit(values:ScenarioInput){const state=await this.update(`/public/runs/${this.getState().run_id}/submit`,{method:'POST',body:JSON.stringify({node_key:this.getState().node.node_key,answers:values})});this.emit('form_submit',{values,state});return state}
async next(){return this.update(`/public/runs/${this.getState().run_id}/next`,{method:'POST'})}
async finish(options:FinishOptions={}){const state=await this.update(`/public/runs/${this.getState().run_id}/finish`,{method:'POST',body:JSON.stringify({result:options.result||'',final_result:options.finalResult??null})});this.emit('finish',state);return state}
async reset(){return this.update(`/public/runs/${this.getState().run_id}/reset`,{method:'POST'})}
destroy(){this.handlers.clear();this.token='';this.state=undefined}
on(event:SDKEvent,handler:(payload:unknown)=>void){const handlers=this.handlers.get(event)||new Set();handlers.add(handler);this.handlers.set(event,handlers);return()=>handlers.delete(handler)}
private async update(path:string,init:RequestInit={}){const state=await this.request<ScenarioState>(path,init);this.setState(state);return state}
private setState(state:ScenarioState){this.state=state;this.emit('node_change',state.node)}
private emit(event:SDKEvent,payload:unknown){this.handlers.get(event)?.forEach(handler=>handler(payload))}
private async request<T>(path:string,init:RequestInit){try{const response=await fetch(`${(this.options.baseURL||'').replace(/\/$/,'')}${path}`,{...init,headers:{'Content-Type':'application/json',...(this.token?{Authorization:`Bearer ${this.token}`}:{})}});const body=await response.json();if(!response.ok)throw new Error(body.message||`Request failed: ${response.status}`);return (body.data||body) as T}catch(error){this.emit('error',error);throw error}}
}
export function create(options:CreateOptions){return new SalesScenario(options)}