@graphen.ai/aiia-sdk 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ yarn add @graphen.ai/aiia-sdk
23
23
  ```typescript
24
24
  import { initSdk } from '@graphen.ai/aiia-sdk/browser';
25
25
 
26
- const sdk = new initSdk({
26
+ const sdk = initSdk({
27
27
  worklet_url: "<Please copy '@graphen.ai/aiia-sdk/dist/aiia-worklet.js' to your project's public folder and provide the source url>"
28
28
  });
29
29
  sdk.start()
@@ -63,11 +63,12 @@ const core = aiiaCore({ license: "<Your Token>" });
63
63
  | CODE: 1011 | 指定依賴已完成 | Specified Dependencies Completed |
64
64
  | CODE: 1012 | 啟動SDK | Starting SDK |
65
65
  | CODE: 1013 | 結束SDK | Ending SDK |
66
+ | CODE: 1014 | 已啟動SDK | SDK has been started |
66
67
  | CODE: 2000 | 安全的關閉連線 | Safely Closing Connection |
67
68
  | CODE: 2001 | 無副作用的關閉連線 | Closing Connection Without Side Effects |
68
69
  | CODE: 2002 | 與雲端的服務中斷 | Disconnected from Cloud Service |
69
70
  | CODE: 2003 | 沒有授權,請聯繫Graphen | No Authorization, Please Contact Graphen |
70
- | CODE: 2004 | 自動重新連線連端服務 | Automatically Reconnecting to Cloud Service |
71
+ | CODE: 2004 | 自動重新連線雲端服務 | Automatically Reconnecting to Cloud Service |
71
72
  | CODE: 2005 | 未預期的斷線,請聯繫Graphen | Unexpected Disconnection, Please Contact Graphen |
72
73
  | CODE: 2010 | 開始初始化必要模組 | Starting Initialization of Required Modules |
73
74
  | CODE: 2011 | 指定模組初始化完成 | Specified Module Initialization Completed |
@@ -1 +1,2 @@
1
+ /** @license AiiaVad Released: 2025-12-8 */
1
2
  !function(){"use strict";var t;!function(t){t[t.unknow=0]="unknow",t[t.allowed=1]="allowed",t[t.rejected=2]="rejected"}(t||(t={}));class e extends AudioWorkletProcessor{inputBuffer;inputBufferLength;processChunkSize;outputSampleRate;originalSampleRate;constructor(t){super(),this.inputBuffer=[],this.inputBufferLength=0;const{outputSampleRate:e,chunkTimeInSeconds:n}=Object.assign({outputSampleRate:16e3,chunkTimeInSeconds:1},t?.processorOptions);this.originalSampleRate=sampleRate,this.outputSampleRate=e,this.processChunkSize=function(t,e,n=1){const s=e*n,u=t/e;return Math.ceil(s*u)}(this.originalSampleRate,this.outputSampleRate,n)}process(t,e,n){const s=t[0][0];if(!s)return!0;const u=new Float32Array(s);if(this.inputBuffer.push(u),this.inputBufferLength+=s.length,this.inputBufferLength>=this.processChunkSize){const t=function(t){const e=t.reduce((t,e)=>t+e.length,0),n=new Float32Array(e);let s=0;for(const e of t)n.set(e,s),s+=e.length;return n}(this.inputBuffer);this.inputBuffer=[],this.inputBufferLength=0;const e=function(t,e,n){if(e===n)return t;const s=n/e,u=Math.ceil(t.length*s),i=new Float32Array(u);for(let e=0;e<u;e++){const n=e/s,u=Math.floor(n),o=n-u,r=t[u],a=t[Math.min(u+1,t.length-1)];i[e]=r+(a-r)*o}return i}(t,this.originalSampleRate,this.outputSampleRate);this.port.postMessage({float32:e,outputSampleRate:this.outputSampleRate},[e.buffer])}return!0}}registerProcessor("aiia-vad",e)}();
package/dist/browser.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("lodash-es"),t=require("moment");require("mathjs");var i,s=require("rxjs"),a=require("@mediapipe/tasks-vision"),n=require("socket.io-client");!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(i||(i={}));const o="undefined"!=typeof window;"undefined"!=typeof process&&null!=process.versions&&process.versions.node;class r{config;get webserver(){return this.config.webserver}get environment(){return this.config.env}get license(){return this.config.license??""}get port(){return function(e,t){const i=t??NaN;if(e){const t=parseInt(e);return isNaN(t)?i:t}return i}(process.env.PORT,this.config.port??3e3)}get ws_url(){return this.config.ws_url??""}get worklet_url(){return this.config.worklet_url??""}get project(){const{project:t}=e.assign({},this.config);return void 0===t?{specific:!1}:"string"==typeof t?{name:t,id:t,specific:!1}:e.assign({},t,{specific:!0})}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}return{api:`https://aiia-content-management-${e}-21193779403.asia-east1.run.app`,socket:`wss://graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`}}get debug(){return this.config.debug??!0}get mediaStream(){return this.config.mediaStream}get chunkTimeInSeconds(){return"number"==typeof this.config.chunkTimeInSeconds&&this.config.chunkTimeInSeconds>0?this.config.chunkTimeInSeconds:.3}get llmSampleRate(){return"number"==typeof this.config.llmSampleRate&&this.config.llmSampleRate>0?this.config.llmSampleRate:16e3}get faceDetection(){return e.assign({perSecond:3,confidence:80},this.config.faceDetection)}get autoClearSubtitle(){return{userDelayTime:this.config.autoClearUserSubtitle??5e3,aiiaDelayTime:this.config.autoClearAiiaSubtitle??NaN}}get eyeTrackEnable(){return this.config.eyeTrackEnable??!1}get eyeTrackCorrection(){return e.assign({x_axis_px:0,y_axis_px:0},this.config.correction)}constructor(e){this.config=e}}var c;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線連端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(c||(c={}));const u={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},h=[];const d=new Proxy(u,{get(i,s,a){if("string"==typeof s)switch(s){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...a)=>{const n=t().format("yyyy-MM-DD[T]HH:mm:ss");e.forEach(h,e=>{e.next(s,n,...a)}),i[s](`[Aiia::${s}_${n}_]`,...a)};default:return}},set:(e,t,i,s)=>!0});var l,b;function p(e,t,i){return Math.max(i,Math.min(e,t))}function m(e){const t=new DataView(e.buffer),i=[];for(let s=0;s<e.length;s++){const e=t.getInt16(2*s,!0)/32768;i.push(e)}return new Float32Array(i)}function g(e){const t=new DataView(e.buffer),i=[];for(let s=0;s<e.length;s++){const e=p(32768*t.getFloat32(4*s,!0),32767,-32768);i.push(e)}return i}!function(e){e[e.unknow=0]="unknow",e[e.allowed=1]="allowed",e[e.rejected=2]="rejected"}(l||(l={}));class f{audioContext;type="audio";currentNode;bufferQueue;gainNode;set volume(e){e<0&&(e=0),e>100&&(e=100),this.gainNode.gain.value=e/100}get volume(){return Math.round(100*this.gainNode.gain.value)}constructor(e){this.audioContext=e,this.bufferQueue=[],this.currentNode=null,this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination)}addBuffer(e){this.bufferQueue.push(e)}addBufferByFloat32(e,t=1){const i=e.length,s=this.audioContext.sampleRate,a=this.audioContext.createBuffer(t,i,s),n=e.slice();for(let e=0;e<t;e++)a.copyToChannel(n,e);this.addBuffer(a)}addBufferByInt16(e,t=1){this.addBufferByFloat32(m(e),t)}startSpeech(){null===this.currentNode&&this.continue()}pauseSpeech(){this.audioContext.suspend().catch(e=>{d.error("pause speech fail",e)})}resumeSpeech(){this.audioContext.resume().catch(e=>{d.error("resume speech fail",e)})}nextSpeech(){this.bufferQueue.length>0&&(this.clearNode(),this.continue())}stopSpeech(){this.bufferQueue=[],this.clearNode()}clearNode(){this.currentNode&&(this.currentNode.onended=function(){},this.currentNode.stop(),this.currentNode.disconnect(),this.currentNode=null)}continue(){const e=this.bufferQueue.shift();if(e){this.currentNode=this.audioContext.createBufferSource(),this.currentNode.buffer=e,this.currentNode.connect(this.gainNode);const t=this.continue.bind(this);this.currentNode.onended=t,this.currentNode.start()}else this.clearNode()}}class S{stream;audioContext;type="vad";workletNode;source;isRecording=!1;outputSampleRate;chunkTimeInSeconds;pcmSub;get pcm(){return this.pcmSub.asObservable()}constructor(e,t,i){this.stream=e,this.audioContext=t,this.outputSampleRate=i?.llmSampleRate??16e3,this.chunkTimeInSeconds=i?.chunkTimeInSeconds??1,this.pcmSub=new s.Subject}async startRecord(){this.isRecording||(this.source=this.audioContext.createMediaStreamSource(this.stream),this.workletNode=new AudioWorkletNode(this.audioContext,"aiia-vad",{processorOptions:{outputSampleRate:this.outputSampleRate,chunkTimeInSeconds:this.chunkTimeInSeconds}}),this.source.connect(this.workletNode),this.workletNode.port.onmessage=e=>{const{float32:t}=e.data;t&&this.pcmSub.next(new Float32Array(t))},this.isRecording=!0)}stopRecord(){this.isRecording&&(this.workletNode&&(this.workletNode.port.onmessage=null,this.workletNode.disconnect()),this.source&&this.source.disconnect(),this.isRecording=!1)}}class y{type="camera";aiiaCamera;get detections(){return this.aiiaCamera.detections}get videoSize(){return this.aiiaCamera.videoSize}constructor(t){const{perSecond:i}=e.assign({perSecond:3},t);this.aiiaCamera=new w,this.aiiaCamera.setFrequency(i),document.body.append(this.aiiaCamera)}async init(e){try{const t=await a.FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm"),i=await a.FaceDetector.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite",delegate:"GPU"},runningMode:"VIDEO"});this.aiiaCamera.setStream(e),this.aiiaCamera.setFaceDetector(i)}catch(e){d.error("Camera init fail",e)}}startCapture(){this.aiiaCamera.startCapture()}stopCapture(){this.aiiaCamera.stopCapture()}onDestroy(){this.aiiaCamera.remove()}}class w extends HTMLElement{static get observedAttributes(){return["debug"]}_shadow;_containerEl;_styleEl;_videoEl;_isDebug;_debugElements;_interval;_lastTimestamp;_loopFrameID;_faceDetector;_detectionsSub;_isReady;get detections(){return this._detectionsSub.asObservable()}get videoSize(){return{width:this._videoEl.videoWidth,height:this._videoEl.videoHeight}}constructor(){super(),this._isDebug=!1,this._isReady=!1,this._shadow=this.attachShadow({mode:"open"}),this._containerEl=document.createElement("div"),this._videoEl=document.createElement("video"),this._styleEl=document.createElement("style"),this._debugElements=[],this._loopFrameID=null,this._detectionsSub=new s.Subject,this._interval=Math.ceil(1e3/3),this._lastTimestamp=0,this.predictWebcam=this.predictWebcam.bind(this),this._settingContainer(),this._settingStyle(),this._settingVideo(),this._containerEl.appendChild(this._videoEl),this._shadow.appendChild(this._styleEl),this._shadow.appendChild(this._containerEl)}connectedCallback(){this.startCapture()}disconnectedCallback(){this.stopCapture()}attributeChangedCallback(e,t,i){"debug"===e&&(this._isDebug=null!==i&&("true"===i||""===i),this._containerEl.style.visibility=this._isDebug?"visible":"hidden")}setStream(e){this._videoEl.onloadedmetadata=()=>{this._isReady=!0},this._videoEl.srcObject=e}setFaceDetector(e){this._faceDetector=e}setFrequency(e){this._interval=Math.ceil(1e3/e)}_settingStyle(){this._styleEl.textContent="\n .aiia_container {\n visibility: hidden;\n position: fixed;\n top: 0;\n left: 0;\n }\n .aiia_container .highlighter {\n background: rgba(0, 255, 0, 0.25);\n border: 1px dashed #fff;\n z-index: 1;\n position: absolute;\n }\n .aiia_container .confidence {\n position: absolute;\n padding-bottom: 5px;\n padding-top: 5px;\n background-color: #007f8b;\n color: #fff;\n border: 1px dashed rgba(255, 255, 255, 0.7);\n z-index: 2;\n font-size: 12px;\n margin: 0;\n }\n "}_settingContainer(){this._containerEl.classList.add("aiia_container")}_settingVideo(){this._videoEl.autoplay=!0,this._videoEl.playsInline=!0,this._videoEl.muted=!0}getVideo(){return this._videoEl}startCapture(){null===this._loopFrameID&&(this._videoEl.play(),this._loopFrameID=window.requestAnimationFrame(this.predictWebcam))}stopCapture(){null!==this._loopFrameID&&(window.cancelAnimationFrame(this._loopFrameID),this._loopFrameID=null),this._videoEl.pause()}async predictWebcam(){if(this._loopFrameID=window.requestAnimationFrame(this.predictWebcam),void 0===this._faceDetector||!this._isReady)return;const e=performance.now();if(this._lastTimestamp+this._interval<=e){this._lastTimestamp=e;const t=this._faceDetector.detectForVideo(this._videoEl,e).detections;this._isDebug&&this._drawRect(t),this._detectionsSub.next(t)}}_drawRect(t){e.forEach(t,(e,t)=>{const{categories:i,boundingBox:s}=e;if(s){let e=this._debugElements[2*t],a=this._debugElements[2*t+1];e&&a||(e=document.createElement("div"),e.classList.add("highlighter"),a=document.createElement("p"),a.classList.add("confidence"),this._containerEl.append(a,e),this._debugElements.push(e,a)),e.style.display="block",a.style.display="block";const n=100*i[0].score;a.innerText=`Confidence: ${Math.round(n)} %`,a.style.right=this._videoEl.offsetWidth-s.width-s.originX+"px",a.style.top=s.originY-30+"px",a.style.width=s.width-10+"px",e.style.right=this._videoEl.offsetWidth-s.width-s.originX+"px",e.style.top=`${s.originY}px`,e.style.width=s.width-10+"px",e.style.height=`${s.height}px`}});for(let e=2*t.length;e<this._debugElements.length;e++)this._debugElements[e].style.display="none"}destroy(){this._detectionsSub.complete(),this.remove()}}customElements.define("aiia-camera",w),function(e){e[e.padding=0]="padding",e[e.rejected=1]="rejected",e[e.loadfail=2]="loadfail",e[e.allowed=3]="allowed"}(b||(b={}));class _{stream;_state;stateSub;audioCtx;audioManager;vadManager;camManager;destory;pcmSub;detectionsSub;get sampleRate(){return this.audioCtx?this.audioCtx.sampleRate:16e3}get pcm(){return this.pcmSub.asObservable()}get detections(){return this.detectionsSub.asObservable()}get state(){return this._state}set state(e){e!==this._state&&(this._state=e,this.stateSub.next(e))}get stateObs(){return this.stateSub.asObservable()}constructor(e){this.stream=e,this.stateSub=new s.Subject,this.pcmSub=new s.Subject,this.destory=new s.Subject,this.detectionsSub=new s.Subject,this.state=b.padding}async init(e,t){const a=this.stream??await async function(e){try{return await navigator.mediaDevices.getUserMedia(e)}catch(e){return null}}({audio:{echoCancellation:!0,noiseSuppression:!0},video:!0});if(null===a)throw this.state=b.rejected,new Error(i["未獲得媒體裝置權限"]);this.stream=a,this.audioCtx=new AudioContext;try{await this.audioCtx.audioWorklet.addModule(e)}catch(e){throw d.fatal(e),this.state=b.loadfail,new Error(i["Worklet模組載入失敗"])}this.state=b.allowed,this.audioManager=new f(this.audioCtx),this.vadManager=new S(a,this.audioCtx,t),this.vadManager.pcm.pipe(s.takeUntil(this.destory)).subscribe(e=>{this.pcmSub.next(e)});const n=new y(t?.faceDetection);n.detections.pipe(s.takeUntil(this.destory)).subscribe(e=>{const{width:t,height:i}=n.videoSize;this.detectionsSub.next({detections:e,clientWidth:t,clientHeight:i})}),n.init(a),this.camManager=n}addAudioQueue(t){const i=e.get(t,"buffer",void 0),s=e.get(t,"float32",void 0),a=e.get(t,"int16",void 0),n=e.get(t,"numberOfChannels",void 0);i?this.audioManager?.addBuffer(i):s?this.audioManager?.addBufferByFloat32(s,n):a&&this.audioManager?.addBufferByInt16(a,n)}playAudio(){this.audioManager?.startSpeech()}stopAudio(){this.audioManager?.stopSpeech()}startRecord(){this.vadManager?.startRecord()}stopRecord(){this.vadManager?.stopRecord()}setVolume(e){this.audioManager&&(this.audioManager.volume=e)}getVolume(){return this.audioManager?this.audioManager.volume:0}startCapture(){this.camManager?.startCapture()}stopCapture(){this.camManager?.stopCapture()}onDestroy(){this.camManager?.onDestroy(),this.audioCtx?.close(),this.destory.next(),this.destory.complete()}}class C{socket;messageSub;destorySub;get message(){return this.messageSub.asObservable()}constructor(t){this.destorySub=new s.Subject,this.messageSub=new s.Subject;const{specific:i,id:a}=t.project;this.socket=n.io(t.ws_url,e.assign({autoConnect:!1},i&&void 0!==a?{query:{uuid:a}}:{}))}start(){const e="list",t="cloud",i="channel";s.fromEvent(this.socket,"connect").pipe(s.takeUntil(this.destorySub)).subscribe(()=>{d.debug("Client ID: ",this.socket.id)}),s.fromEvent(this.socket,e).pipe(s.take(1),s.takeUntil(this.destorySub)).subscribe(t=>{this.messageSub.next({event:e,data:t})}),s.fromEvent(this.socket,t).pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this.messageSub.next({event:t,data:e})}),s.fromEvent(this.socket,i).pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this.messageSub.next({event:i,data:e})}),this.socket.connect()}send(e,t){this.socket.emit(e,t)}sendToCloud(e){this.send("cloud",JSON.stringify(e))}onDestroy(){this.destorySub.next(),this.destorySub.complete(),this.messageSub.complete(),this.socket.disconnect()}}class v{config;media;chat;destorySub;projectsSub;layoutSub;aiiaSubtitleSub;userSubtitleSub;sampleRateOfWhisper;channelState;stateSub;_currentState;isMuteAiia;aiiaSubtitleClearSub;userSubtitleClearSub;_lastProjects;guestSub;_hasGuest;get state(){return this.stateSub.asObservable()}get currentState(){return this._currentState}get layout(){return this.layoutSub.asObservable()}get projects(){return this.projectsSub.asObservable()}get lastProjects(){return e.map(this._lastProjects,e=>({...e}))}get aiiaSubtitle(){return this.aiiaSubtitleSub.asObservable()}get userSubtitle(){return this.userSubtitleSub.asObservable()}get guest(){return this.guestSub.asObservable()}get hasGuest(){return this._hasGuest}set volume(e){this.media.setVolume(e)}get volume(){return this.media.getVolume()}constructor(e,t){this.config=e,this.media=t,this.layoutSub=new s.Subject,this.projectsSub=new s.ReplaySubject(1),this.destorySub=new s.Subject,this.aiiaSubtitleSub=new s.Subject,this.userSubtitleSub=new s.Subject,this.aiiaSubtitleClearSub=new s.Subject,this.userSubtitleClearSub=new s.Subject,this.stateSub=new s.BehaviorSubject("NotStart"),this.sampleRateOfWhisper={input:e.llmSampleRate,output:16e3},this.channelState={proxy:!1,cloud:!1},this.isMuteAiia=!1,this._lastProjects=[],this.guestSub=new s.ReplaySubject(1),this._hasGuest=!1,this.chat=new C(e),d.code(c["指定依賴已完成"],"chat"),this.close=this.onDestroy,this.behavior=this.behavior.bind(this),this.cloudToObj=this.cloudToObj.bind(this);const{userDelayTime:i,aiiaDelayTime:a}=e.autoClearSubtitle;isNaN(a)||this.aiiaSubtitleClearSub.pipe(s.debounceTime(a),s.takeUntil(this.destorySub)).subscribe(()=>{this.aiiaSubtitleSub.next("")}),isNaN(i)||this.userSubtitleClearSub.pipe(s.debounceTime(i),s.takeUntil(this.destorySub)).subscribe(()=>{this.userSubtitleSub.next("")}),this.stateSub.pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this._currentState=e}),this.projectsSub.pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this._lastProjects=e}),this.guestSub.pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this._hasGuest=e})}start(){this.media.stateObs.pipe(s.takeUntil(this.destorySub)).subscribe(()=>{this.channelCheck()}),this.chat.message.pipe(s.takeUntil(this.destorySub)).subscribe(({event:t,data:i})=>{switch(t){case"cloud":this.cloudToObj(i);break;case"list":{const{id:t,name:s,specific:a}=this.config.project;let n=null;if(a&&void 0!==t)n=t;else{const a=e.find(i,e=>e.name===s||e.id===t);a&&(n=a.id)}null!==n?this.chooseProject(n):(this.projectsSub.next(i),this.stateSub.next("WattingProjectID"),d.code(c["請訂閱專案列表"]));break}case"channel":switch(i){case"open":this.channelState.proxy=!0,this.channelCheck();break;case"close":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService");break;case"no_permissions":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("NoPermissions");break;case"reconnect":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("ReconnectingCloud")}}}),this.chat.start(),this.stateSub.next("ConnectingCloud"),d.code(c["啟動SDK"])}close;skip(){this.chat.sendToCloud({request:"skip"})}reset(){this.chat.sendToCloud({request:"reset"})}chooseProject(e){this.chat.send("uuid",e),this.stateSub.next("ConnectingCloud")}mute(){this.isMuteAiia=!0,this.media.stopAudio(),this.media.stopRecord()}unmute(){this.isMuteAiia=!1,this.media.startRecord()}cloudToObj(t){try{const i=JSON.parse(t);void 0!==e.get(i,"signal")?this.behavior(i):e.isArray(i)?e.map(i,this.behavior):e.map(e.values(i),this.behavior)}catch(e){d.error("message transfer fail",e)}}behavior(t){switch(t.signal){case"layout":this.layoutSub.next(t.content);break;case"audio":if("sampleRate"===t.command)this.sampleRateOfWhisper.output=t.content;else switch(t.command){case"pcm":if(!this.isMuteAiia){const e=function(e,t,i){if(t===i)return e;const s=i/t,a=Math.ceil(e.length*s),n=new Float32Array(a);for(let t=0;t<a;t++){const i=t/s,a=Math.floor(i),o=i-a,r=e[a],c=e[Math.min(a+1,e.length-1)];n[t]=r+(c-r)*o}return n}(m(new Int16Array(t.content)),this.sampleRateOfWhisper.output,this.media.sampleRate);this.media.addAudioQueue({float32:e}),this.media.playAudio()}break;case"interrupted":this.media.stopAudio(),this.aiiaSubtitleSub.next("")}break;case"status":"Connected"===e.get(t,"content")&&(this.channelState.cloud=!0,this.channelCheck(),this.layoutSub.next({type:"resetLayout"}));break;case"agent":if(!this.isMuteAiia){const i=e.get(t,"content.function_result.response");void 0!==i&&(this.aiiaSubtitleSub.next(i),this.aiiaSubtitleClearSub.next())}break;case"asr":if(!this.isMuteAiia&&"ASR"===e.get(t,"content.function_type")){const i=e.get(t,"content.function_result.result");void 0!==i&&(this.userSubtitleSub.next(i),this.userSubtitleClearSub.next())}break;case"cv":{const i=e.get(t,"content.status");"user present"===i?this.guestSub.next(!0):"user leave"===i?this.guestSub.next(!1):d.warn("Unexpect value",e.get(t,"content"));break}default:d.debug(t.signal)}}channelCheck(){const{proxy:t,cloud:i}=this.channelState;if(t&&i&&this.media.state===b.allowed){this.chat.sendToCloud({request:"audio",command:"sampleRate",content:this.sampleRateOfWhisper.input}),this.media.pcm.pipe(s.takeUntil(this.destorySub)).subscribe(e=>{this.chat.sendToCloud({request:"audio",command:"pcm",content:g(e)})});const t=(a=this.config.faceDetection.confidence,function(t,i){const{width:s,height:n}=i,o=e.filter(t,e=>100*(e.categories[0]?.score??0)>=a),r=e.compact(e.map(o,e=>{if(void 0!==e.boundingBox){const t=e.boundingBox,i=t.width*t.height,a=e.categories[0]?.score??0;return{...t,area:i,score:100*a,clientWidth:s,clientHeight:n}}return null}));return e.sortBy(r,["area","score"])[0]??null});this.media.detections.pipe(s.takeUntil(this.destorySub),s.map(({detections:e,clientHeight:i,clientWidth:s})=>t(e,{width:s,height:i}))).subscribe(e=>{this.chat.send("face",e)}),this.media.startRecord(),this.media.startCapture(),this.stateSub.next("InService"),d.code(c["通道、雲端服務、麥克風都已就緒"])}else t&&i&&this.media.state!==b.allowed&&(this.stateSub.next("InServiceNoMedia"),d.code(c["未取得媒體裝置權限,請重新設定或忽略此訊息"]));var a}onDestroy(){this.chat.onDestroy(),this.media.onDestroy(),this.destorySub.next(),this.destorySub.complete(),this.stateSub.next("Destroy"),this.stateSub.complete(),d.code(c["結束SDK"])}}exports.initSdk=function(t){if(o){!function(e){const{info:t,error:i,warn:s,debug:a,fatal:n,code:o}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);o||(u.code=()=>{}),a||(u.debug=()=>{}),t||(u.info=()=>{}),s||(u.warn=()=>{}),i||(u.error=()=>{}),n||(u.fatal=()=>{})}(e.get(t,"debug",!0)),d.code(c["初始化必要依賴"]);const i=new r(e.assign({},t));d.code(c["指定依賴已完成"],"config");const s=new _(i.mediaStream);d.code(c["指定依賴已完成"],"config");const a=new v(i,s);return d.code(c["指定依賴已完成"],"sdk"),""!==i.worklet_url&&s.init(i.worklet_url,i),a}throw new Error(i["環境錯誤(browser)"])};
1
+ "use strict";var e=require("lodash-es"),t=require("moment");require("mathjs");var s,i=require("rxjs"),a=require("socket.io-client"),n=require("@mediapipe/tasks-vision");!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(s||(s={}));const o="undefined"!=typeof window;"undefined"!=typeof process&&null!=process.versions&&process.versions.node;class r{config;get webserver(){return this.safeRead("webserver")}get environment(){const e=this.safeRead("env");return"string"==typeof e?e:"developement"}get license(){return this.safeRead("license")??""}get port(){return function(e,t){const s="number"==typeof t?t:NaN;if(e){const t=Number(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.safeRead("port")??3e3)}get ws_url(){const e=this.safeRead("ws_url");return"string"==typeof e?e:""}get worklet_url(){const e=this.safeRead("worklet_url");return"string"==typeof e?e:""}get project(){const t=this.safeRead("project");if(void 0===t)return{specific:!1};if("string"==typeof t)return{name:t,id:t,specific:!1};{const s=e.get(t,"id",void 0),i=e.get(t,"name",void 0);return{id:"string"==typeof s?s:void 0,name:"string"==typeof i?i:void 0,specific:!0}}}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}const t=`graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`;return{api:`https://${t}`,socket:`wss://${t}`}}get debug(){return this.safeRead("debug")??!0}get mediaStream(){return this.safeRead("mediaStream")}get chunkTimeInSeconds(){const e=this.safeRead("chunkTimeInSeconds");return"number"==typeof e&&e>0?e:.3}get llmSampleRate(){const e=this.safeRead("llmSampleRate");return"number"==typeof e&&e>0?e:16e3}get detection(){return e.assign({perSecond:3,confidence:80},this.safeRead("faceDetection"),this.safeRead("cameraDetection"))}get autoClearSubtitle(){const e=this.safeRead("autoClearUserSubtitle"),t=this.safeRead("autoClearAiiaSubtitle");return{userDelayTime:"number"==typeof e?e:5e3,aiiaDelayTime:"number"==typeof t?t:NaN}}get eyeTrackEnable(){return Boolean(this.safeRead("eyeTrackEnable"))}get eyeTrackCorrection(){return e.assign({x_axis_px:0,y_axis_px:0},this.safeRead("correction"))}constructor(e){this.config=e}safeRead(t){return e.get(this.config,t,void 0)}}var c;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["已啟動SDK"]="CODE: 1014",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線雲端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(c||(c={}));const u={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},d=[];const h=new Proxy(u,{get(s,i,a){if("string"==typeof i)switch(i){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...a)=>{const n=t().format("yyyy-MM-DD[T]HH:mm:ss:sss");e.forEach(d,e=>{e.next(i,n,...a)}),s[i](`[Aiia::${i}_${n}_]`,...a)};default:return}},set:(e,t,s,i)=>!0});class l{socket;messageSub;destorySub;get message(){return this.messageSub.asObservable()}constructor(t){this.destorySub=new i.Subject,this.messageSub=new i.Subject;const{specific:s,id:n}=t.project;this.socket=a.io(t.ws_url,e.assign({autoConnect:!1},s&&void 0!==n?{query:{uuid:n}}:{}))}start(){const e="list",t="cloud",s="channel";i.fromEvent(this.socket,"connect").pipe(i.takeUntil(this.destorySub)).subscribe(()=>{h.debug("Client ID: ",this.socket.id)}),i.fromEvent(this.socket,e).pipe(i.take(1),i.takeUntil(this.destorySub)).subscribe(t=>{this.messageSub.next({event:e,data:t})}),i.fromEvent(this.socket,t).pipe(i.takeUntil(this.destorySub)).subscribe(e=>{this.messageSub.next({event:t,data:e})}),i.fromEvent(this.socket,s).pipe(i.takeUntil(this.destorySub)).subscribe(e=>{this.messageSub.next({event:s,data:e})}),this.socket.connect()}send(e,t){this.socket.emit(e,t)}sendToCloud(e){this.send("cloud",e)}onDestroy(){this.destorySub.next(),this.destorySub.complete(),this.messageSub.complete(),this.socket.disconnect()}}var b,p;function m(e,t,s){return Math.max(s,Math.min(e,t))}function g(e){const t=new DataView(e.buffer),s=[];for(let i=0;i<e.length;i++){const e=t.getInt16(2*i,!0)/32768;s.push(e)}return new Float32Array(s)}function S(e){const t=new DataView(e.buffer),s=[];for(let i=0;i<e.length;i++){const e=m(32768*t.getFloat32(4*i,!0),32767,-32768);s.push(e)}return s}!function(e){e[e.unknow=0]="unknow",e[e.allowed=1]="allowed",e[e.rejected=2]="rejected"}(b||(b={}));class f{audioContext;type="audio";currentNode;bufferQueue;gainNode;set volume(e){e<0&&(e=0),e>100&&(e=100),this.gainNode.gain.value=e/100}get volume(){return Math.round(100*this.gainNode.gain.value)}constructor(e){this.audioContext=e,this.bufferQueue=[],this.currentNode=null,this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination)}addBuffer(e){this.bufferQueue.push(e)}addBufferByFloat32(e,t=1){const s=e.length,i=this.audioContext.sampleRate,a=this.audioContext.createBuffer(t,s,i),n=e.slice();for(let e=0;e<t;e++)a.copyToChannel(n,e);this.addBuffer(a)}addBufferByInt16(e,t=1){this.addBufferByFloat32(g(e),t)}startSpeech(){null===this.currentNode&&this.continue()}pauseSpeech(){this.audioContext.suspend().catch(e=>{h.error("pause speech fail",e)})}resumeSpeech(){this.audioContext.resume().catch(e=>{h.error("resume speech fail",e)})}nextSpeech(){this.bufferQueue.length>0&&(this.clearNode(),this.continue())}stopSpeech(){this.bufferQueue=[],this.clearNode()}clearNode(){this.currentNode&&(this.currentNode.onended=function(){},this.currentNode.stop(),this.currentNode.disconnect(),this.currentNode=null)}continue(){const e=this.bufferQueue.shift();if(e){this.currentNode=this.audioContext.createBufferSource(),this.currentNode.buffer=e,this.currentNode.connect(this.gainNode);const t=this.continue.bind(this);this.currentNode.onended=t,this.currentNode.start()}else this.clearNode()}}class _{stream;audioContext;type="vad";workletNode;source;isRecording=!1;outputSampleRate;chunkTimeInSeconds;pcmSub;get pcm(){return this.pcmSub.asObservable()}constructor(e,t,s){this.stream=e,this.audioContext=t,this.outputSampleRate=s?.llmSampleRate??16e3,this.chunkTimeInSeconds=s?.chunkTimeInSeconds??1,this.pcmSub=new i.Subject}async startRecord(){this.isRecording||(this.source=this.audioContext.createMediaStreamSource(this.stream),this.workletNode=new AudioWorkletNode(this.audioContext,"aiia-vad",{processorOptions:{outputSampleRate:this.outputSampleRate,chunkTimeInSeconds:this.chunkTimeInSeconds}}),this.source.connect(this.workletNode),this.workletNode.port.onmessage=e=>{const{float32:t}=e.data;t&&this.pcmSub.next(new Float32Array(t))},this.isRecording=!0)}stopRecord(){this.isRecording&&(this.workletNode&&(this.workletNode.port.onmessage=null,this.workletNode.disconnect()),this.source&&this.source.disconnect(),this.isRecording=!1)}}class y{type="camera";aiiaCamera;get detections(){return this.aiiaCamera.detections}get landmarks(){return this.aiiaCamera.landmarks}get videoSize(){return this.aiiaCamera.videoSize}constructor(t){const{perSecond:s}=e.assign({perSecond:3},t);this.aiiaCamera=new C,this.aiiaCamera.setFrequency(s),document.body.append(this.aiiaCamera)}async init(e){try{const t=await n.FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm"),[s,i]=await Promise.all([n.FaceDetector.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite",delegate:"GPU"},runningMode:"VIDEO"}),n.PoseLandmarker.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task",delegate:"GPU"},runningMode:"VIDEO",numPoses:5})]);this.aiiaCamera.setStream(e),this.aiiaCamera.setFaceDetector(s),this.aiiaCamera.setPoseLandmarker(i)}catch(e){h.error("Camera init fail",e)}}startCapture(){this.aiiaCamera.startCapture()}stopCapture(){this.aiiaCamera.stopCapture()}onDestroy(){this.aiiaCamera.remove()}}class C extends HTMLElement{static get observedAttributes(){return["debug"]}_shadow;_containerEl;_styleEl;_videoEl;_canvasEl;_canvasCtx;_isDebug;_debugElements;_interval;_lastTimestamp;_loopFrameID;_faceDetector;_detectionsSub;_poseLandmarker;_landmarksSub;_isReady;_drawingUtils;landmarkColors=["gray","lightgray","gray","gray","lightgray"];connectorColors=["green","blue","red","yellow","purple"];get detections(){return this._detectionsSub.asObservable()}get landmarks(){return this._landmarksSub.asObservable()}get videoSize(){return{width:this._videoEl.videoWidth,height:this._videoEl.videoHeight}}constructor(){super(),this.id="AiiaCamera",this._isDebug=!1,this._isReady=!1,this._shadow=this.attachShadow({mode:"open"}),this._containerEl=document.createElement("div"),this._videoEl=document.createElement("video"),this._styleEl=document.createElement("style"),this._canvasEl=document.createElement("canvas"),this._debugElements=[],this._loopFrameID=null,this._detectionsSub=new i.Subject,this._landmarksSub=new i.Subject,this._interval=Math.ceil(1e3/3),this._lastTimestamp=0,this.predictWebcam=this.predictWebcam.bind(this),this._settingContainer(),this._settingStyle(),this._settingVideo(),this._containerEl.appendChild(this._videoEl),this._shadow.appendChild(this._styleEl),this._shadow.appendChild(this._containerEl),this._containerEl.appendChild(this._canvasEl);const e=this._canvasEl.getContext("2d");e?(this._canvasCtx=e,this._drawingUtils=new n.DrawingUtils(this._canvasCtx)):(this._canvasCtx=null,this._drawingUtils=null)}connectedCallback(){this.startCapture()}disconnectedCallback(){this.stopCapture()}attributeChangedCallback(e,t,s){"debug"===e&&(this._isDebug=null!==s&&("true"===s||""===s),this._containerEl.style.visibility=this._isDebug?"visible":"hidden")}setStream(e){this._videoEl.onloadedmetadata=()=>{this._isReady=!0},this._videoEl.srcObject=e}setFaceDetector(e){this._faceDetector=e}setPoseLandmarker(e){this._poseLandmarker=e}setFrequency(e){this._interval=Math.ceil(1e3/e)}_settingStyle(){this._styleEl.textContent="\n .aiia_container {\n visibility: hidden;\n position: fixed;\n top: 0;\n left: 0;\n\n > canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 640px;\n height: 480px;\n }\n }\n .aiia_container .highlighter {\n background: rgba(0, 255, 0, 0.25);\n border: 1px dashed #fff;\n z-index: 1;\n position: absolute;\n }\n .aiia_container .confidence {\n position: absolute;\n padding-bottom: 5px;\n padding-top: 5px;\n background-color: #007f8b;\n color: #fff;\n border: 1px dashed rgba(255, 255, 255, 0.7);\n z-index: 2;\n font-size: 12px;\n margin: 0;\n }\n "}_settingContainer(){this._containerEl.classList.add("aiia_container")}_settingVideo(){this._videoEl.autoplay=!0,this._videoEl.playsInline=!0,this._videoEl.muted=!0}getVideo(){return this._videoEl}startCapture(){null===this._loopFrameID&&(this._videoEl.play(),this._loopFrameID=window.requestAnimationFrame(this.predictWebcam))}stopCapture(){null!==this._loopFrameID&&(window.cancelAnimationFrame(this._loopFrameID),this._loopFrameID=null),this._videoEl.pause()}async predictWebcam(){if(this._isReady){const e=performance.now();if(this._lastTimestamp+this._interval<=e){this._lastTimestamp=e;const t=this._faceDetector?.detectForVideo(this._videoEl,e).detections,s=this._poseLandmarker?.detectForVideo(this._videoEl,e).landmarks;t&&(this._isDebug&&this._drawRect(t),this._detectionsSub.next(t)),s&&(this._isDebug&&this._drawRect2(s),this._landmarksSub.next(s))}}this._loopFrameID=window.requestAnimationFrame(this.predictWebcam)}_drawRect(t){e.forEach(t,(e,t)=>{const{categories:s,boundingBox:i}=e;if(i){let e=this._debugElements[2*t],a=this._debugElements[2*t+1];e&&a||(e=document.createElement("div"),e.classList.add("highlighter"),a=document.createElement("p"),a.classList.add("confidence"),this._containerEl.append(a,e),this._debugElements.push(e,a)),e.style.display="block",a.style.display="block";const n=100*s[0].score;a.innerText=`Confidence: ${Math.round(n)} %`,a.style.right=this._videoEl.offsetWidth-i.width-i.originX+"px",a.style.top=i.originY-30+"px",a.style.width=i.width-10+"px",e.style.right=this._videoEl.offsetWidth-i.width-i.originX+"px",e.style.top=`${i.originY}px`,e.style.width=i.width-10+"px",e.style.height=`${i.height}px`}});for(let e=2*t.length;e<this._debugElements.length;e++)this._debugElements[e].style.display="none"}_drawRect2(t){this._canvasCtx&&this._drawingUtils&&(this._canvasCtx.save(),this._canvasCtx.clearRect(0,0,640,480),e.forEach(t,(e,t)=>{this._canvasCtx?.save(),this._drawingUtils?.drawLandmarks(e,{radius:e=>n.DrawingUtils.lerp(e.from.z,-.15,.1,5,1),color:this.landmarkColors[t]}),this._drawingUtils?.drawConnectors(e,n.PoseLandmarker.POSE_CONNECTIONS,{color:this.connectorColors[t],lineWidth:2})}),this._canvasCtx.restore())}destroy(){this._detectionsSub.complete(),this.remove()}}customElements.define("aiia-camera",C),function(e){e[e.padding=0]="padding",e[e.rejected=1]="rejected",e[e.loadfail=2]="loadfail",e[e.allowed=3]="allowed"}(p||(p={}));class v{stream;_state;stateSub;audioCtx;audioManager;vadManager;camManager;destroy;pcmSub;detectionsSub;landmarksSub;get sampleRate(){return this.audioCtx?this.audioCtx.sampleRate:16e3}get pcm(){return this.pcmSub.asObservable()}get detections(){return this.detectionsSub.asObservable()}get landmarks(){return this.landmarksSub.asObservable()}get state(){return this._state}set state(e){e!==this._state&&(this._state=e,this.stateSub.next(e))}get stateObs(){return this.stateSub.asObservable()}constructor(e){this.stream=e,this.stateSub=new i.ReplaySubject(1),this.pcmSub=new i.Subject,this.destroy=new i.Subject,this.detectionsSub=new i.Subject,this.landmarksSub=new i.Subject,this.state=p.padding}async init(e,t){const a=this.stream??await async function(e){try{return await navigator.mediaDevices.getUserMedia(e)}catch(e){return null}}({audio:{echoCancellation:!0,noiseSuppression:!0},video:!0});if(null===a)throw this.state=p.rejected,new Error(s["未獲得媒體裝置權限"]);this.stream=a,this.audioCtx=new AudioContext;try{await this.audioCtx.audioWorklet.addModule(e)}catch(e){throw h.fatal(e),this.state=p.loadfail,new Error(s["Worklet模組載入失敗"])}this.state=p.allowed,this.audioManager=new f(this.audioCtx),this.vadManager=new _(a,this.audioCtx,t),this.vadManager.pcm.pipe(i.takeUntil(this.destroy)).subscribe(e=>{this.pcmSub.next(e)});const n=new y(t?.cameraDetection);n.detections.pipe(i.takeUntil(this.destroy)).subscribe(e=>{const{width:t,height:s}=n.videoSize;this.detectionsSub.next({detections:e,clientWidth:t,clientHeight:s})}),n.landmarks.pipe(i.takeUntil(this.destroy)).subscribe(e=>{this.landmarksSub.next(e)}),n.init(a),this.camManager=n}addAudioQueue(t){const s=e.get(t,"buffer",void 0),i=e.get(t,"float32",void 0),a=e.get(t,"int16",void 0),n=e.get(t,"numberOfChannels",void 0);s?this.audioManager?.addBuffer(s):i?this.audioManager?.addBufferByFloat32(i,n):a&&this.audioManager?.addBufferByInt16(a,n)}playAudio(){this.audioManager?.startSpeech()}stopAudio(){this.audioManager?.stopSpeech()}startRecord(){this.vadManager?.startRecord()}stopRecord(){this.vadManager?.stopRecord()}setVolume(e){this.audioManager&&(this.audioManager.volume=e)}getVolume(){return this.audioManager?this.audioManager.volume:0}startCapture(){this.camManager?.startCapture()}stopCapture(){this.camManager?.stopCapture()}onDestroy(){this.camManager?.onDestroy(),this.audioCtx?.close(),this.destroy.next(),this.destroy.complete()}}class w{config;media;chat;destroySub;projectsSub;layoutSub;aiiaSubtitleSub;userSubtitleSub;sampleRateOfWhisper;channelState;stateSub;_currentState;isMuteAiia;aiiaSubtitleClearSub;userSubtitleClearSub;_lastProjects;guestSub;_hasGuest;mediaEventReady;isStart;get state(){return this.stateSub.asObservable()}get currentState(){return this._currentState}get layout(){return this.layoutSub.asObservable()}get projects(){return this.projectsSub.asObservable()}get lastProjects(){return e.cloneDeep(this._lastProjects)}get aiiaSubtitle(){return this.aiiaSubtitleSub.asObservable()}get userSubtitle(){return this.userSubtitleSub.asObservable()}get guest(){return this.guestSub.asObservable()}get hasGuest(){return this._hasGuest}set volume(e){this.media.setVolume(e)}get volume(){return this.media.getVolume()}constructor(e,t){this.config=e,this.media=t,this.layoutSub=new i.Subject,this.projectsSub=new i.ReplaySubject(1),this.destroySub=new i.Subject,this.aiiaSubtitleSub=new i.Subject,this.userSubtitleSub=new i.Subject,this.aiiaSubtitleClearSub=new i.Subject,this.userSubtitleClearSub=new i.Subject,this.stateSub=new i.BehaviorSubject("NotStart"),this.sampleRateOfWhisper={input:e.llmSampleRate,output:16e3},this.channelState={proxy:!1,cloud:!1},this.isMuteAiia=!1,this._lastProjects=[],this.guestSub=new i.ReplaySubject(1),this._hasGuest=!1,this.mediaEventReady=!1,this.isStart=!1,this.chat=new l(e),h.code(c["指定依賴已完成"],"chat"),this.close=this.onDestroy,this.behavior=this.behavior.bind(this);const{userDelayTime:s,aiiaDelayTime:a}=e.autoClearSubtitle;isNaN(a)||this.aiiaSubtitleClearSub.pipe(i.debounceTime(a),i.takeUntil(this.destroySub)).subscribe(()=>{this.aiiaSubtitleSub.next("")}),isNaN(s)||this.userSubtitleClearSub.pipe(i.debounceTime(s),i.takeUntil(this.destroySub)).subscribe(()=>{this.userSubtitleSub.next("")}),this.stateSub.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{this._currentState=e}),this.projectsSub.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{this._lastProjects=e}),this.guestSub.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{this._hasGuest=e}),t.stateObs.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{p.allowed===e&&this.channelCheck()})}start(){this.isStart?h.code(c["已啟動SDK"]):(this.isStart=!0,this.chat.message.pipe(i.takeUntil(this.destroySub)).subscribe(({event:t,data:s})=>{switch(t){case"cloud":e.map(s,this.behavior);break;case"list":{const{id:t,name:i,specific:a}=this.config.project;let n=null;if(a&&void 0!==t)n=t;else{const a=e.find(s,e=>e.name===i||e.id===t);a&&(n=a.id)}null!==n?this.chooseProject(n):null===s?(this.stateSub.next("CloseService"),h.error("No project ready or lose network")):(this.projectsSub.next(s),this.stateSub.next("WaitingProjectID"),h.code(c["請訂閱專案列表"]));break}case"channel":switch(this.channelState.proxy=!0,s){case"open":this.channelCheck();break;case"close":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService");break;case"no_permissions":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("NoPermissions");break;case"reconnect":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("ReconnectingCloud");break;case"net_error":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService"),h.error("Please check network status")}}}),this.chat.start(),this.stateSub.next("ConnectingCloud"),h.code(c["啟動SDK"]))}close;skip(){this.chat.sendToCloud({request:"skip"})}reset(){this.chat.sendToCloud({request:"reset"})}chooseProject(e){this.chat.send("uuid",e),this.stateSub.next("ConnectingCloud")}mute(){this.isMuteAiia=!0,this.media.stopAudio(),this.media.stopRecord()}unmute(){this.isMuteAiia=!1,this.media.startRecord()}behavior(t){switch(t.signal){case"layout":this.layoutSub.next(t.content);break;case"audio":if("sampleRate"===t.command)this.sampleRateOfWhisper.output=t.content;else switch(t.command){case"pcm":if(!this.isMuteAiia){const e=function(e,t,s){if(t===s)return e;const i=s/t,a=Math.ceil(e.length*i),n=new Float32Array(a);for(let t=0;t<a;t++){const s=t/i,a=Math.floor(s),o=s-a,r=e[a],c=e[Math.min(a+1,e.length-1)];n[t]=r+(c-r)*o}return n}(g(new Int16Array(t.content)),this.sampleRateOfWhisper.output,this.media.sampleRate);this.media.addAudioQueue({float32:e}),this.media.playAudio()}break;case"interrupted":this.media.stopAudio(),this.aiiaSubtitleSub.next("")}break;case"status":"Connected"===e.get(t,"content")&&(this.channelState.cloud=!0,this.channelCheck(),this.layoutSub.next({type:"resetLayout"}));break;case"agent":if(!this.isMuteAiia){const s=e.get(t,"content.function_result.response");void 0!==s&&(this.aiiaSubtitleSub.next(s),this.aiiaSubtitleClearSub.next())}break;case"asr":if(!this.isMuteAiia&&"ASR"===e.get(t,"content.function_type")){const s=e.get(t,"content.function_result.result");void 0!==s&&(this.userSubtitleSub.next(s),this.userSubtitleClearSub.next())}break;case"cv":{const s=e.get(t,"content.status");"user present"===s?this.guestSub.next(!0):"user leave"===s?this.guestSub.next(!1):h.warn("Unexpect value",e.get(t,"content"));break}default:h.debug(t.signal)}}channelCheck(){const{proxy:t,cloud:s}=this.channelState;if(t&&s&&this.media.state===p.allowed){if(this.chat.sendToCloud({request:"audio",command:"sampleRate",content:this.sampleRateOfWhisper.input}),!this.mediaEventReady){this.mediaEventReady=!0,this.media.pcm.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{this.chat.sendToCloud({request:"audio",command:"pcm",content:S(e)})});const t=(a=this.config.detection.confidence,function(t,s){const{width:i,height:n}=s,o=e.filter(t,e=>100*(e.categories[0]?.score??0)>=a),r=e.compact(e.map(o,e=>{if(void 0!==e.boundingBox){const t=e.boundingBox,s=t.width*t.height,a=e.categories[0]?.score??0;return{...t,area:s,score:100*a,clientWidth:i,clientHeight:n}}return null}));return e.sortBy(r,["area","score"])[0]??null});this.media.detections.pipe(i.takeUntil(this.destroySub),i.map(({detections:e,clientHeight:s,clientWidth:i})=>t(e,{width:i,height:s}))).subscribe(e=>{this.chat.send("face",e)}),this.media.landmarks.pipe(i.takeUntil(this.destroySub)).subscribe(e=>{this.chat.sendToCloud({request:"pose_detect",content:e})})}this.media.startRecord(),this.media.startCapture(),this.stateSub.next("InService"),h.code(c["通道、雲端服務、麥克風都已就緒"])}else t&&s&&this.media.state!==p.allowed&&(this.stateSub.next("InServiceNoMedia"),h.code(c["未取得媒體裝置權限,請重新設定或忽略此訊息"]));var a}onDestroy(){this.chat.onDestroy(),this.media.onDestroy(),this.destroySub.next(),this.destroySub.complete(),this.stateSub.next("Destroy"),this.stateSub.complete(),h.code(c["結束SDK"])}}exports.initSdk=function(t){if(o){!function(e){const{info:t,error:s,warn:i,debug:a,fatal:n,code:o}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);o||(u.code=()=>{}),a||(u.debug=()=>{}),t||(u.info=()=>{}),i||(u.warn=()=>{}),s||(u.error=()=>{}),n||(u.fatal=()=>{})}(e.get(t,"debug",!0)),h.code(c["初始化必要依賴"]);const s=new r(e.assign({},t));h.code(c["指定依賴已完成"],"config");const i=new v(s.mediaStream);h.code(c["指定依賴已完成"],"config");const a=new w(s,i);return h.code(c["指定依賴已完成"],"sdk"),""!==s.worklet_url&&i.init(s.worklet_url,s),a}throw new Error(s["環境錯誤(browser)"])};
package/dist/browser.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Detection } from '@mediapipe/tasks-vision';
2
2
  import { Http2SecureServer } from 'http2';
3
3
  import { Http2Server } from 'http2';
4
+ import type { Landmark } from '@mediapipe/tasks-vision';
4
5
  import { Observable } from 'rxjs';
5
6
  import { Server } from 'http';
6
7
  import { Server as Server_2 } from 'https';
@@ -52,9 +53,10 @@ export declare namespace aiia_msg {
52
53
  OLD_AGENT,
53
54
  QUESTIONNAIRE,
54
55
  LayoutID,
55
- LAYOUT,
56
+ LAYOUT_2 as LAYOUT,
56
57
  AUDIO,
57
58
  CV,
59
+ MovementToUE,
58
60
  CLOUD_SOCKET_MSG,
59
61
  Signal,
60
62
  SendMessage_MiniGame,
@@ -64,6 +66,8 @@ export declare namespace aiia_msg {
64
66
  SendMessage_Audio,
65
67
  SendMessage_Origin,
66
68
  SendMessage_Face,
69
+ LandmarkResult,
70
+ SendMessage_Pose,
67
71
  SendMessage
68
72
  }
69
73
  }
@@ -71,14 +75,14 @@ export declare namespace aiia_msg {
71
75
  declare class AiiaConfig {
72
76
  private config;
73
77
  get webserver(): (Server<any, any> | Server_2<any, any> | Http2SecureServer<any, any, any, any> | Http2Server<any, any, any, any>) | undefined;
74
- get environment(): "developement" | "production" | undefined;
78
+ get environment(): string;
75
79
  get license(): string;
76
80
  get port(): number;
77
81
  get ws_url(): string;
78
82
  get worklet_url(): string;
79
- get project(): Partial<AiiaProjectItem> & {
83
+ get project(): {
80
84
  specific: boolean;
81
- };
85
+ } & Partial<AiiaProjectItem>;
82
86
  get endPoint(): {
83
87
  api: string;
84
88
  socket: string;
@@ -87,7 +91,7 @@ declare class AiiaConfig {
87
91
  get mediaStream(): MediaStream | undefined;
88
92
  get chunkTimeInSeconds(): number;
89
93
  get llmSampleRate(): number;
90
- get faceDetection(): {
94
+ get detection(): {
91
95
  perSecond: number;
92
96
  confidence: number;
93
97
  };
@@ -101,18 +105,26 @@ declare class AiiaConfig {
101
105
  y_axis_px: number;
102
106
  };
103
107
  constructor(config: InitOptions);
108
+ private safeRead;
104
109
  }
105
110
 
106
- export declare interface AiiaProjectItem {
111
+ declare interface AiiaProjectItem {
107
112
  id: string;
108
113
  name: string;
109
114
  }
110
115
 
116
+ export declare interface AiiaProjectItemWithAvatar extends AiiaProjectItem {
117
+ avatar: {
118
+ name: string;
119
+ url: string;
120
+ };
121
+ }
122
+
111
123
  declare class AiiaSdk {
112
124
  private config;
113
125
  private media;
114
126
  private chat;
115
- private destorySub;
127
+ private destroySub;
116
128
  private projectsSub;
117
129
  private layoutSub;
118
130
  private aiiaSubtitleSub;
@@ -127,6 +139,8 @@ declare class AiiaSdk {
127
139
  private _lastProjects;
128
140
  private guestSub;
129
141
  private _hasGuest;
142
+ private mediaEventReady;
143
+ private isStart;
130
144
  /**
131
145
  * [zh]
132
146
  * 可訂閱,服務狀態
@@ -150,23 +164,7 @@ declare class AiiaSdk {
150
164
  * [en]
151
165
  * Subscribable, Control layout
152
166
  */
153
- get layout(): Observable< {
154
- type: "mount";
155
- id: aiia_msg.LayoutID;
156
- panel: string;
157
- props?: Record<string, unknown>;
158
- zIndex?: number;
159
- } | {
160
- type: "update";
161
- id: aiia_msg.LayoutID;
162
- props?: Record<string, unknown>;
163
- zIndex?: number;
164
- } | {
165
- type: "unmount";
166
- id: aiia_msg.LayoutID;
167
- } | {
168
- type: "resetLayout";
169
- }>;
167
+ get layout(): Observable<LAYOUT>;
170
168
  /**
171
169
  * [zh]
172
170
  * 若沒有明確指定 Project ID 時,請訂閱此物件
@@ -174,7 +172,7 @@ declare class AiiaSdk {
174
172
  * [en]
175
173
  * If no Project ID is explicitly specified, please subscribe to this object
176
174
  */
177
- get projects(): Observable<AiiaProjectItem[]>;
175
+ get projects(): Observable<AiiaProjectItemWithAvatar[]>;
178
176
  /**
179
177
  * [zh]
180
178
  * 可讀取最近一次收到的專案列表
@@ -182,10 +180,7 @@ declare class AiiaSdk {
182
180
  * [en]
183
181
  * You can read the list of projects received recently
184
182
  */
185
- get lastProjects(): {
186
- id: string;
187
- name: string;
188
- }[];
183
+ get lastProjects(): AiiaProjectItemWithAvatar[];
189
184
  /**
190
185
  * [zh]
191
186
  * 可訂閱,Aiia 的回應字幕
@@ -276,7 +271,7 @@ declare class AiiaSdk {
276
271
  * If no Project is explicitly specified, please execute this method.
277
272
  * @param project_id
278
273
  */
279
- chooseProject(project_id: AiiaProjectItem["id"]): void;
274
+ chooseProject(project_id: AiiaProjectItemWithAvatar["id"]): void;
280
275
  /**
281
276
  * [zh]
282
277
  * 不同於`this.volume = 0`的調整輸出音量
@@ -297,7 +292,6 @@ declare class AiiaSdk {
297
292
  * Restore audio signal data
298
293
  */
299
294
  unmute(): void;
300
- private cloudToObj;
301
295
  private behavior;
302
296
  private channelCheck;
303
297
  /**
@@ -310,7 +304,7 @@ declare class AiiaSdk {
310
304
  onDestroy(): void;
311
305
  }
312
306
 
313
- export declare type AiiaState = "NotStart" | "WattingProjectID" | "ConnectingCloud" | "ReconnectingCloud" | "InService" | "InServiceNoMedia" | "NoPermissions" | "CloseService" | "Destroy";
307
+ export declare type AiiaState = "NotStart" | "WaitingProjectID" | "ConnectingCloud" | "ReconnectingCloud" | "InService" | "InServiceNoMedia" | "NoPermissions" | "CloseService" | "Destroy";
314
308
 
315
309
  declare interface ASR {
316
310
  signal: "asr";
@@ -347,7 +341,7 @@ declare type AUDIO = {
347
341
 
348
342
  export declare type BrowserInitOptions = Omit<InitOptions, "webserver" | "port" | "license" | "eyeTrackEnable" | "correction">;
349
343
 
350
- declare type CLOUD_SOCKET_MSG = TTS | MIC | ASR | AGENT | OLD_ASR | OLD_AGENT | RATINGS | QUESTIONNAIRE | LAYOUT | AUDIO | CV;
344
+ declare type CLOUD_SOCKET_MSG = TTS | MIC | ASR | AGENT | OLD_ASR | OLD_AGENT | RATINGS | QUESTIONNAIRE | LAYOUT_2 | AUDIO | CV | MovementToUE;
351
345
 
352
346
  declare interface CV {
353
347
  signal: "cv";
@@ -375,10 +369,10 @@ declare interface InitOptions {
375
369
  project?: string | Partial<AiiaProjectItem>;
376
370
  /**
377
371
  * [zh]
378
- * 指定的環境,預設值: production
372
+ * 指定的環境,預設值: developement
379
373
  *
380
374
  * [en]
381
- * Specific Env, default: production
375
+ * Specific Env, default: developement
382
376
  */
383
377
  env?: "developement" | "production";
384
378
  /**
@@ -450,15 +444,17 @@ declare interface InitOptions {
450
444
  */
451
445
  llmSampleRate?: number;
452
446
  /**
447
+ * @deprecated
448
+ *
453
449
  * [zh]
454
- * 人臉辨識
450
+ * 人臉辨識(已棄用,請改用 `cameraDetection`),同時設定會以 `cameraDetection` 為主
455
451
  *
456
452
  * @property perSecond: 每秒檢測幾次, 預設: 每秒 3 次
457
453
  *
458
454
  * @property confidence: 分數(0 ~ 100), 預設: 80
459
455
  *
460
456
  * [en]
461
- * facial recognition
457
+ * Face recognition (deprecated, please use `cameraDetection` instead), and the settings will be based on `cameraDetection`
462
458
  *
463
459
  * @property perSecond: Detection times per second, default: 3 times per second
464
460
  *
@@ -468,6 +464,25 @@ declare interface InitOptions {
468
464
  perSecond?: number;
469
465
  confidence?: number;
470
466
  };
467
+ /**
468
+ * [zh]
469
+ * 攝影機辨識設定
470
+ *
471
+ * @property perSecond: 每秒檢測幾次, 預設: 每秒 3 次
472
+ *
473
+ * @property confidence: 人臉辨識分數(0 ~ 100), 預設: 80
474
+ *
475
+ * [en]
476
+ * Camera Identification Settings
477
+ *
478
+ * @property perSecond: Detection times per second, default: 3 times per second
479
+ *
480
+ * @property confidence: Face recognition score(0 ~ 100), default: 80
481
+ */
482
+ cameraDetection?: {
483
+ perSecond?: number;
484
+ confidence?: number;
485
+ };
471
486
  /**
472
487
  * [zh]
473
488
  * 清除字幕,可指定時間(毫秒),預設 5000 毫秒後清除
@@ -507,7 +522,13 @@ declare interface InitOptions {
507
522
 
508
523
  export declare function initSdk(options?: BrowserInitOptions): AiiaSdk;
509
524
 
510
- declare interface LAYOUT {
525
+ declare type LandmarkResult = Landmark[][];
526
+
527
+ export declare type LAYOUT = aiia_msg.LAYOUT["content"] | {
528
+ type: "resetLayout";
529
+ };
530
+
531
+ declare interface LAYOUT_2 {
511
532
  signal: "layout";
512
533
  content: {
513
534
  type: "mount";
@@ -538,9 +559,10 @@ declare class MediaManager {
538
559
  private audioManager?;
539
560
  private vadManager?;
540
561
  private camManager?;
541
- private destory;
562
+ private destroy;
542
563
  private pcmSub;
543
564
  private detectionsSub;
565
+ private landmarksSub;
544
566
  get sampleRate(): number;
545
567
  get pcm(): Observable<Float32Array<ArrayBufferLike>>;
546
568
  get detections(): Observable< {
@@ -548,6 +570,7 @@ declare class MediaManager {
548
570
  clientWidth: number;
549
571
  clientHeight: number;
550
572
  }>;
573
+ get landmarks(): Observable<aiia_msg.LandmarkResult>;
551
574
  get state(): MediaStateEnum;
552
575
  set state(v: MediaStateEnum);
553
576
  get stateObs(): Observable<MediaStateEnum>;
@@ -555,7 +578,7 @@ declare class MediaManager {
555
578
  init(workletUrl: string, config?: {
556
579
  llmSampleRate?: AiiaConfig["llmSampleRate"];
557
580
  chunkTimeInSeconds?: AiiaConfig["chunkTimeInSeconds"];
558
- faceDetection?: AiiaConfig["faceDetection"];
581
+ cameraDetection?: AiiaConfig["detection"];
559
582
  }): Promise<void>;
560
583
  addAudioQueue(input: {
561
584
  buffer: AudioBuffer;
@@ -591,6 +614,15 @@ declare interface MIC {
591
614
  };
592
615
  }
593
616
 
617
+ declare interface MovementToUE {
618
+ signal: "osc";
619
+ content: {
620
+ host: string;
621
+ port: number;
622
+ message: [string, ...(string | number)[]];
623
+ };
624
+ }
625
+
594
626
  /** @deprecated */
595
627
  declare interface OLD_AGENT {
596
628
  signal: "response";
@@ -643,7 +675,7 @@ declare type SendMessage = {
643
675
  request: "reset";
644
676
  } | {
645
677
  request: "skip";
646
- } | SendMessage_Layout | SendMessage_MiniGame | SendMessage_Audio | SendMessage_Face;
678
+ } | SendMessage_Layout | SendMessage_MiniGame | SendMessage_Audio | SendMessage_Face | SendMessage_Pose;
647
679
 
648
680
  declare type SendMessage_Audio = SendMessage_Audio_Init | SendMessage_Audio_PCM;
649
681
 
@@ -691,6 +723,11 @@ declare interface SendMessage_Origin {
691
723
  content?: any;
692
724
  }
693
725
 
726
+ declare interface SendMessage_Pose {
727
+ request: "pose_detect";
728
+ content: LandmarkResult;
729
+ }
730
+
694
731
  declare type Signal = CLOUD_SOCKET_MSG["signal"];
695
732
 
696
733
  declare interface TTS {
package/dist/browser.mjs CHANGED
@@ -1 +1 @@
1
- import{assign as e,forEach as t,filter as i,compact as s,map as a,sortBy as o,get as n,find as r,isArray as c,values as u}from"lodash-es";import h from"moment";import"mathjs";import{Subject as d,takeUntil as l,fromEvent as p,take as b,ReplaySubject as m,BehaviorSubject as g,debounceTime as f,map as S}from"rxjs";import{FilesetResolver as y,FaceDetector as w}from"@mediapipe/tasks-vision";import{io as _}from"socket.io-client";var C;!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(C||(C={}));const v="undefined"!=typeof window;"undefined"!=typeof process&&null!=process.versions&&process.versions.node;class x{config;get webserver(){return this.config.webserver}get environment(){return this.config.env}get license(){return this.config.license??""}get port(){return function(e,t){const i=t??NaN;if(e){const t=parseInt(e);return isNaN(t)?i:t}return i}(process.env.PORT,this.config.port??3e3)}get ws_url(){return this.config.ws_url??""}get worklet_url(){return this.config.worklet_url??""}get project(){const{project:t}=e({},this.config);return void 0===t?{specific:!1}:"string"==typeof t?{name:t,id:t,specific:!1}:e({},t,{specific:!0})}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}return{api:`https://aiia-content-management-${e}-21193779403.asia-east1.run.app`,socket:`wss://graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`}}get debug(){return this.config.debug??!0}get mediaStream(){return this.config.mediaStream}get chunkTimeInSeconds(){return"number"==typeof this.config.chunkTimeInSeconds&&this.config.chunkTimeInSeconds>0?this.config.chunkTimeInSeconds:.3}get llmSampleRate(){return"number"==typeof this.config.llmSampleRate&&this.config.llmSampleRate>0?this.config.llmSampleRate:16e3}get faceDetection(){return e({perSecond:3,confidence:80},this.config.faceDetection)}get autoClearSubtitle(){return{userDelayTime:this.config.autoClearUserSubtitle??5e3,aiiaDelayTime:this.config.autoClearAiiaSubtitle??NaN}}get eyeTrackEnable(){return this.config.eyeTrackEnable??!1}get eyeTrackCorrection(){return e({x_axis_px:0,y_axis_px:0},this.config.correction)}constructor(e){this.config=e}}var E;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線連端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(E||(E={}));const R={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},D=[];const k=new Proxy(R,{get(e,i,s){if("string"==typeof i)switch(i){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...s)=>{const a=h().format("yyyy-MM-DD[T]HH:mm:ss");t(D,e=>{e.next(i,a,...s)}),e[i](`[Aiia::${i}_${a}_]`,...s)};default:return}},set:(e,t,i,s)=>!0});var O,N;function M(e,t,i){return Math.max(i,Math.min(e,t))}function T(e){const t=new DataView(e.buffer),i=[];for(let s=0;s<e.length;s++){const e=t.getInt16(2*s,!0)/32768;i.push(e)}return new Float32Array(i)}function j(e){const t=new DataView(e.buffer),i=[];for(let s=0;s<e.length;s++){const e=M(32768*t.getFloat32(4*s,!0),32767,-32768);i.push(e)}return i}!function(e){e[e.unknow=0]="unknow",e[e.allowed=1]="allowed",e[e.rejected=2]="rejected"}(O||(O={}));class A{audioContext;type="audio";currentNode;bufferQueue;gainNode;set volume(e){e<0&&(e=0),e>100&&(e=100),this.gainNode.gain.value=e/100}get volume(){return Math.round(100*this.gainNode.gain.value)}constructor(e){this.audioContext=e,this.bufferQueue=[],this.currentNode=null,this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination)}addBuffer(e){this.bufferQueue.push(e)}addBufferByFloat32(e,t=1){const i=e.length,s=this.audioContext.sampleRate,a=this.audioContext.createBuffer(t,i,s),o=e.slice();for(let e=0;e<t;e++)a.copyToChannel(o,e);this.addBuffer(a)}addBufferByInt16(e,t=1){this.addBufferByFloat32(T(e),t)}startSpeech(){null===this.currentNode&&this.continue()}pauseSpeech(){this.audioContext.suspend().catch(e=>{k.error("pause speech fail",e)})}resumeSpeech(){this.audioContext.resume().catch(e=>{k.error("resume speech fail",e)})}nextSpeech(){this.bufferQueue.length>0&&(this.clearNode(),this.continue())}stopSpeech(){this.bufferQueue=[],this.clearNode()}clearNode(){this.currentNode&&(this.currentNode.onended=function(){},this.currentNode.stop(),this.currentNode.disconnect(),this.currentNode=null)}continue(){const e=this.bufferQueue.shift();if(e){this.currentNode=this.audioContext.createBufferSource(),this.currentNode.buffer=e,this.currentNode.connect(this.gainNode);const t=this.continue.bind(this);this.currentNode.onended=t,this.currentNode.start()}else this.clearNode()}}class I{stream;audioContext;type="vad";workletNode;source;isRecording=!1;outputSampleRate;chunkTimeInSeconds;pcmSub;get pcm(){return this.pcmSub.asObservable()}constructor(e,t,i){this.stream=e,this.audioContext=t,this.outputSampleRate=i?.llmSampleRate??16e3,this.chunkTimeInSeconds=i?.chunkTimeInSeconds??1,this.pcmSub=new d}async startRecord(){this.isRecording||(this.source=this.audioContext.createMediaStreamSource(this.stream),this.workletNode=new AudioWorkletNode(this.audioContext,"aiia-vad",{processorOptions:{outputSampleRate:this.outputSampleRate,chunkTimeInSeconds:this.chunkTimeInSeconds}}),this.source.connect(this.workletNode),this.workletNode.port.onmessage=e=>{const{float32:t}=e.data;t&&this.pcmSub.next(new Float32Array(t))},this.isRecording=!0)}stopRecord(){this.isRecording&&(this.workletNode&&(this.workletNode.port.onmessage=null,this.workletNode.disconnect()),this.source&&this.source.disconnect(),this.isRecording=!1)}}class F{type="camera";aiiaCamera;get detections(){return this.aiiaCamera.detections}get videoSize(){return this.aiiaCamera.videoSize}constructor(t){const{perSecond:i}=e({perSecond:3},t);this.aiiaCamera=new W,this.aiiaCamera.setFrequency(i),document.body.append(this.aiiaCamera)}async init(e){try{const t=await y.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm"),i=await w.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite",delegate:"GPU"},runningMode:"VIDEO"});this.aiiaCamera.setStream(e),this.aiiaCamera.setFaceDetector(i)}catch(e){k.error("Camera init fail",e)}}startCapture(){this.aiiaCamera.startCapture()}stopCapture(){this.aiiaCamera.stopCapture()}onDestroy(){this.aiiaCamera.remove()}}class W extends HTMLElement{static get observedAttributes(){return["debug"]}_shadow;_containerEl;_styleEl;_videoEl;_isDebug;_debugElements;_interval;_lastTimestamp;_loopFrameID;_faceDetector;_detectionsSub;_isReady;get detections(){return this._detectionsSub.asObservable()}get videoSize(){return{width:this._videoEl.videoWidth,height:this._videoEl.videoHeight}}constructor(){super(),this._isDebug=!1,this._isReady=!1,this._shadow=this.attachShadow({mode:"open"}),this._containerEl=document.createElement("div"),this._videoEl=document.createElement("video"),this._styleEl=document.createElement("style"),this._debugElements=[],this._loopFrameID=null,this._detectionsSub=new d,this._interval=Math.ceil(1e3/3),this._lastTimestamp=0,this.predictWebcam=this.predictWebcam.bind(this),this._settingContainer(),this._settingStyle(),this._settingVideo(),this._containerEl.appendChild(this._videoEl),this._shadow.appendChild(this._styleEl),this._shadow.appendChild(this._containerEl)}connectedCallback(){this.startCapture()}disconnectedCallback(){this.stopCapture()}attributeChangedCallback(e,t,i){"debug"===e&&(this._isDebug=null!==i&&("true"===i||""===i),this._containerEl.style.visibility=this._isDebug?"visible":"hidden")}setStream(e){this._videoEl.onloadedmetadata=()=>{this._isReady=!0},this._videoEl.srcObject=e}setFaceDetector(e){this._faceDetector=e}setFrequency(e){this._interval=Math.ceil(1e3/e)}_settingStyle(){this._styleEl.textContent="\n .aiia_container {\n visibility: hidden;\n position: fixed;\n top: 0;\n left: 0;\n }\n .aiia_container .highlighter {\n background: rgba(0, 255, 0, 0.25);\n border: 1px dashed #fff;\n z-index: 1;\n position: absolute;\n }\n .aiia_container .confidence {\n position: absolute;\n padding-bottom: 5px;\n padding-top: 5px;\n background-color: #007f8b;\n color: #fff;\n border: 1px dashed rgba(255, 255, 255, 0.7);\n z-index: 2;\n font-size: 12px;\n margin: 0;\n }\n "}_settingContainer(){this._containerEl.classList.add("aiia_container")}_settingVideo(){this._videoEl.autoplay=!0,this._videoEl.playsInline=!0,this._videoEl.muted=!0}getVideo(){return this._videoEl}startCapture(){null===this._loopFrameID&&(this._videoEl.play(),this._loopFrameID=window.requestAnimationFrame(this.predictWebcam))}stopCapture(){null!==this._loopFrameID&&(window.cancelAnimationFrame(this._loopFrameID),this._loopFrameID=null),this._videoEl.pause()}async predictWebcam(){if(this._loopFrameID=window.requestAnimationFrame(this.predictWebcam),void 0===this._faceDetector||!this._isReady)return;const e=performance.now();if(this._lastTimestamp+this._interval<=e){this._lastTimestamp=e;const t=this._faceDetector.detectForVideo(this._videoEl,e).detections;this._isDebug&&this._drawRect(t),this._detectionsSub.next(t)}}_drawRect(e){t(e,(e,t)=>{const{categories:i,boundingBox:s}=e;if(s){let e=this._debugElements[2*t],a=this._debugElements[2*t+1];e&&a||(e=document.createElement("div"),e.classList.add("highlighter"),a=document.createElement("p"),a.classList.add("confidence"),this._containerEl.append(a,e),this._debugElements.push(e,a)),e.style.display="block",a.style.display="block";const o=100*i[0].score;a.innerText=`Confidence: ${Math.round(o)} %`,a.style.right=this._videoEl.offsetWidth-s.width-s.originX+"px",a.style.top=s.originY-30+"px",a.style.width=s.width-10+"px",e.style.right=this._videoEl.offsetWidth-s.width-s.originX+"px",e.style.top=`${s.originY}px`,e.style.width=s.width-10+"px",e.style.height=`${s.height}px`}});for(let t=2*e.length;t<this._debugElements.length;t++)this._debugElements[t].style.display="none"}destroy(){this._detectionsSub.complete(),this.remove()}}customElements.define("aiia-camera",W),function(e){e[e.padding=0]="padding",e[e.rejected=1]="rejected",e[e.loadfail=2]="loadfail",e[e.allowed=3]="allowed"}(N||(N={}));class B{stream;_state;stateSub;audioCtx;audioManager;vadManager;camManager;destory;pcmSub;detectionsSub;get sampleRate(){return this.audioCtx?this.audioCtx.sampleRate:16e3}get pcm(){return this.pcmSub.asObservable()}get detections(){return this.detectionsSub.asObservable()}get state(){return this._state}set state(e){e!==this._state&&(this._state=e,this.stateSub.next(e))}get stateObs(){return this.stateSub.asObservable()}constructor(e){this.stream=e,this.stateSub=new d,this.pcmSub=new d,this.destory=new d,this.detectionsSub=new d,this.state=N.padding}async init(e,t){const i=this.stream??await async function(e){try{return await navigator.mediaDevices.getUserMedia(e)}catch(e){return null}}({audio:{echoCancellation:!0,noiseSuppression:!0},video:!0});if(null===i)throw this.state=N.rejected,new Error(C["未獲得媒體裝置權限"]);this.stream=i,this.audioCtx=new AudioContext;try{await this.audioCtx.audioWorklet.addModule(e)}catch(e){throw k.fatal(e),this.state=N.loadfail,new Error(C["Worklet模組載入失敗"])}this.state=N.allowed,this.audioManager=new A(this.audioCtx),this.vadManager=new I(i,this.audioCtx,t),this.vadManager.pcm.pipe(l(this.destory)).subscribe(e=>{this.pcmSub.next(e)});const s=new F(t?.faceDetection);s.detections.pipe(l(this.destory)).subscribe(e=>{const{width:t,height:i}=s.videoSize;this.detectionsSub.next({detections:e,clientWidth:t,clientHeight:i})}),s.init(i),this.camManager=s}addAudioQueue(e){const t=n(e,"buffer",void 0),i=n(e,"float32",void 0),s=n(e,"int16",void 0),a=n(e,"numberOfChannels",void 0);t?this.audioManager?.addBuffer(t):i?this.audioManager?.addBufferByFloat32(i,a):s&&this.audioManager?.addBufferByInt16(s,a)}playAudio(){this.audioManager?.startSpeech()}stopAudio(){this.audioManager?.stopSpeech()}startRecord(){this.vadManager?.startRecord()}stopRecord(){this.vadManager?.stopRecord()}setVolume(e){this.audioManager&&(this.audioManager.volume=e)}getVolume(){return this.audioManager?this.audioManager.volume:0}startCapture(){this.camManager?.startCapture()}stopCapture(){this.camManager?.stopCapture()}onDestroy(){this.camManager?.onDestroy(),this.audioCtx?.close(),this.destory.next(),this.destory.complete()}}class P{socket;messageSub;destorySub;get message(){return this.messageSub.asObservable()}constructor(t){this.destorySub=new d,this.messageSub=new d;const{specific:i,id:s}=t.project;this.socket=_(t.ws_url,e({autoConnect:!1},i&&void 0!==s?{query:{uuid:s}}:{}))}start(){const e="list",t="cloud",i="channel";p(this.socket,"connect").pipe(l(this.destorySub)).subscribe(()=>{k.debug("Client ID: ",this.socket.id)}),p(this.socket,e).pipe(b(1),l(this.destorySub)).subscribe(t=>{this.messageSub.next({event:e,data:t})}),p(this.socket,t).pipe(l(this.destorySub)).subscribe(e=>{this.messageSub.next({event:t,data:e})}),p(this.socket,i).pipe(l(this.destorySub)).subscribe(e=>{this.messageSub.next({event:i,data:e})}),this.socket.connect()}send(e,t){this.socket.emit(e,t)}sendToCloud(e){this.send("cloud",JSON.stringify(e))}onDestroy(){this.destorySub.next(),this.destorySub.complete(),this.messageSub.complete(),this.socket.disconnect()}}class V{config;media;chat;destorySub;projectsSub;layoutSub;aiiaSubtitleSub;userSubtitleSub;sampleRateOfWhisper;channelState;stateSub;_currentState;isMuteAiia;aiiaSubtitleClearSub;userSubtitleClearSub;_lastProjects;guestSub;_hasGuest;get state(){return this.stateSub.asObservable()}get currentState(){return this._currentState}get layout(){return this.layoutSub.asObservable()}get projects(){return this.projectsSub.asObservable()}get lastProjects(){return a(this._lastProjects,e=>({...e}))}get aiiaSubtitle(){return this.aiiaSubtitleSub.asObservable()}get userSubtitle(){return this.userSubtitleSub.asObservable()}get guest(){return this.guestSub.asObservable()}get hasGuest(){return this._hasGuest}set volume(e){this.media.setVolume(e)}get volume(){return this.media.getVolume()}constructor(e,t){this.config=e,this.media=t,this.layoutSub=new d,this.projectsSub=new m(1),this.destorySub=new d,this.aiiaSubtitleSub=new d,this.userSubtitleSub=new d,this.aiiaSubtitleClearSub=new d,this.userSubtitleClearSub=new d,this.stateSub=new g("NotStart"),this.sampleRateOfWhisper={input:e.llmSampleRate,output:16e3},this.channelState={proxy:!1,cloud:!1},this.isMuteAiia=!1,this._lastProjects=[],this.guestSub=new m(1),this._hasGuest=!1,this.chat=new P(e),k.code(E["指定依賴已完成"],"chat"),this.close=this.onDestroy,this.behavior=this.behavior.bind(this),this.cloudToObj=this.cloudToObj.bind(this);const{userDelayTime:i,aiiaDelayTime:s}=e.autoClearSubtitle;isNaN(s)||this.aiiaSubtitleClearSub.pipe(f(s),l(this.destorySub)).subscribe(()=>{this.aiiaSubtitleSub.next("")}),isNaN(i)||this.userSubtitleClearSub.pipe(f(i),l(this.destorySub)).subscribe(()=>{this.userSubtitleSub.next("")}),this.stateSub.pipe(l(this.destorySub)).subscribe(e=>{this._currentState=e}),this.projectsSub.pipe(l(this.destorySub)).subscribe(e=>{this._lastProjects=e}),this.guestSub.pipe(l(this.destorySub)).subscribe(e=>{this._hasGuest=e})}start(){this.media.stateObs.pipe(l(this.destorySub)).subscribe(()=>{this.channelCheck()}),this.chat.message.pipe(l(this.destorySub)).subscribe(({event:e,data:t})=>{switch(e){case"cloud":this.cloudToObj(t);break;case"list":{const{id:e,name:i,specific:s}=this.config.project;let a=null;if(s&&void 0!==e)a=e;else{const s=r(t,t=>t.name===i||t.id===e);s&&(a=s.id)}null!==a?this.chooseProject(a):(this.projectsSub.next(t),this.stateSub.next("WattingProjectID"),k.code(E["請訂閱專案列表"]));break}case"channel":switch(t){case"open":this.channelState.proxy=!0,this.channelCheck();break;case"close":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService");break;case"no_permissions":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("NoPermissions");break;case"reconnect":this.channelState.proxy=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("ReconnectingCloud")}}}),this.chat.start(),this.stateSub.next("ConnectingCloud"),k.code(E["啟動SDK"])}close;skip(){this.chat.sendToCloud({request:"skip"})}reset(){this.chat.sendToCloud({request:"reset"})}chooseProject(e){this.chat.send("uuid",e),this.stateSub.next("ConnectingCloud")}mute(){this.isMuteAiia=!0,this.media.stopAudio(),this.media.stopRecord()}unmute(){this.isMuteAiia=!1,this.media.startRecord()}cloudToObj(e){try{const t=JSON.parse(e);void 0!==n(t,"signal")?this.behavior(t):c(t)?a(t,this.behavior):a(u(t),this.behavior)}catch(e){k.error("message transfer fail",e)}}behavior(e){switch(e.signal){case"layout":this.layoutSub.next(e.content);break;case"audio":if("sampleRate"===e.command)this.sampleRateOfWhisper.output=e.content;else switch(e.command){case"pcm":if(!this.isMuteAiia){const t=function(e,t,i){if(t===i)return e;const s=i/t,a=Math.ceil(e.length*s),o=new Float32Array(a);for(let t=0;t<a;t++){const i=t/s,a=Math.floor(i),n=i-a,r=e[a],c=e[Math.min(a+1,e.length-1)];o[t]=r+(c-r)*n}return o}(T(new Int16Array(e.content)),this.sampleRateOfWhisper.output,this.media.sampleRate);this.media.addAudioQueue({float32:t}),this.media.playAudio()}break;case"interrupted":this.media.stopAudio(),this.aiiaSubtitleSub.next("")}break;case"status":"Connected"===n(e,"content")&&(this.channelState.cloud=!0,this.channelCheck(),this.layoutSub.next({type:"resetLayout"}));break;case"agent":if(!this.isMuteAiia){const t=n(e,"content.function_result.response");void 0!==t&&(this.aiiaSubtitleSub.next(t),this.aiiaSubtitleClearSub.next())}break;case"asr":if(!this.isMuteAiia&&"ASR"===n(e,"content.function_type")){const t=n(e,"content.function_result.result");void 0!==t&&(this.userSubtitleSub.next(t),this.userSubtitleClearSub.next())}break;case"cv":{const t=n(e,"content.status");"user present"===t?this.guestSub.next(!0):"user leave"===t?this.guestSub.next(!1):k.warn("Unexpect value",n(e,"content"));break}default:k.debug(e.signal)}}channelCheck(){const{proxy:e,cloud:t}=this.channelState;if(e&&t&&this.media.state===N.allowed){this.chat.sendToCloud({request:"audio",command:"sampleRate",content:this.sampleRateOfWhisper.input}),this.media.pcm.pipe(l(this.destorySub)).subscribe(e=>{this.chat.sendToCloud({request:"audio",command:"pcm",content:j(e)})});const e=(n=this.config.faceDetection.confidence,function(e,t){const{width:r,height:c}=t,u=i(e,e=>100*(e.categories[0]?.score??0)>=n),h=s(a(u,e=>{if(void 0!==e.boundingBox){const t=e.boundingBox,i=t.width*t.height,s=e.categories[0]?.score??0;return{...t,area:i,score:100*s,clientWidth:r,clientHeight:c}}return null}));return o(h,["area","score"])[0]??null});this.media.detections.pipe(l(this.destorySub),S(({detections:t,clientHeight:i,clientWidth:s})=>e(t,{width:s,height:i}))).subscribe(e=>{this.chat.send("face",e)}),this.media.startRecord(),this.media.startCapture(),this.stateSub.next("InService"),k.code(E["通道、雲端服務、麥克風都已就緒"])}else e&&t&&this.media.state!==N.allowed&&(this.stateSub.next("InServiceNoMedia"),k.code(E["未取得媒體裝置權限,請重新設定或忽略此訊息"]));var n}onDestroy(){this.chat.onDestroy(),this.media.onDestroy(),this.destorySub.next(),this.destorySub.complete(),this.stateSub.next("Destroy"),this.stateSub.complete(),k.code(E["結束SDK"])}}function q(t){if(v){!function(e){const{info:t,error:i,warn:s,debug:a,fatal:o,code:n}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);n||(R.code=()=>{}),a||(R.debug=()=>{}),t||(R.info=()=>{}),s||(R.warn=()=>{}),i||(R.error=()=>{}),o||(R.fatal=()=>{})}(n(t,"debug",!0)),k.code(E["初始化必要依賴"]);const i=new x(e({},t));k.code(E["指定依賴已完成"],"config");const s=new B(i.mediaStream);k.code(E["指定依賴已完成"],"config");const a=new V(i,s);return k.code(E["指定依賴已完成"],"sdk"),""!==i.worklet_url&&s.init(i.worklet_url,i),a}throw new Error(C["環境錯誤(browser)"])}export{q as initSdk};
1
+ import{get as e,assign as t,forEach as s,filter as i,compact as a,map as n,sortBy as o,cloneDeep as r,find as c}from"lodash-es";import u from"moment";import"mathjs";import{Subject as d,fromEvent as h,takeUntil as l,take as p,ReplaySubject as b,BehaviorSubject as m,debounceTime as g,map as f}from"rxjs";import{io as S}from"socket.io-client";import{DrawingUtils as _,PoseLandmarker as y,FilesetResolver as C,FaceDetector as w}from"@mediapipe/tasks-vision";var v;!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(v||(v={}));const x="undefined"!=typeof window;"undefined"!=typeof process&&null!=process.versions&&process.versions.node;class k{config;get webserver(){return this.safeRead("webserver")}get environment(){const e=this.safeRead("env");return"string"==typeof e?e:"developement"}get license(){return this.safeRead("license")??""}get port(){return function(e,t){const s="number"==typeof t?t:NaN;if(e){const t=Number(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.safeRead("port")??3e3)}get ws_url(){const e=this.safeRead("ws_url");return"string"==typeof e?e:""}get worklet_url(){const e=this.safeRead("worklet_url");return"string"==typeof e?e:""}get project(){const t=this.safeRead("project");if(void 0===t)return{specific:!1};if("string"==typeof t)return{name:t,id:t,specific:!1};{const s=e(t,"id",void 0),i=e(t,"name",void 0);return{id:"string"==typeof s?s:void 0,name:"string"==typeof i?i:void 0,specific:!0}}}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}const t=`graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`;return{api:`https://${t}`,socket:`wss://${t}`}}get debug(){return this.safeRead("debug")??!0}get mediaStream(){return this.safeRead("mediaStream")}get chunkTimeInSeconds(){const e=this.safeRead("chunkTimeInSeconds");return"number"==typeof e&&e>0?e:.3}get llmSampleRate(){const e=this.safeRead("llmSampleRate");return"number"==typeof e&&e>0?e:16e3}get detection(){return t({perSecond:3,confidence:80},this.safeRead("faceDetection"),this.safeRead("cameraDetection"))}get autoClearSubtitle(){const e=this.safeRead("autoClearUserSubtitle"),t=this.safeRead("autoClearAiiaSubtitle");return{userDelayTime:"number"==typeof e?e:5e3,aiiaDelayTime:"number"==typeof t?t:NaN}}get eyeTrackEnable(){return Boolean(this.safeRead("eyeTrackEnable"))}get eyeTrackCorrection(){return t({x_axis_px:0,y_axis_px:0},this.safeRead("correction"))}constructor(e){this.config=e}safeRead(t){return e(this.config,t,void 0)}}var R;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["已啟動SDK"]="CODE: 1014",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線雲端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(R||(R={}));const E={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},D=[];const O=new Proxy(E,{get(e,t,i){if("string"==typeof t)switch(t){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...i)=>{const a=u().format("yyyy-MM-DD[T]HH:mm:ss:sss");s(D,e=>{e.next(t,a,...i)}),e[t](`[Aiia::${t}_${a}_]`,...i)};default:return}},set:(e,t,s,i)=>!0});class N{socket;messageSub;destorySub;get message(){return this.messageSub.asObservable()}constructor(e){this.destorySub=new d,this.messageSub=new d;const{specific:s,id:i}=e.project;this.socket=S(e.ws_url,t({autoConnect:!1},s&&void 0!==i?{query:{uuid:i}}:{}))}start(){const e="list",t="cloud",s="channel";h(this.socket,"connect").pipe(l(this.destorySub)).subscribe(()=>{O.debug("Client ID: ",this.socket.id)}),h(this.socket,e).pipe(p(1),l(this.destorySub)).subscribe(t=>{this.messageSub.next({event:e,data:t})}),h(this.socket,t).pipe(l(this.destorySub)).subscribe(e=>{this.messageSub.next({event:t,data:e})}),h(this.socket,s).pipe(l(this.destorySub)).subscribe(e=>{this.messageSub.next({event:s,data:e})}),this.socket.connect()}send(e,t){this.socket.emit(e,t)}sendToCloud(e){this.send("cloud",e)}onDestroy(){this.destorySub.next(),this.destorySub.complete(),this.messageSub.complete(),this.socket.disconnect()}}var M,T;function A(e,t,s){return Math.max(s,Math.min(e,t))}function j(e){const t=new DataView(e.buffer),s=[];for(let i=0;i<e.length;i++){const e=t.getInt16(2*i,!0)/32768;s.push(e)}return new Float32Array(s)}function I(e){const t=new DataView(e.buffer),s=[];for(let i=0;i<e.length;i++){const e=A(32768*t.getFloat32(4*i,!0),32767,-32768);s.push(e)}return s}!function(e){e[e.unknow=0]="unknow",e[e.allowed=1]="allowed",e[e.rejected=2]="rejected"}(M||(M={}));class F{audioContext;type="audio";currentNode;bufferQueue;gainNode;set volume(e){e<0&&(e=0),e>100&&(e=100),this.gainNode.gain.value=e/100}get volume(){return Math.round(100*this.gainNode.gain.value)}constructor(e){this.audioContext=e,this.bufferQueue=[],this.currentNode=null,this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination)}addBuffer(e){this.bufferQueue.push(e)}addBufferByFloat32(e,t=1){const s=e.length,i=this.audioContext.sampleRate,a=this.audioContext.createBuffer(t,s,i),n=e.slice();for(let e=0;e<t;e++)a.copyToChannel(n,e);this.addBuffer(a)}addBufferByInt16(e,t=1){this.addBufferByFloat32(j(e),t)}startSpeech(){null===this.currentNode&&this.continue()}pauseSpeech(){this.audioContext.suspend().catch(e=>{O.error("pause speech fail",e)})}resumeSpeech(){this.audioContext.resume().catch(e=>{O.error("resume speech fail",e)})}nextSpeech(){this.bufferQueue.length>0&&(this.clearNode(),this.continue())}stopSpeech(){this.bufferQueue=[],this.clearNode()}clearNode(){this.currentNode&&(this.currentNode.onended=function(){},this.currentNode.stop(),this.currentNode.disconnect(),this.currentNode=null)}continue(){const e=this.bufferQueue.shift();if(e){this.currentNode=this.audioContext.createBufferSource(),this.currentNode.buffer=e,this.currentNode.connect(this.gainNode);const t=this.continue.bind(this);this.currentNode.onended=t,this.currentNode.start()}else this.clearNode()}}class P{stream;audioContext;type="vad";workletNode;source;isRecording=!1;outputSampleRate;chunkTimeInSeconds;pcmSub;get pcm(){return this.pcmSub.asObservable()}constructor(e,t,s){this.stream=e,this.audioContext=t,this.outputSampleRate=s?.llmSampleRate??16e3,this.chunkTimeInSeconds=s?.chunkTimeInSeconds??1,this.pcmSub=new d}async startRecord(){this.isRecording||(this.source=this.audioContext.createMediaStreamSource(this.stream),this.workletNode=new AudioWorkletNode(this.audioContext,"aiia-vad",{processorOptions:{outputSampleRate:this.outputSampleRate,chunkTimeInSeconds:this.chunkTimeInSeconds}}),this.source.connect(this.workletNode),this.workletNode.port.onmessage=e=>{const{float32:t}=e.data;t&&this.pcmSub.next(new Float32Array(t))},this.isRecording=!0)}stopRecord(){this.isRecording&&(this.workletNode&&(this.workletNode.port.onmessage=null,this.workletNode.disconnect()),this.source&&this.source.disconnect(),this.isRecording=!1)}}class W{type="camera";aiiaCamera;get detections(){return this.aiiaCamera.detections}get landmarks(){return this.aiiaCamera.landmarks}get videoSize(){return this.aiiaCamera.videoSize}constructor(e){const{perSecond:s}=t({perSecond:3},e);this.aiiaCamera=new B,this.aiiaCamera.setFrequency(s),document.body.append(this.aiiaCamera)}async init(e){try{const t=await C.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm"),[s,i]=await Promise.all([w.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite",delegate:"GPU"},runningMode:"VIDEO"}),y.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task",delegate:"GPU"},runningMode:"VIDEO",numPoses:5})]);this.aiiaCamera.setStream(e),this.aiiaCamera.setFaceDetector(s),this.aiiaCamera.setPoseLandmarker(i)}catch(e){O.error("Camera init fail",e)}}startCapture(){this.aiiaCamera.startCapture()}stopCapture(){this.aiiaCamera.stopCapture()}onDestroy(){this.aiiaCamera.remove()}}class B extends HTMLElement{static get observedAttributes(){return["debug"]}_shadow;_containerEl;_styleEl;_videoEl;_canvasEl;_canvasCtx;_isDebug;_debugElements;_interval;_lastTimestamp;_loopFrameID;_faceDetector;_detectionsSub;_poseLandmarker;_landmarksSub;_isReady;_drawingUtils;landmarkColors=["gray","lightgray","gray","gray","lightgray"];connectorColors=["green","blue","red","yellow","purple"];get detections(){return this._detectionsSub.asObservable()}get landmarks(){return this._landmarksSub.asObservable()}get videoSize(){return{width:this._videoEl.videoWidth,height:this._videoEl.videoHeight}}constructor(){super(),this.id="AiiaCamera",this._isDebug=!1,this._isReady=!1,this._shadow=this.attachShadow({mode:"open"}),this._containerEl=document.createElement("div"),this._videoEl=document.createElement("video"),this._styleEl=document.createElement("style"),this._canvasEl=document.createElement("canvas"),this._debugElements=[],this._loopFrameID=null,this._detectionsSub=new d,this._landmarksSub=new d,this._interval=Math.ceil(1e3/3),this._lastTimestamp=0,this.predictWebcam=this.predictWebcam.bind(this),this._settingContainer(),this._settingStyle(),this._settingVideo(),this._containerEl.appendChild(this._videoEl),this._shadow.appendChild(this._styleEl),this._shadow.appendChild(this._containerEl),this._containerEl.appendChild(this._canvasEl);const e=this._canvasEl.getContext("2d");e?(this._canvasCtx=e,this._drawingUtils=new _(this._canvasCtx)):(this._canvasCtx=null,this._drawingUtils=null)}connectedCallback(){this.startCapture()}disconnectedCallback(){this.stopCapture()}attributeChangedCallback(e,t,s){"debug"===e&&(this._isDebug=null!==s&&("true"===s||""===s),this._containerEl.style.visibility=this._isDebug?"visible":"hidden")}setStream(e){this._videoEl.onloadedmetadata=()=>{this._isReady=!0},this._videoEl.srcObject=e}setFaceDetector(e){this._faceDetector=e}setPoseLandmarker(e){this._poseLandmarker=e}setFrequency(e){this._interval=Math.ceil(1e3/e)}_settingStyle(){this._styleEl.textContent="\n .aiia_container {\n visibility: hidden;\n position: fixed;\n top: 0;\n left: 0;\n\n > canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 640px;\n height: 480px;\n }\n }\n .aiia_container .highlighter {\n background: rgba(0, 255, 0, 0.25);\n border: 1px dashed #fff;\n z-index: 1;\n position: absolute;\n }\n .aiia_container .confidence {\n position: absolute;\n padding-bottom: 5px;\n padding-top: 5px;\n background-color: #007f8b;\n color: #fff;\n border: 1px dashed rgba(255, 255, 255, 0.7);\n z-index: 2;\n font-size: 12px;\n margin: 0;\n }\n "}_settingContainer(){this._containerEl.classList.add("aiia_container")}_settingVideo(){this._videoEl.autoplay=!0,this._videoEl.playsInline=!0,this._videoEl.muted=!0}getVideo(){return this._videoEl}startCapture(){null===this._loopFrameID&&(this._videoEl.play(),this._loopFrameID=window.requestAnimationFrame(this.predictWebcam))}stopCapture(){null!==this._loopFrameID&&(window.cancelAnimationFrame(this._loopFrameID),this._loopFrameID=null),this._videoEl.pause()}async predictWebcam(){if(this._isReady){const e=performance.now();if(this._lastTimestamp+this._interval<=e){this._lastTimestamp=e;const t=this._faceDetector?.detectForVideo(this._videoEl,e).detections,s=this._poseLandmarker?.detectForVideo(this._videoEl,e).landmarks;t&&(this._isDebug&&this._drawRect(t),this._detectionsSub.next(t)),s&&(this._isDebug&&this._drawRect2(s),this._landmarksSub.next(s))}}this._loopFrameID=window.requestAnimationFrame(this.predictWebcam)}_drawRect(e){s(e,(e,t)=>{const{categories:s,boundingBox:i}=e;if(i){let e=this._debugElements[2*t],a=this._debugElements[2*t+1];e&&a||(e=document.createElement("div"),e.classList.add("highlighter"),a=document.createElement("p"),a.classList.add("confidence"),this._containerEl.append(a,e),this._debugElements.push(e,a)),e.style.display="block",a.style.display="block";const n=100*s[0].score;a.innerText=`Confidence: ${Math.round(n)} %`,a.style.right=this._videoEl.offsetWidth-i.width-i.originX+"px",a.style.top=i.originY-30+"px",a.style.width=i.width-10+"px",e.style.right=this._videoEl.offsetWidth-i.width-i.originX+"px",e.style.top=`${i.originY}px`,e.style.width=i.width-10+"px",e.style.height=`${i.height}px`}});for(let t=2*e.length;t<this._debugElements.length;t++)this._debugElements[t].style.display="none"}_drawRect2(e){this._canvasCtx&&this._drawingUtils&&(this._canvasCtx.save(),this._canvasCtx.clearRect(0,0,640,480),s(e,(e,t)=>{this._canvasCtx?.save(),this._drawingUtils?.drawLandmarks(e,{radius:e=>_.lerp(e.from.z,-.15,.1,5,1),color:this.landmarkColors[t]}),this._drawingUtils?.drawConnectors(e,y.POSE_CONNECTIONS,{color:this.connectorColors[t],lineWidth:2})}),this._canvasCtx.restore())}destroy(){this._detectionsSub.complete(),this.remove()}}customElements.define("aiia-camera",B),function(e){e[e.padding=0]="padding",e[e.rejected=1]="rejected",e[e.loadfail=2]="loadfail",e[e.allowed=3]="allowed"}(T||(T={}));class V{stream;_state;stateSub;audioCtx;audioManager;vadManager;camManager;destroy;pcmSub;detectionsSub;landmarksSub;get sampleRate(){return this.audioCtx?this.audioCtx.sampleRate:16e3}get pcm(){return this.pcmSub.asObservable()}get detections(){return this.detectionsSub.asObservable()}get landmarks(){return this.landmarksSub.asObservable()}get state(){return this._state}set state(e){e!==this._state&&(this._state=e,this.stateSub.next(e))}get stateObs(){return this.stateSub.asObservable()}constructor(e){this.stream=e,this.stateSub=new b(1),this.pcmSub=new d,this.destroy=new d,this.detectionsSub=new d,this.landmarksSub=new d,this.state=T.padding}async init(e,t){const s=this.stream??await async function(e){try{return await navigator.mediaDevices.getUserMedia(e)}catch(e){return null}}({audio:{echoCancellation:!0,noiseSuppression:!0},video:!0});if(null===s)throw this.state=T.rejected,new Error(v["未獲得媒體裝置權限"]);this.stream=s,this.audioCtx=new AudioContext;try{await this.audioCtx.audioWorklet.addModule(e)}catch(e){throw O.fatal(e),this.state=T.loadfail,new Error(v["Worklet模組載入失敗"])}this.state=T.allowed,this.audioManager=new F(this.audioCtx),this.vadManager=new P(s,this.audioCtx,t),this.vadManager.pcm.pipe(l(this.destroy)).subscribe(e=>{this.pcmSub.next(e)});const i=new W(t?.cameraDetection);i.detections.pipe(l(this.destroy)).subscribe(e=>{const{width:t,height:s}=i.videoSize;this.detectionsSub.next({detections:e,clientWidth:t,clientHeight:s})}),i.landmarks.pipe(l(this.destroy)).subscribe(e=>{this.landmarksSub.next(e)}),i.init(s),this.camManager=i}addAudioQueue(t){const s=e(t,"buffer",void 0),i=e(t,"float32",void 0),a=e(t,"int16",void 0),n=e(t,"numberOfChannels",void 0);s?this.audioManager?.addBuffer(s):i?this.audioManager?.addBufferByFloat32(i,n):a&&this.audioManager?.addBufferByInt16(a,n)}playAudio(){this.audioManager?.startSpeech()}stopAudio(){this.audioManager?.stopSpeech()}startRecord(){this.vadManager?.startRecord()}stopRecord(){this.vadManager?.stopRecord()}setVolume(e){this.audioManager&&(this.audioManager.volume=e)}getVolume(){return this.audioManager?this.audioManager.volume:0}startCapture(){this.camManager?.startCapture()}stopCapture(){this.camManager?.stopCapture()}onDestroy(){this.camManager?.onDestroy(),this.audioCtx?.close(),this.destroy.next(),this.destroy.complete()}}class L{config;media;chat;destroySub;projectsSub;layoutSub;aiiaSubtitleSub;userSubtitleSub;sampleRateOfWhisper;channelState;stateSub;_currentState;isMuteAiia;aiiaSubtitleClearSub;userSubtitleClearSub;_lastProjects;guestSub;_hasGuest;mediaEventReady;isStart;get state(){return this.stateSub.asObservable()}get currentState(){return this._currentState}get layout(){return this.layoutSub.asObservable()}get projects(){return this.projectsSub.asObservable()}get lastProjects(){return r(this._lastProjects)}get aiiaSubtitle(){return this.aiiaSubtitleSub.asObservable()}get userSubtitle(){return this.userSubtitleSub.asObservable()}get guest(){return this.guestSub.asObservable()}get hasGuest(){return this._hasGuest}set volume(e){this.media.setVolume(e)}get volume(){return this.media.getVolume()}constructor(e,t){this.config=e,this.media=t,this.layoutSub=new d,this.projectsSub=new b(1),this.destroySub=new d,this.aiiaSubtitleSub=new d,this.userSubtitleSub=new d,this.aiiaSubtitleClearSub=new d,this.userSubtitleClearSub=new d,this.stateSub=new m("NotStart"),this.sampleRateOfWhisper={input:e.llmSampleRate,output:16e3},this.channelState={proxy:!1,cloud:!1},this.isMuteAiia=!1,this._lastProjects=[],this.guestSub=new b(1),this._hasGuest=!1,this.mediaEventReady=!1,this.isStart=!1,this.chat=new N(e),O.code(R["指定依賴已完成"],"chat"),this.close=this.onDestroy,this.behavior=this.behavior.bind(this);const{userDelayTime:s,aiiaDelayTime:i}=e.autoClearSubtitle;isNaN(i)||this.aiiaSubtitleClearSub.pipe(g(i),l(this.destroySub)).subscribe(()=>{this.aiiaSubtitleSub.next("")}),isNaN(s)||this.userSubtitleClearSub.pipe(g(s),l(this.destroySub)).subscribe(()=>{this.userSubtitleSub.next("")}),this.stateSub.pipe(l(this.destroySub)).subscribe(e=>{this._currentState=e}),this.projectsSub.pipe(l(this.destroySub)).subscribe(e=>{this._lastProjects=e}),this.guestSub.pipe(l(this.destroySub)).subscribe(e=>{this._hasGuest=e}),t.stateObs.pipe(l(this.destroySub)).subscribe(e=>{T.allowed===e&&this.channelCheck()})}start(){this.isStart?O.code(R["已啟動SDK"]):(this.isStart=!0,this.chat.message.pipe(l(this.destroySub)).subscribe(({event:e,data:t})=>{switch(e){case"cloud":n(t,this.behavior);break;case"list":{const{id:e,name:s,specific:i}=this.config.project;let a=null;if(i&&void 0!==e)a=e;else{const i=c(t,t=>t.name===s||t.id===e);i&&(a=i.id)}null!==a?this.chooseProject(a):null===t?(this.stateSub.next("CloseService"),O.error("No project ready or lose network")):(this.projectsSub.next(t),this.stateSub.next("WaitingProjectID"),O.code(R["請訂閱專案列表"]));break}case"channel":switch(this.channelState.proxy=!0,t){case"open":this.channelCheck();break;case"close":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService");break;case"no_permissions":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("NoPermissions");break;case"reconnect":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("ReconnectingCloud");break;case"net_error":this.channelState.cloud=!1,this.media.stopRecord(),this.media.stopCapture(),this.stateSub.next("CloseService"),O.error("Please check network status")}}}),this.chat.start(),this.stateSub.next("ConnectingCloud"),O.code(R["啟動SDK"]))}close;skip(){this.chat.sendToCloud({request:"skip"})}reset(){this.chat.sendToCloud({request:"reset"})}chooseProject(e){this.chat.send("uuid",e),this.stateSub.next("ConnectingCloud")}mute(){this.isMuteAiia=!0,this.media.stopAudio(),this.media.stopRecord()}unmute(){this.isMuteAiia=!1,this.media.startRecord()}behavior(t){switch(t.signal){case"layout":this.layoutSub.next(t.content);break;case"audio":if("sampleRate"===t.command)this.sampleRateOfWhisper.output=t.content;else switch(t.command){case"pcm":if(!this.isMuteAiia){const e=function(e,t,s){if(t===s)return e;const i=s/t,a=Math.ceil(e.length*i),n=new Float32Array(a);for(let t=0;t<a;t++){const s=t/i,a=Math.floor(s),o=s-a,r=e[a],c=e[Math.min(a+1,e.length-1)];n[t]=r+(c-r)*o}return n}(j(new Int16Array(t.content)),this.sampleRateOfWhisper.output,this.media.sampleRate);this.media.addAudioQueue({float32:e}),this.media.playAudio()}break;case"interrupted":this.media.stopAudio(),this.aiiaSubtitleSub.next("")}break;case"status":"Connected"===e(t,"content")&&(this.channelState.cloud=!0,this.channelCheck(),this.layoutSub.next({type:"resetLayout"}));break;case"agent":if(!this.isMuteAiia){const s=e(t,"content.function_result.response");void 0!==s&&(this.aiiaSubtitleSub.next(s),this.aiiaSubtitleClearSub.next())}break;case"asr":if(!this.isMuteAiia&&"ASR"===e(t,"content.function_type")){const s=e(t,"content.function_result.result");void 0!==s&&(this.userSubtitleSub.next(s),this.userSubtitleClearSub.next())}break;case"cv":{const s=e(t,"content.status");"user present"===s?this.guestSub.next(!0):"user leave"===s?this.guestSub.next(!1):O.warn("Unexpect value",e(t,"content"));break}default:O.debug(t.signal)}}channelCheck(){const{proxy:e,cloud:t}=this.channelState;if(e&&t&&this.media.state===T.allowed){if(this.chat.sendToCloud({request:"audio",command:"sampleRate",content:this.sampleRateOfWhisper.input}),!this.mediaEventReady){this.mediaEventReady=!0,this.media.pcm.pipe(l(this.destroySub)).subscribe(e=>{this.chat.sendToCloud({request:"audio",command:"pcm",content:I(e)})});const e=(s=this.config.detection.confidence,function(e,t){const{width:r,height:c}=t,u=i(e,e=>100*(e.categories[0]?.score??0)>=s),d=a(n(u,e=>{if(void 0!==e.boundingBox){const t=e.boundingBox,s=t.width*t.height,i=e.categories[0]?.score??0;return{...t,area:s,score:100*i,clientWidth:r,clientHeight:c}}return null}));return o(d,["area","score"])[0]??null});this.media.detections.pipe(l(this.destroySub),f(({detections:t,clientHeight:s,clientWidth:i})=>e(t,{width:i,height:s}))).subscribe(e=>{this.chat.send("face",e)}),this.media.landmarks.pipe(l(this.destroySub)).subscribe(e=>{this.chat.sendToCloud({request:"pose_detect",content:e})})}this.media.startRecord(),this.media.startCapture(),this.stateSub.next("InService"),O.code(R["通道、雲端服務、麥克風都已就緒"])}else e&&t&&this.media.state!==T.allowed&&(this.stateSub.next("InServiceNoMedia"),O.code(R["未取得媒體裝置權限,請重新設定或忽略此訊息"]));var s}onDestroy(){this.chat.onDestroy(),this.media.onDestroy(),this.destroySub.next(),this.destroySub.complete(),this.stateSub.next("Destroy"),this.stateSub.complete(),O.code(R["結束SDK"])}}function U(s){if(x){!function(e){const{info:t,error:s,warn:i,debug:a,fatal:n,code:o}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);o||(E.code=()=>{}),a||(E.debug=()=>{}),t||(E.info=()=>{}),i||(E.warn=()=>{}),s||(E.error=()=>{}),n||(E.fatal=()=>{})}(e(s,"debug",!0)),O.code(R["初始化必要依賴"]);const i=new k(t({},s));O.code(R["指定依賴已完成"],"config");const a=new V(i.mediaStream);O.code(R["指定依賴已完成"],"config");const n=new L(i,a);return O.code(R["指定依賴已完成"],"sdk"),""!==i.worklet_url&&a.init(i.worklet_url,i),n}throw new Error(v["環境錯誤(browser)"])}export{U as initSdk};
package/dist/node.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=require("lodash-es"),s=require("moment"),o=require("mathjs"),i=require("socket.io"),r=require("rxjs"),n=require("axios"),c=require("ws"),a=require("fs"),h=require("os"),l=require("path"),u=require("node-osc");!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(e||(e={}));const d="access_token",S="undefined"!=typeof process&&null!=process.versions&&null!=process.versions.node;class b{config;get webserver(){return this.config.webserver}get environment(){return this.config.env}get license(){return this.config.license??""}get port(){return function(e,t){const s=t??NaN;if(e){const t=parseInt(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.config.port??3e3)}get ws_url(){return this.config.ws_url??""}get worklet_url(){return this.config.worklet_url??""}get project(){const{project:e}=t.assign({},this.config);return void 0===e?{specific:!1}:"string"==typeof e?{name:e,id:e,specific:!1}:t.assign({},e,{specific:!0})}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}return{api:`https://aiia-content-management-${e}-21193779403.asia-east1.run.app`,socket:`wss://graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`}}get debug(){return this.config.debug??!0}get mediaStream(){return this.config.mediaStream}get chunkTimeInSeconds(){return"number"==typeof this.config.chunkTimeInSeconds&&this.config.chunkTimeInSeconds>0?this.config.chunkTimeInSeconds:.3}get llmSampleRate(){return"number"==typeof this.config.llmSampleRate&&this.config.llmSampleRate>0?this.config.llmSampleRate:16e3}get faceDetection(){return t.assign({perSecond:3,confidence:80},this.config.faceDetection)}get autoClearSubtitle(){return{userDelayTime:this.config.autoClearUserSubtitle??5e3,aiiaDelayTime:this.config.autoClearAiiaSubtitle??NaN}}get eyeTrackEnable(){return this.config.eyeTrackEnable??!1}get eyeTrackCorrection(){return t.assign({x_axis_px:0,y_axis_px:0},this.config.correction)}constructor(e){this.config=e}}var p;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線連端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(p||(p={}));const _={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},g=[];function E(e){return g.push(e),E}const f=new Proxy(_,{get(e,o,i){if("string"==typeof o)switch(o){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...i)=>{const r=s().format("yyyy-MM-DD[T]HH:mm:ss");t.forEach(g,e=>{e.next(o,r,...i)}),e[o](`[Aiia::${o}_${r}_]`,...i)};default:return}},set:(e,t,s,o)=>!0});class y{config;server;memberSub;get port(){return this.config.port}get webserver(){return this.config.webserver}get memberChange(){return this.memberSub.asObservable()}constructor(e){this.config=e,this.memberSub=new r.Subject}start(){if(this.server=new i.Server(this.webserver,{cors:{origin:"*"}}),this.server.on("connection",e=>{this.memberSub.next({type:"in",socket:e}),f.debug("client on",e.id),e.on("disconnect",t=>{this.memberSub.next({type:"out",socket:e}),f.debug("client off",t)})}),void 0===this.webserver){const e=this.port;this.server.listen(e),f.info("Websocket server run on",e)}else f.info("Websocket server rely your webserver.")}onDestroy(){this.server&&this.server.close()}}class m{instance;controller;constructor(e){this.controller=new AbortController,this.instance=n.create({baseURL:e,signal:this.controller.signal})}async getAccessToken(s,o){const i={license_number:s,token_value:o??""},r=await this.instance.post("/api/v1/license/agent_token",i).then(e=>t.get(e.data,"token_value","")).catch(()=>null);if(null===r)throw new Error(e["網絡錯誤"]);if(""===r)throw new Error(e["獲取權杖失敗"]);return r}async getProjectList(s){const o=await this.instance.post("/api/v1/license/projects",{license_number:s}).then(e=>t.map(t.get(e,"data.projects",[]),({id:e,sub_project_name:t})=>({id:e,name:t}))).catch(e=>null);if(null===o)throw new Error(e["網絡錯誤"]);if(0===o.length)throw new Error(e["沒有可用專案"]);return o}onDestroy(){this.controller.abort()}}var O;!function(e){e[e.MDN_NORMAL_CLOSURE=1e3]="MDN_NORMAL_CLOSURE",e[e.MDN_GOING_AWAY=1001]="MDN_GOING_AWAY",e[e.MDN_PROTOCOL_ERROR=1002]="MDN_PROTOCOL_ERROR",e[e.MDN_UNSUPPORTED_DATA=1003]="MDN_UNSUPPORTED_DATA",e[e.MDN_RESERVED_1004=1004]="MDN_RESERVED_1004",e[e.MDN_NO_STATUS_RECEIVED=1005]="MDN_NO_STATUS_RECEIVED",e[e.MDN_ABNORMAL_CLOSURE=1006]="MDN_ABNORMAL_CLOSURE",e[e.MDN_INVALID_FRAME_PAYLOAD=1007]="MDN_INVALID_FRAME_PAYLOAD",e[e.MDN_POLICY_VIOLATION=1008]="MDN_POLICY_VIOLATION",e[e.MDN_MESSAGE_TOO_BIG=1009]="MDN_MESSAGE_TOO_BIG",e[e.MDN_MANDATORY_EXTENSION=1010]="MDN_MANDATORY_EXTENSION",e[e.MDN_INTERNAL_ERROR=1011]="MDN_INTERNAL_ERROR",e[e.MDN_SERVICE_RESTART=1012]="MDN_SERVICE_RESTART",e[e.MDN_TRY_AGAIN_LATER=1013]="MDN_TRY_AGAIN_LATER",e[e.MDN_BAD_GATEWAY=1014]="MDN_BAD_GATEWAY",e[e.MDN_TLS_HANDSHAKE=1015]="MDN_TLS_HANDSHAKE",e[e.WHEN_BROWSER_CLOSE=4001]="WHEN_BROWSER_CLOSE",e[e.MANUAL_CLOSE=4002]="MANUAL_CLOSE",e[e.WS_5000_NORAML_CLOSURE=4100]="WS_5000_NORAML_CLOSURE",e[e.WS_5001_UNAUTHORIZED=4101]="WS_5001_UNAUTHORIZED",e[e.WS_5002_LICENSE_EXPIRED=4102]="WS_5002_LICENSE_EXPIRED",e[e.WS_5003_TOKEN_EXPIRED=4103]="WS_5003_TOKEN_EXPIRED"}(O||(O={}));class D{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}isConnected;constructor(){this.isConnected=!1,this.messageSub=new r.Subject,this.linkStartSub=new r.Subject,this.closeSub=new r.Subject,this.destorySub=new r.Subject}start(e){this.socket=new c(e,{autoPong:!0}),r.fromEvent(this.socket,"open").pipe(r.takeUntil(this.destorySub)).subscribe(()=>{this.isConnected=!0,f.code(p["啟動服務"],"proxy"),this.linkStartSub.next()}),r.fromEvent(this.socket,"message").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),r.fromEvent(this.socket,"error").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{error:t}=e;f.error("Proxy has some problem",t)}),r.fromEvent(this.socket,"close").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s,type:o,wasClean:i}=e;switch(t){case O.WHEN_BROWSER_CLOSE:f.code(p["無副作用的關閉連線"]);break;case O.MDN_NORMAL_CLOSURE:case O.MDN_GOING_AWAY:this.closeSub.next("close"),f.code(p["安全的關閉連線"]);break;case O.MDN_NO_STATUS_RECEIVED:case O.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),f.code(p["與雲端的服務中斷"],{code:t,reason:s,type:o,wasClean:i});break;case O.WS_5001_UNAUTHORIZED:case O.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),f.code(p["沒有授權,請聯繫Graphen"]);break;case O.WS_5000_NORAML_CLOSURE:case O.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),f.code(p["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),f.code(p["未預期的斷線,請聯繫Graphen"],{code:t,reason:s,type:o,wasClean:i})}})}reconnect(e){this.onDestroy(),this.destorySub=new r.Subject,this.start(e)}send(e){this.isConnected&&void 0!==this.socket&&this.socket.send(e,e=>{e&&f.error("send message fail",e?.message,e)})}sendToCloud(e){this.send(JSON.stringify(e))}onClose(){this.socket?.close(O.WHEN_BROWSER_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}const N=new class{AppName;platform;isMAC;isWIN;isLINUX;saveFolder;homedir;appFolder;EOL;constructor(e){switch(this.AppName=e,this.platform=process.platform,this.homedir=h.homedir(),this.EOL=h.EOL,this.platform){case"darwin":this.isMAC=!0,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=l.join(this.homedir,"Library","Application Support");break;case"win32":this.isMAC=!1,this.isWIN=!0,this.isLINUX=!1,this.saveFolder=process.env.APPDATA||l.join(this.homedir,"AppData","Roaming");break;case"linux":this.isMAC=!1,this.isWIN=!1,this.isLINUX=!0,this.saveFolder=process.env.XDG_CONFIG_HOME||l.join(this.homedir,".config");break;default:console.log(`當前系統是未知的平台: ${this.platform}`),this.isMAC=!1,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=this.homedir}this.appFolder=l.join(this.saveFolder,this.AppName),this.folderCheck(this.appFolder)}addPath(...e){return l.join(...e)}folderCheck(e){a.mkdirSync(e,{recursive:!0})}fileCheck(e){a.existsSync(e)||a.writeFileSync(e,"",{encoding:"utf8"})}}("aiia-sdk");class k{type="node";filePath;fileName;constructor(){this.fileName="storage.json",this.filePath=N.addPath(N.appFolder,this.fileName),N.fileCheck(this.filePath)}getFileDataSync(){const e=a.readFileSync(this.filePath,"utf8");try{return JSON.parse(e)}catch(e){return{}}}setFileDataSync(e){try{const t=JSON.stringify(e);a.writeFileSync(this.filePath,t,"utf8")}catch(e){}}async getFileData(){return new Promise(e=>{a.readFile(this.filePath,"utf8",(t,s)=>{if(t)e({});else try{e(JSON.parse(s))}catch(t){e({})}})})}async setFileData(e){return new Promise(t=>{try{const s=JSON.stringify(e);a.writeFile(this.filePath,s,"utf8",e=>{t()})}catch(e){}})}async getItem(e){return(await this.getFileData())[e]??null}async setItem(e,t){const s=await this.getFileData();s[e]=t,await this.setFileData(s)}async removeItem(e){const s=await this.getFileData(),o=t.omit(s,e);await this.setFileData(o)}async clear(){await this.setFileData({})}getItemSync(e){return this.getFileDataSync()[e]??null}setItemSync(e,t){const s=this.getFileDataSync();s[e]=t,this.setFileDataSync(s)}removeItemSync(e){const s=t.omit(this.getFileDataSync(),e);this.setFileDataSync(s)}clearSync(){this.setFileDataSync({})}}class R{config;constructor(e){this.config=e,f.info("UEProxy, config=",e)}start(){f.info("UEProxy, start proxy")}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"osc"===t.signal){f.info("UEProxy, payload=",t);try{let e=t.content;const s=new u.Client(e.host,e.port);f.debug("UEProxy, client open to port "+e.port),s.send(e.message,e=>{e&&f.error(e),s.close(),f.debug("UEProxy, client closed")})}catch(e){f.error(e)}}}cloudToObj(e){try{return JSON.parse(e)}catch(e){f.error("message transfer fail",e)}}onDestroy(){f.info("UEProxy, destroy proxy")}}class C{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}messageQueue=[];isConnected;constructor(){this.isConnected=!1,this.messageSub=new r.Subject,this.linkStartSub=new r.Subject,this.closeSub=new r.Subject,this.destorySub=new r.Subject}url="ws://localhost:8080";start(){this.socket=new c(this.url,{autoPong:!0}),r.fromEvent(this.socket,"open").pipe(r.takeUntil(this.destorySub)).subscribe(()=>{this.isConnected=!0,f.code(p["啟動服務"],"proxy"),this.flushMessageQueue(),this.linkStartSub.next()}),r.fromEvent(this.socket,"message").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),r.fromEvent(this.socket,"error").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{error:t}=e;f.error("Proxy has some problem",t)}),r.fromEvent(this.socket,"close").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s}=e;switch(t){case O.MANUAL_CLOSE:f.code(p["無副作用的關閉連線"]);break;case O.WS_5000_NORAML_CLOSURE:case O.MDN_NORMAL_CLOSURE:case O.MDN_GOING_AWAY:this.closeSub.next("close"),f.code(p["安全的關閉連線"]);break;case O.MDN_NO_STATUS_RECEIVED:case O.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),f.code(p["與雲端的服務中斷"],{code:t,reason:s});break;case O.WS_5001_UNAUTHORIZED:case O.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),f.code(p["沒有授權,請聯繫Graphen"]);break;case O.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),f.code(p["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),f.code(p["未預期的斷線,請聯繫Graphen"],{code:t,reason:s})}})}reconnect(){this.onDestroy(),this.destorySub=new r.Subject,this.start()}flushMessageQueue(){for(;this.messageQueue.length>0&&this.isConnected&&void 0!==this.socket;){const e=this.messageQueue.shift();e&&this.socket.send(e)}}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"audio"===t.signal&&"pcm"===t.command&&(f.info("AudioSocketProxy, payload=",t),this.isConnected&&void 0!==this.socket))try{const e=t.content,s=Buffer.allocUnsafe(2*e.length);for(let t=0;t<e.length;t++)s.writeInt16LE(e[t],2*t);this.socket.send(s)}catch(e){f.error(e)}}cloudToObj(e){try{return JSON.parse(e)}catch(e){f.error("message transfer fail",e)}}onClose(){this.socket?.close(O.MANUAL_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}class A{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}messageQueue=[];isConnected;constructor(){this.isConnected=!1,this.messageSub=new r.Subject,this.linkStartSub=new r.Subject,this.closeSub=new r.Subject,this.destorySub=new r.Subject}url="ws://localhost:8081";start(){this.socket=new c(this.url,{autoPong:!0}),r.fromEvent(this.socket,"open").pipe(r.takeUntil(this.destorySub)).subscribe(()=>{this.isConnected=!0,f.code(p["啟動服務"],"proxy"),this.flushMessageQueue(),this.linkStartSub.next()}),r.fromEvent(this.socket,"message").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),r.fromEvent(this.socket,"error").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{const{error:t}=e;f.error("Proxy has some problem",t)}),r.fromEvent(this.socket,"close").pipe(r.takeUntil(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s}=e;switch(t){case O.MANUAL_CLOSE:f.code(p["無副作用的關閉連線"]);break;case O.WS_5000_NORAML_CLOSURE:case O.MDN_NORMAL_CLOSURE:case O.MDN_GOING_AWAY:this.closeSub.next("close"),f.code(p["安全的關閉連線"]);break;case O.MDN_NO_STATUS_RECEIVED:case O.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),f.code(p["與雲端的服務中斷"],{code:t,reason:s});break;case O.WS_5001_UNAUTHORIZED:case O.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),f.code(p["沒有授權,請聯繫Graphen"]);break;case O.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),f.code(p["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),f.code(p["未預期的斷線,請聯繫Graphen"],{code:t,reason:s})}})}reconnect(){this.onDestroy(),this.destorySub=new r.Subject,this.start()}flushMessageQueue(){for(;this.messageQueue.length>0&&this.isConnected&&void 0!==this.socket;){const e=this.messageQueue.shift();e&&this.socket.send(e)}}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"audio"===t.signal&&"interrupted"===t.command&&(f.info("AudioInterruptSocketProxy, payload=",t),this.isConnected&&void 0!==this.socket))try{this.socket.send(t.command)}catch(e){f.error(e)}}cloudToObj(e){try{return JSON.parse(e)}catch(e){f.error("message transfer fail",e)}}onClose(){this.socket?.close(O.MANUAL_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}class w{next(e,t,...o){const i=`log_${s().format("yyyy-MM-DD")}.txt`,r=N.addPath(N.appFolder,i);try{const s=o.reduce((e,t)=>{switch(typeof t){case"string":case"number":case"bigint":return`${e} ${t.toString()}`;case"boolean":return`${e} ${t?"true":"false"}`;case"object":try{return`${e} ${JSON.stringify(t)}`}catch(t){return e}case"symbol":case"undefined":case"function":return`${e} [${typeof t}]`}},"");a.appendFileSync(r,`LOG(${e})__${t}__::${s}${N.EOL}`,{encoding:"utf8"})}catch(e){}}}class x{config;stroage;apiProxy;chat;udpProxy;audioProxy;audioIntrProxy;isStart;channelMap;destorySub;access_token;eyeBallUdp;get accessToken(){return this.access_token}constructor(e,t){this.config=e,this.stroage=t,this.isStart=!1,this.destorySub=new r.Subject,this.channelMap=new Map,this.access_token=t.getItemSync(d),this.apiProxy=new m(this.config.endPoint.api),f.code(p["指定模組初始化完成"],"api"),this.chat=new y(this.config),f.code(p["指定模組初始化完成"],"chat"),this.udpProxy=new R(this.config),f.code(p["指定模組初始化完成"],"udp"),this.audioProxy=new C,f.code(p["指定模組初始化完成"],"audio"),this.audioIntrProxy=new A,f.code(p["指定模組初始化完成"],"audio intr"),e.eyeTrackEnable&&(this.eyeBallUdp=new u.Client("127.0.0.1",6745),f.code(p["指定模組初始化完成"],"eye ball track")),this.openChannel=this.openChannel.bind(this)}async start(){if(this.isStart)throw new Error(e["重複初始化"]);this.isStart=!0,await this.replaceAccessToken(),this.chat.memberChange.pipe(r.takeUntil(this.destorySub)).subscribe(({type:e,socket:s})=>{switch(e){case"in":{const e=new D,i=new r.Subject,n=(o=s.handshake.query.uuid,t.isArray(o)?t.get(o,"[0]",void 0):o??void 0);void 0===n?(this.getProjectList().then(e=>{s.emit("list",e)}),r.fromEvent(s,"uuid").pipe(r.take(1),r.takeUntil(i)).subscribe(t=>{this.openChannel(t,s,e,i)})):this.openChannel(n,s,e,i),this.channelMap.set(s.id,()=>{i.next(),i.complete()});break}case"out":this.channelMap.has(s.id)&&(this.channelMap.get(s.id)(),this.channelMap.delete(s.id))}var o}),this.chat.start(),f.code(p["啟動服務"],"chat"),this.udpProxy.start(),f.code(p["啟動服務"],"udp")}async replaceAccessToken(){const e=await this.apiProxy.getAccessToken(this.config.license,this.access_token);return this.access_token=e,this.stroage.setItemSync(d,e),e}async getProjectList(){return await this.apiProxy.getProjectList(this.config.license)}getProxySocketURL(e){return`${this.config.endPoint.socket}/v1/chat/project/${this.accessToken}/${e}`}openChannel(e,s,i,n){r.fromEvent(s,"cloud").pipe(r.takeUntil(n)).subscribe(e=>{i.send(e)});const{x_axis_px:c,y_axis_px:a}=this.config.eyeTrackCorrection;r.fromEvent(s,"face").pipe(r.takeUntil(n)).subscribe(e=>{const s=e,r=null!==s;if(r&&void 0!==this.eyeBallUdp){const{x:e,y:i}=function(e,s){const{maxX:i,maxY:r,minX:n,minY:c}=t.assign({maxX:25,minX:-25,maxY:50,minY:-50},s),a=o.abs(i)+o.abs(n),h=o.abs(r)+o.abs(c),{clientHeight:l,clientWidth:u,originX:d,originY:S,width:b,height:p}=e,_=o.matrix([[a/u,0,n],[0,h/l,c],[0,0,1]]),g=d+b/2,E=l-(S+p/2),f=g>u?u:g<0?0:g,y=E>l?l:E<0?0:E,m=o.matrix([[f],[y],[1]]),O=o.multiply(_,m);return{x:O.get([0,0]),y:O.get([1,0])}}(s);this.eyeBallUdp.send(["/",e+c,i+a],e=>{null!=e&&f.error("eye track error:",e)})}i.sendToCloud({request:"face_detect",content:r})}),i.message.pipe(r.takeUntil(n)).subscribe(e=>{s.emit("cloud",e),this.udpProxy.send(e),this.audioProxy.send(e),this.audioIntrProxy.send(e)}),i.close.pipe(r.takeUntil(n)).subscribe(async t=>{switch(s.emit("channel",t),t){case"close":case"no_permissions":i.onDestroy();break;case"reconnect":await this.replaceAccessToken(),i.reconnect(this.getProxySocketURL(e))}}),i.linkStart.pipe(r.takeUntil(n)).subscribe(()=>{s.emit("channel","open")}),n.subscribe(()=>{i.onClose(),this.audioProxy.onClose(),this.audioIntrProxy.onClose()}),i.start(this.getProxySocketURL(e)),this.audioProxy.start(),this.audioIntrProxy.start()}onDestroy(){this.apiProxy.onDestroy(),this.chat.onDestroy(),this.udpProxy.onDestroy(),this.destorySub.next(),this.destorySub.complete(),this.eyeBallUdp?.close(),f.code(p["服務結束"])}}exports.aiiaCore=function(s){if(S){if(void 0===t.get(s,"license",void 0))throw new Error(e["未提供憑證"]);!function(e){const{info:t,error:s,warn:o,debug:i,fatal:r,code:n}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);return n||(_.code=()=>{}),i||(_.debug=()=>{}),t||(_.info=()=>{}),o||(_.warn=()=>{}),s||(_.error=()=>{}),r||(_.fatal=()=>{}),E}(t.get(s,"debug",!0))(new w),f.code(p["開始初始化必要模組"]);const o=new b(t.assign({},s));f.code(p["指定模組初始化完成"],"config");const i=new k;f.code(p["指定模組初始化完成"],"storage");const r=new x(o,i);return f.code(p["指定模組初始化完成"],"core"),r.start(),f.code(p["啟動服務"]),()=>{r.onDestroy()}}throw new Error(e["環境錯誤(nodejs)"])};
1
+ "use strict";var e=require("lodash-es"),t=require("moment"),s=require("mathjs"),r=require("socket.io"),o=require("rxjs"),n=require("axios"),i=require("ws"),a=require("fs"),c=require("os"),h=require("path"),l=require("node-osc"),u=require("child_process"),d=require("systeminformation"),p=require("@nut-tree-fork/nut-js"),f=require("readline"),g=require("net");function y(e){var t=Object.create(null);return e&&Object.keys(e).forEach(function(s){if("default"!==s){var r=Object.getOwnPropertyDescriptor(e,s);Object.defineProperty(t,s,r.get?r:{enumerable:!0,get:function(){return e[s]}})}}),t.default=e,Object.freeze(t)}var m,b=y(f);!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(m||(m={}));const S="access_token",_="undefined"!=typeof process&&null!=process.versions&&null!=process.versions.node;function w(e){return new Promise(t=>{setTimeout(t,e)})}class E{config;get webserver(){return this.safeRead("webserver")}get environment(){const e=this.safeRead("env");return"string"==typeof e?e:"developement"}get license(){return this.safeRead("license")??""}get port(){return function(e,t){const s="number"==typeof t?t:NaN;if(e){const t=Number(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.safeRead("port")??3e3)}get ws_url(){const e=this.safeRead("ws_url");return"string"==typeof e?e:""}get worklet_url(){const e=this.safeRead("worklet_url");return"string"==typeof e?e:""}get project(){const t=this.safeRead("project");if(void 0===t)return{specific:!1};if("string"==typeof t)return{name:t,id:t,specific:!1};{const s=e.get(t,"id",void 0),r=e.get(t,"name",void 0);return{id:"string"==typeof s?s:void 0,name:"string"==typeof r?r:void 0,specific:!0}}}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}const t=`graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`;return{api:`https://${t}`,socket:`wss://${t}`}}get debug(){return this.safeRead("debug")??!0}get mediaStream(){return this.safeRead("mediaStream")}get chunkTimeInSeconds(){const e=this.safeRead("chunkTimeInSeconds");return"number"==typeof e&&e>0?e:.3}get llmSampleRate(){const e=this.safeRead("llmSampleRate");return"number"==typeof e&&e>0?e:16e3}get detection(){return e.assign({perSecond:3,confidence:80},this.safeRead("faceDetection"),this.safeRead("cameraDetection"))}get autoClearSubtitle(){const e=this.safeRead("autoClearUserSubtitle"),t=this.safeRead("autoClearAiiaSubtitle");return{userDelayTime:"number"==typeof e?e:5e3,aiiaDelayTime:"number"==typeof t?t:NaN}}get eyeTrackEnable(){return Boolean(this.safeRead("eyeTrackEnable"))}get eyeTrackCorrection(){return e.assign({x_axis_px:0,y_axis_px:0},this.safeRead("correction"))}constructor(e){this.config=e}safeRead(t){return e.get(this.config,t,void 0)}}var k;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["已啟動SDK"]="CODE: 1014",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線雲端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(k||(k={}));const D={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},O=[];function N(e){return O.push(e),N}const R=new Proxy(D,{get(s,r,o){if("string"==typeof r)switch(r){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...o)=>{const n=t().format("yyyy-MM-DD[T]HH:mm:ss:sss");e.forEach(O,e=>{e.next(r,n,...o)}),s[r](`[Aiia::${r}_${n}_]`,...o)};default:return}},set:(e,t,s,r)=>!0});class C{config;server;memberSub;get port(){return this.config.port}get webserver(){return this.config.webserver}get memberChange(){return this.memberSub.asObservable()}constructor(e){this.config=e,this.memberSub=new o.Subject}start(){if(this.server=new r.Server(this.webserver,{cors:{origin:"*"}}),this.server.on("connection",e=>{R.debug("client on",e.id),this.memberSub.next({type:"in",socket:e}),e.on("disconnect",t=>{R.debug("client off",e.id,"reason",t),this.memberSub.next({type:"out",socket:e})})}),void 0===this.webserver){const e=this.port;this.server.listen(e),R.info("Websocket server run on",e)}else R.info("Websocket server rely your webserver.")}onDestroy(){this.server&&this.server.close()}}class A{instance;controller;constructor(e){this.controller=new AbortController,this.instance=n.create({baseURL:e,signal:this.controller.signal})}async getAccessToken(t,s){const r=performance.now(),o={license_number:t,token_value:s??""},n=await this.instance.post("/api/v1/license/agent_token",o).then(t=>e.get(t.data,"token_value","")).catch(()=>null);if(R.debug("Complete certification",`Took ${performance.now()-r} milliseconds.`),null===n)throw new Error(m["網絡錯誤"]);if(""===n)throw new Error(m["獲取權杖失敗"]);return n}async getProjectList(t){const s=performance.now(),r=await this.instance.post("/api/v1/license/projects",{license_number:t}).then(t=>e.map(e.get(t,"data.projects",[]),({id:e,sub_project_name:t,avatar:s})=>{const{name:r,url:o}=s;return{avatar:{name:r,url:o},id:e,name:t}})).catch(e=>null);if(R.debug("Got Project List",`Took ${performance.now()-s} milliseconds.`),null===r)throw new Error(m["網絡錯誤"]);if(0===r.length)throw new Error(m["沒有可用專案"]);return r}onDestroy(){this.controller.abort()}}var M;!function(e){e[e.MDN_NORMAL_CLOSURE=1e3]="MDN_NORMAL_CLOSURE",e[e.MDN_GOING_AWAY=1001]="MDN_GOING_AWAY",e[e.MDN_PROTOCOL_ERROR=1002]="MDN_PROTOCOL_ERROR",e[e.MDN_UNSUPPORTED_DATA=1003]="MDN_UNSUPPORTED_DATA",e[e.MDN_RESERVED_1004=1004]="MDN_RESERVED_1004",e[e.MDN_NO_STATUS_RECEIVED=1005]="MDN_NO_STATUS_RECEIVED",e[e.MDN_ABNORMAL_CLOSURE=1006]="MDN_ABNORMAL_CLOSURE",e[e.MDN_INVALID_FRAME_PAYLOAD=1007]="MDN_INVALID_FRAME_PAYLOAD",e[e.MDN_POLICY_VIOLATION=1008]="MDN_POLICY_VIOLATION",e[e.MDN_MESSAGE_TOO_BIG=1009]="MDN_MESSAGE_TOO_BIG",e[e.MDN_MANDATORY_EXTENSION=1010]="MDN_MANDATORY_EXTENSION",e[e.MDN_INTERNAL_ERROR=1011]="MDN_INTERNAL_ERROR",e[e.MDN_SERVICE_RESTART=1012]="MDN_SERVICE_RESTART",e[e.MDN_TRY_AGAIN_LATER=1013]="MDN_TRY_AGAIN_LATER",e[e.MDN_BAD_GATEWAY=1014]="MDN_BAD_GATEWAY",e[e.MDN_TLS_HANDSHAKE=1015]="MDN_TLS_HANDSHAKE",e[e.WHEN_BROWSER_CLOSE=4001]="WHEN_BROWSER_CLOSE",e[e.WS_4100_NORAML_CLOSURE=4100]="WS_4100_NORAML_CLOSURE",e[e.WS_4101_UNAUTHORIZED=4101]="WS_4101_UNAUTHORIZED",e[e.WS_4102_LICENSE_EXPIRED=4102]="WS_4102_LICENSE_EXPIRED",e[e.WS_4103_TOKEN_EXPIRED=4103]="WS_4103_TOKEN_EXPIRED",e[e.WS_4104_LLM_SESSION_ERROR=4104]="WS_4104_LLM_SESSION_ERROR",e[e.WS_4105_TOO_MANY_CONNECTIONS=4105]="WS_4105_TOO_MANY_CONNECTIONS"}(M||(M={}));class P{id;uuid;socket;messageSub;linkStartSub;destorySub;closeSub;isFinished;debugInfo;lastCloseLog;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}get isConnected(){return this.socket?.readyState===i.OPEN}constructor(e){this.id=e,this.messageSub=new o.Subject,this.linkStartSub=new o.Subject,this.closeSub=new o.Subject,this.destorySub=new o.Subject,this.isFinished=!1,this.uuid=crypto.randomUUID(),this.debugInfo=`browser(${this.id}), proxy(${this.uuid})`,R.debug(`create proxy for browser(${this.id})`)}start(e){this.socket=new i(e,{autoPong:!0}),o.fromEvent(this.socket,"open").pipe(o.takeUntil(this.destorySub)).subscribe(()=>{R.code(k["啟動服務"],"proxy",this.debugInfo),this.linkStartSub.next()}),o.fromEvent(this.socket,"message").pipe(o.takeUntil(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),o.fromEvent(this.socket,"error").pipe(o.takeUntil(this.destorySub)).subscribe(e=>{const{error:t}=e;R.error(this.debugInfo,"Proxy has some problem",t)}),o.fromEvent(this.socket,"close").pipe(o.takeUntil(this.destorySub)).subscribe(e=>{const{code:t,reason:s,type:r,wasClean:o}=e;switch(this.lastCloseLog=`最近一次關閉原因: ${s}, 錯誤碼: ${t}, Type: ${r}`,t){case M.WHEN_BROWSER_CLOSE:R.code(k["無副作用的關閉連線"],this.debugInfo),this.onDestroy();break;case M.MDN_NORMAL_CLOSURE:case M.MDN_GOING_AWAY:this.closeSub.next("close"),R.code(k["安全的關閉連線"],this.debugInfo);break;case M.MDN_NO_STATUS_RECEIVED:case M.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),R.code(k["與雲端的服務中斷"],this.debugInfo,{code:t,reason:s,type:r,wasClean:o});break;case M.WS_4100_NORAML_CLOSURE:case M.WS_4103_TOKEN_EXPIRED:case M.WS_4104_LLM_SESSION_ERROR:case M.WS_4105_TOO_MANY_CONNECTIONS:this.closeSub.next("reconnect"),R.code(k["自動重新連線雲端服務"],this.debugInfo,`socket code: ${t}`);break;case M.WS_4101_UNAUTHORIZED:case M.WS_4102_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),R.code(k["沒有授權,請聯繫Graphen"],this.debugInfo,`socket code: ${t}`);break;default:this.closeSub.next("close"),R.code(k["未預期的斷線,請聯繫Graphen"],this.debugInfo,{code:t,reason:s,type:r,wasClean:o})}})}reconnect(e){this.isFinished||(this.destorySub.next(),this.destorySub.complete(),this.destorySub=new o.Subject,this.start(e))}send(e){const s=`currentTime: ${t().format("yyyy-MM-DD[T]HH:mm:ss:sss")}`;this.isConnected?this.socket.send(e,t=>{t&&R.error("send message fail",this.debugInfo,s,e.slice(0,100),t)}):R.warn("The socket is not connected",this.debugInfo,s,e.slice(0,100))}sendToCloud(e){this.send(JSON.stringify(e))}onClose(){if(this.socket)switch(this.socket.readyState){case 0:R.debug("Once the cloud connection is complete, the connection will be disconnected."),this.linkStart.pipe(o.takeUntil(this.destorySub),o.take(1)).subscribe(()=>{this.socket?.close(M.WHEN_BROWSER_CLOSE)});break;case 1:R.debug("Close safely, no side effects"),this.socket.close(M.WHEN_BROWSER_CLOSE);break;case 2:case 3:R.debug("The connection have been lost.")}}getStatusInfo(){const e=(()=>{if(!this.socket)return"雲端服務尚未啟動";{const e=this.socket.readyState;switch(e){case 0:return"正在連線雲端服務中";case 1:return"雲端服務正常運作";case 2:return"正在關閉雲端服務";case 3:return"雲端服務已關閉";default:return`未知的 Websocket 狀態: ${e}`}}})(),t=this.lastCloseLog?`${e}, ${this.lastCloseLog}`:e;return`瀏覽器(${this.id}) - 雲端代理(${this.uuid}), ${t}`}onDestroy(){this.isFinished=!0,this.destorySub.next(),this.destorySub.complete(),R.debug(`Destroy cloud channel(${this.debugInfo})`)}}const T=new class{AppName;platform;isMAC;isWIN;isLINUX;saveFolder;homedir;appFolder;EOL;constructor(e){switch(this.AppName=e,this.platform=process.platform,this.homedir=c.homedir(),this.EOL=c.EOL,this.platform){case"darwin":this.isMAC=!0,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=h.join(this.homedir,"Library","Application Support");break;case"win32":this.isMAC=!1,this.isWIN=!0,this.isLINUX=!1,this.saveFolder=process.env.APPDATA||h.join(this.homedir,"AppData","Roaming");break;case"linux":this.isMAC=!1,this.isWIN=!1,this.isLINUX=!0,this.saveFolder=process.env.XDG_CONFIG_HOME||h.join(this.homedir,".config");break;default:console.log(`當前系統是未知的平台: ${this.platform}`),this.isMAC=!1,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=this.homedir}this.appFolder=h.join(this.saveFolder,this.AppName),this.folderCheck(this.appFolder)}addPath(...e){return h.join(...e)}folderCheck(e){a.mkdirSync(e,{recursive:!0})}createFolder(e){const t=this.addPath(this.appFolder,e);return this.folderCheck(t),t}fileCheck(e,t=""){a.existsSync(e)||a.writeFileSync(e,t,{encoding:"utf8"})}}("aiia-sdk");class v{type="node";filePath;fileName;constructor(){this.fileName="storage.json",this.filePath=T.addPath(T.appFolder,this.fileName),T.fileCheck(this.filePath)}getFileDataSync(){const e=a.readFileSync(this.filePath,"utf8");try{return JSON.parse(e)}catch(e){return{}}}setFileDataSync(e){try{const t=JSON.stringify(e);a.writeFileSync(this.filePath,t,"utf8")}catch(e){}}async getFileData(){return new Promise(e=>{a.readFile(this.filePath,"utf8",(t,s)=>{if(t)e({});else try{e(JSON.parse(s))}catch(t){e({})}})})}async setFileData(e){return new Promise(t=>{try{const s=JSON.stringify(e);a.writeFile(this.filePath,s,"utf8",e=>{t()})}catch(e){}})}async getItem(e){return(await this.getFileData())[e]??null}async setItem(e,t){const s=await this.getFileData();s[e]=t,await this.setFileData(s)}async removeItem(t){const s=await this.getFileData(),r=e.omit(s,t);await this.setFileData(r)}async clear(){await this.setFileData({})}getItemSync(e){return this.getFileDataSync()[e]??null}setItemSync(e,t){const s=this.getFileDataSync();s[e]=t,this.setFileDataSync(s)}removeItemSync(t){const s=e.omit(this.getFileDataSync(),t);this.setFileDataSync(s)}clearSync(){this.setFileDataSync({})}}const x={[k["通道、雲端服務、麥克風都已就緒"]]:"通道、雲端服務、麥克風都已就緒",[k["未取得媒體裝置權限,請重新設定或忽略此訊息"]]:"未取得媒體裝置權限,請重新設定或忽略此訊息",[k["請訂閱專案列表"]]:"請訂閱專案列表",[k["初始化必要依賴"]]:"初始化必要依賴",[k["指定依賴已完成"]]:"指定依賴已完成",[k["啟動SDK"]]:"啟動SDK",[k["結束SDK"]]:"結束SDK",[k["已啟動SDK"]]:"已啟動SDK",[k["安全的關閉連線"]]:"安全的關閉連線",[k["無副作用的關閉連線"]]:"無副作用的關閉連線",[k["與雲端的服務中斷"]]:"與雲端的服務中斷",[k["沒有授權,請聯繫Graphen"]]:"沒有授權,請聯繫Graphen",[k["自動重新連線雲端服務"]]:"自動重新連線雲端服務",[k["未預期的斷線,請聯繫Graphen"]]:"未預期的斷線,請聯繫Graphen",[k["開始初始化必要模組"]]:"開始初始化必要模組",[k["指定模組初始化完成"]]:"指定模組初始化完成",[k["啟動服務"]]:"啟動服務",[k["服務結束"]]:"服務結束",[m["網絡錯誤"]]:"網絡錯誤",[m["環境錯誤(browser)"]]:"環境錯誤",[m["Worklet模組載入失敗"]]:"Worklet模組載入失敗",[m["未獲得媒體裝置權限"]]:"未獲得媒體裝置權限",[m["實例已摧毀"]]:"實例已摧毀",[m["環境錯誤(nodejs)"]]:"nodejs",[m["未提供憑證"]]:"未提供憑證",[m["獲取權杖失敗"]]:"獲取權杖失敗",[m["沒有可用專案"]]:"沒有可用專案",[m["重複初始化"]]:"重複初始化"},I=new RegExp(/^(ERROR )?CODE: [0-9]{3,4}$/);class L{path;constructor(e){this.path=e?T.createFolder(e):T.appFolder}next(e,s,...r){const o=`log_${t().format("yyyy-MM-DD")}.txt`,n=T.addPath(this.path,o),i="code"===e?e=>I.test(e)?`${e}(${x[e]})`:e:e=>e;try{const t=r.reduce((e,t)=>{if(t instanceof Error){let s=`Error[${t.message??"No Message"}]${T.EOL}`;return s+=t.stack??"unknow stack","string"==typeof t.cause&&(s+=`${T.EOL}cause: ${t.cause}`),`${e}${T.EOL}${s}${T.EOL}`}switch(typeof t){case"string":return`${e} ${i(t)}`;case"number":case"bigint":return`${e} ${t.toString()}`;case"boolean":return`${e} ${t?"true":"false"}`;case"object":try{return`${e} ${JSON.stringify(t)}`}catch(t){return e}case"symbol":case"undefined":case"function":return`${e} [${typeof t}]`}},"");a.appendFileSync(n,`LOG(${e})__${s}__::${t}${T.EOL}`,{encoding:"utf8"})}catch(e){}}}const $="127.0.0.1",U={AvatarPort:"9876",ActionPort:"6969",LocationPort:"4060",LookPort:"6745",BackGroundPort:"2400"},W={StreamAudioPort:"8080",CleanAudioPort:"8081"};function F(){return{...U,...W}}class j{clientMap;eyeBallUdp;x_axis_px;y_axis_px;defPortMap;constructor(){this.clientMap=new Map,this.x_axis_px=0,this.y_axis_px=0,this.defPortMap={};const t=e.pick(U,"AvatarPort","ActionPort","LocationPort","BackGroundPort");e.forEach(e.keys(t),e=>{const s=e,r=t[s];this.defPortMap[r]=s})}enableUDPClient(t){e.forEach(e.keys(t),e=>{const s=new l.Client($,t[e]);this.clientMap.set(e,s)})}enableEyeTrack(e){this.eyeBallUdp=new l.Client($,e.port),this.x_axis_px=e.x_axis_px,this.y_axis_px=e.y_axis_px}eyeTrack(t){if(void 0!==this.eyeBallUdp){const r=null!==t,{x:o,y:n}=(()=>{if(r){const{x:r,y:o}=function(t,r){const{maxX:o,maxY:n,minX:i,minY:a}=e.assign({maxX:25,minX:-25,maxY:50,minY:-50},r),c=s.abs(o)+s.abs(i),h=s.abs(n)+s.abs(a),{clientHeight:l,clientWidth:u,originX:d,originY:p,width:f,height:g}=t,y=s.matrix([[c/u,0,i],[0,h/l,a],[0,0,1]]),m=d+f/2,b=l-(p+g/2),S=s.matrix([[m],[b],[1]]),_=s.multiply(y,S);return{x:_.get([0,0]),y:_.get([1,0])}}(t);return{x:this.toFloat(r+this.x_axis_px),y:this.toFloat(o+this.y_axis_px)}}return{x:this.toFloat(7),y:this.toFloat(20)}})();this.eyeBallUdp.send(["/",o,n],e=>{null!=e&&R.error("Eye track error:",e)})}}movement(t){e.map(t,({content:t})=>{const s=e.get(t,"port",""),r=e.get(t,"message"),o=this.defPortMap[s.toString()];this.clientMap.has(o)&&this.clientMap.get(o).send(r,e=>{null!=e&&R.error("Occur error when send message to UE",e)})})}toFloat(t){return e.isInteger(t)?t+1e-5:t}onDestroy(){e.forEach(Array.from(this.clientMap),([e,t])=>{t.close()}),R.info("Close all UnrealEngine's UDP")}}class G{destroy;speechWS;interruptWS;get speechIsConnected(){return this.speechWS?.readyState===i.OPEN}get intrIsConnected(){return this.interruptWS?.readyState===i.OPEN}constructor(){this.destroy=new o.Subject}connectSocket(e){this.speechWS=this.getSocket(e.speech,"speech"),this.interruptWS=this.getSocket(e.interrupt,"interrupt")}getSocket(e,t){const s=new i(`ws://${$}:${e}`);return s.onerror=s=>{const{message:r,error:o,type:n}=s;R.error(`UE Tool(${t}) fail: ${r}`,o,{port:e,type:n})},s.onclose=s=>{const{code:r,reason:o,type:n}=s;R.debug(`UE Tool(${t}) close`,{code:r,reason:o,type:n,port:e})},s}interrupted(){this.intrIsConnected&&this.interruptWS.send("interrupted")}speech(e){if(this.speechIsConnected){const t=Buffer.allocUnsafe(2*e.length);for(let s=0;s<e.length;s++)t.writeInt16LE(e[s],2*s);this.speechWS.send(t)}}onDestroy(){this.speechWS?.close(),this.interruptWS?.close(),this.destroy.next(),this.destroy.complete(),R.info("Close all UnrealEngine's WebSocket")}}class B{config;udpManager;socketManager;movement;eyeTrack;interrupted;speech;constructor(e){this.config=e,this.udpManager=new j,this.socketManager=new G,this.movement=()=>{},this.eyeTrack=()=>{},this.interrupted=()=>{},this.speech=()=>{}}onStart(t){const s={},r=F(),o=e.assign(F(),t);e.forEach(e.keys(r),e=>{const t=o[e],n=Number(t);s[e]=isNaN(n)?Number(r[e]):n});const{StreamAudioPort:n,CleanAudioPort:i,LookPort:a,...c}=s;this.udpManager.enableUDPClient(c),this.socketManager.connectSocket({speech:n,interrupt:i}),this.movement=this.udpManager.movement.bind(this.udpManager),this.eyeTrack=this.udpManager.eyeTrack.bind(this.udpManager),this.interrupted=this.socketManager.interrupted.bind(this.socketManager),this.speech=this.socketManager.speech.bind(this.socketManager),this.config.eyeTrackEnable&&this.udpManager.enableEyeTrack({...this.config.eyeTrackCorrection,port:a})}onDestroy(){this.udpManager.onDestroy(),this.socketManager.onDestroy(),R.info("UE tools destroy")}}async function Y(e){return new Promise(t=>{a.stat(e,e=>{t(e?[!1,e]:[!0])})})}async function H(e,t,...s){return T.isWIN?new Promise(r=>{const o=[`cd ${e} && ${t} `,...s].join(" "),n=u.exec(o);n.once("error",e=>{r([!1,e])}),n.once("spawn",()=>{n.unref(),r([!0])})}):Promise.resolve([!1,new Error("The function isn't implement in this system")])}async function K(){try{return await d.networkConnections()}catch(e){return console.error("檢查 Port 時發生錯誤:",e),[]}}async function X(t){const s=t.toString(),r=await K();return e.some(r,e=>e.localPort===s)}async function q(e){const t=await p.getWindows();let s=null;const r=new RegExp(e.toLocaleLowerCase());for(const e of t){const t=await e.title;if(r.test(t.toLocaleLowerCase())){s=e;break}}return s}function V(e){return[2e3,2e3,2e3,1e3,1e3,2e3,3e3,5e3][e]??[6e3,4e3][e%2]}function J(){if(T.isWIN){const e="UEProject_5_6";return{proc:e,name:`${e}.exe`}}return null}class Z{config;ueConfig;destroySub;constructor(t){this.destroySub=new o.Subject;const s={isKiosk:!1,browserKeeping:!1},{unrealFolder:r,openUrl:n,mode:i,foreground:a,remoteDebug:c}=e.assign({},t);if("string"==typeof r&&""!==r.trim()){s.path=r;const e=J();e&&(this.ueConfig=new z(r,e.proc))}"string"==typeof n&&""!==n.trim()&&(s.url=n),"string"==typeof i&&""!==i.trim()&&(s.isKiosk="kiosk"===i),"string"==typeof a&&""!==a.trim()&&(s.browserKeeping="always"===a),"number"==typeof c&&(s.remoteDebug=c),this.config=s}async exec(){let e=!1,t=await this.isUnrealRunning();t||(this.ueConfig&&await this.ueConfig.getSafePortTable(),t=await this.openUnreal()),t&&(e=await this.isUnrealInService()),e&&await this.openBrowser();return this.ueConfig?await this.ueConfig.getPortTable():F()}async isUnrealRunning(){const t=J();if(null===t)return!1;const{proc:s}=t,r=await async function(...e){const t=e.length>0?e.join(", "):"*";return await d.processLoad(t).catch(()=>[])}(s);if(r.length<=0)return!1;const o=s.toLocaleLowerCase(),n=e.find(r,e=>e.proc.toLocaleLowerCase()===o);return!!(n&&"number"==typeof n.pid&&n.pids.length>=2)}async isUnrealInService(e=12){let t=!1,s=0;const{StreamAudioPort:r}=this.ueConfig?await this.ueConfig.getPortTable():F();for(;!t&&s<e;)t=await X(r),await w(V(s)),s++;return t}async openUnreal(){const e=J();if(null===e)return!1;const{path:t}=this.config;if(void 0===t)return!1;const s=T.addPath(t,e.name),[r,o]=await Y(s);if(!r)return R.error(o),!1;const[n,i]=await async function(e,...t){return T.isWIN?new Promise(s=>{const r=u.spawn(e,t,{detached:!0,stdio:"ignore"});r.once("error",e=>{s([!1,e])}),r.once("spawn",()=>{r.unref(),s([!0])})}):Promise.resolve([!1,new Error("The function isn't implement in this system")])}(s);return i&&R.error(i),n}async openBrowser(){const{url:e,isKiosk:t,remoteDebug:s}=this.config;if(void 0===e)return!1;const r=T.isWIN?{edge:["C:/Program Files (x86)/Microsoft/Edge/Application","msedge.exe"],chrome:["C:/Program Files/Google/Chrome/Application","chrome.exe"],chrome2:[`${T.homedir}/AppData/Local/Google/Chrome/Application`,"chrome.exe"]}:null;if(null===r)return!1;const{proc:o}=J(),{chrome:n,chrome2:i,edge:a}=r,[[c],[h],[l]]=await Promise.all([Y(T.addPath(...n)),Y(T.addPath(...a)),Y(T.addPath(...i))]);if(c||l){const r=[];if(t&&(r.push("--kiosk"),r.push(`--auto-select-desktop-capture-source="${o}"`)),void 0!==s){r.push(`--remote-debugging-port=${s}`);const e=T.createFolder("google");r.push(`--user-data-dir=${e}`)}r.push(e);const[a,h]=c?n:i,[l,u]=await H(a,h,...r);return l?(this.setAppInForeground("chrome"),!0):(R.error(u),!1)}if(h){const r=[];if(t&&(r.push("--kiosk"),r.push(`--auto-select-desktop-capture-source="${o}"`)),void 0!==s){r.push(`--remote-debugging-port=${s}`);const e=T.createFolder("edge");r.push(`--user-data-dir=${e}`)}r.push(e);const[n,i]=a,[c,h]=await H(n,i,...r);return c?(this.setAppInForeground("edge"),!0):(R.error(h),!1)}return R.info("Can't find browser"),!1}async setAppInForeground(e){await w(3e3);const t=await q(e);t?.focus(),this.config.browserKeeping&&o.interval(1e4).pipe(o.takeUntil(this.destroySub)).subscribe(()=>{q(e).then(e=>{e?.focus()})})}async getStateInfo(){const e=await this.isUnrealRunning();return{isOpen:e,isReady:!!e&&await this.isUnrealInService(1)}}onDestroy(){this.destroySub.next(),this.destroySub.complete()}}class z{folder;file;constructor(e,t){this.folder=T.addPath(e,t,"config"),this.file=T.addPath(this.folder,"config.json"),T.folderCheck(this.folder),T.fileCheck(this.file,JSON.stringify(F()))}async getSafePortTable(){const t=F(),s=await K(),r=await async function(t,s){const r=s??await K(),o={};return e.forEach(e.keys(t),s=>{const n=e.some(r,({localPort:e})=>e===t[s]);o[s]=n}),o}(t,s);if(e.some(e.values(r),e=>e)){const o=new Set(e.filter(e.map(s,e=>Number(e.localPort)),e=>!isNaN(e)));e.forEach(e.keys(r),e=>{const s=e;if(r[s]){const e=Number(t[s]);let r=1,n=NaN;for(;isNaN(n)&&r<=200;){const t=e+r;o.has(t)?r++:(o.add(t),n=t)}isNaN(n)||(t[s]=n.toString())}})}await this.setPortTable(t)}getPortTable(){return new Promise(e=>{a.readFile(this.file,"utf8",(t,s)=>{if(t)e(F());else try{e(JSON.parse(s))}catch(t){e(F())}})})}async setPortTable(e){try{const t=JSON.stringify(e);await new Promise(e=>{a.writeFile(this.file,t,"utf8",t=>{e()})})}catch(e){}}}function Q(...e){switch(e[0]){case"--status":return"服務狀態確認\n 用法: status [額外參數]\n 範例:\n status\n";case"--log":return"日誌讀取(無參數時,會返回可用日誌)\n 用法: log [額外參數]\n\n 範例:\n log --y\n log --yesterday\n log --specific=2025-11-28\n 可用參數:\n today : 讀取今天的日誌\n t : 讀取今天的日誌\n yesterday : 讀取昨天的日誌\n y : 讀取昨天的日誌\n specific=<yyyy-MM-dd> : 讀取指定日期的日誌\n s=<yyyy-MM-dd> : 讀取指定日期的日誌\n";case"--exit":case"--close":return"安全離開\n 用法 1: exit\n 用法 2: close\n";default:return"用法: [可用指令] [額外參數]\n 範例:\n help\n help --log\n 可用指令列表:\n help : 獲得可用的指令,使用 [--可用指令] 獲得該指令的詳細說明。\n status : 顯示當前的服務狀態\n log : 讀取今天的日誌\n exit : 關閉連線\n close : exit 的別稱,關閉連線\n"}}function ee(e){return async(...t)=>{const{core:s,channel:r}=e.getStatusInfo(),o=r.reduce((e,t,s)=>e+` channel ${s+1} - ${t}\n`,"\n");let n=`服務狀態\n 核心狀態 : ${s}\n 通道列表 : ${0===r.length?"無服務通道":o}\n`;if(e.automaion){const{isOpen:t,isReady:s}=await e.automaion.getStateInfo();n+=" Unreal : ",n+=t?s?"已啟動,服務已就緒":"已啟動,但服務未就緒":"尚未啟動",n+="\n"}return n}}function te(...e){const s=e[0];if(!s)return new Promise(e=>{a.readdir(T.appFolder,(t,s)=>{e(t?"沒有可用日誌":s.filter(e=>e.endsWith(".txt")).join("\n"))})});{let e=null;switch(s){case"--today":case"--t":e=t();break;case"--yesterday":case"--y":e=t().add(-1);break;default:{const r=new RegExp(/^--(specific|s)=([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})$/),o=s.match(r);if(o){const[s,r,n]=o[2].split("-");e=t(`${s}-${r.padStart(2,"0")}-${n.padStart(2,"0")}`);break}return"請使用 -- 開始,或使用 help --log 查看幫助"}}if(!e.isValid())return"指定了無效的日期,請再次確認";{const t=`log_${e.format("yyyy-MM-DD")}.txt`,s=T.addPath(T.appFolder,t);try{return a.readFileSync(s,"utf8")}catch(e){return"找不到日誌"}}}}class se{server;constructor(t,s){const r=function(e){return e?{help:Q,status:ee(e),log:te}:{}}(s),{port:o,host:n}=t;this.server=g.createServer(t=>{console.log(`⚙️ 新的連線來自: ${t.remoteAddress}`);const s=b.createInterface({input:t,output:t,prompt:"Aiia Admin > "});t.setEncoding("utf8"),t.write("歡迎使用Aiia Admin!\n"),t.write("使用 `help` 獲得幫助\n"),s.prompt(),s.on("line",async o=>{const[n,...i]=o.trim().split(/\s/),a=n.toLocaleLowerCase();if("exit"===a||"close"===a)t.write("👋 切斷連線...\n"),t.end();else{const o=e.get(r,a,null);if(o){const e=await o(...i);t.write("\n"+e+"\n")}else t.write(`"${n}"是無效的指令.\n`);s.prompt()}}),t.on("end",()=>{console.log(`⚙️ 切斷連線: ${t.remoteAddress}`)})}),this.server.listen(o,n,()=>{console.log(`連線指令: telnet 127.0.0.1 ${o}`)})}onDestroy(){this.server.close()}}class re{config;stroage;expiment;apiProxy;chat;ueTools;automaion;state;browserSocketMap;destorySub;access_token;timestamp;socketAddr;get accessToken(){return this.access_token}constructor(e,t,s){this.config=e,this.stroage=t,this.expiment=s,this.state=0,this.destorySub=new o.Subject,this.browserSocketMap=new Map,this.timestamp=0,this.access_token=t.getItemSync(S);const{api:r,socket:n}=e.endPoint;this.socketAddr=n,this.apiProxy=new A(r),R.code(k["指定模組初始化完成"],"api"),this.chat=new C(e),R.code(k["指定模組初始化完成"],"chat"),this.ueTools=new B(e),R.code(k["指定模組初始化完成"],"udp tools"),this.openChannel=this.openChannel.bind(this),this.initService()}async start(){if(this.state>=1)throw new Error(m["重複初始化"]);this.state=1,await this.replaceAccessToken(),this.chat.memberChange.pipe(o.takeUntil(this.destorySub)).subscribe(({type:t,socket:s})=>{const r=s;switch(t){case"in":{const t=new P(s.id),i=new o.Subject;r.__cloudProxy=t;const a=(n=s.handshake.query.uuid,e.isArray(n)?e.get(n,"[0]",void 0):n??void 0);void 0===a?(this.getProjectList().then(e=>{s.emit("list",e)}).catch(e=>{R.error(e),s.emit("list",null)}),o.fromEvent(s,"uuid").pipe(o.take(1),o.takeUntil(i)).subscribe(e=>{this.openChannel(e,r,t,i)})):this.openChannel(a,r,t,i),this.browserSocketMap.set(r,i);break}case"out":if(this.browserSocketMap.has(r)){const e=this.browserSocketMap.get(r);e.next(),e.complete(),this.browserSocketMap.delete(r)}}var n}),this.chat.start(),R.code(k["啟動服務"],"chat");const{interaction:t}=e.assign({},this.expiment);if(void 0!==t&&"number"==typeof t.port){const{port:e,host:s}=t,r=new se({port:e,host:s},this);this.destorySub.subscribe(()=>{r.onDestroy()})}}initService(){const{automaion:t}=e.assign({},this.expiment);if(void 0!==t){const e=new Z(t);e.exec().then(e=>{this.ueTools.onStart(e)}),this.destorySub.subscribe(()=>{e.onDestroy()}),this.automaion=e}else this.ueTools.onStart();o.interval(18e4).pipe(o.takeUntil(this.destorySub)).subscribe(()=>{this.inspectClientSocket()})}async replaceAccessToken(){try{const e=await this.apiProxy.getAccessToken(this.config.license,this.access_token),s=this.access_token;this.access_token=e,this.timestamp=0,this.stroage.setItemSync(S,e),this.stroage.setItemSync(t().format("yyyy-MM-DD[T]HH:mm:ss:sss"),s)}catch(e){throw R.error(e),e}}async getProjectList(){try{return await this.apiProxy.getProjectList(this.config.license)}catch(e){throw R.error(e),e}}async getProxySocketURL(e){const t=Date.now()-this.timestamp;return t<5e3&&await w(5100-t),this.timestamp=Date.now(),`${this.socketAddr}/v1/chat/project/${this.accessToken}/${e}`}async openChannel(t,s,r,n){o.fromEvent(s,"cloud").pipe(o.takeUntil(n)).subscribe(e=>{r.sendToCloud(e)}),o.fromEvent(s,"face").pipe(o.takeUntil(n)).subscribe(e=>{const t=e,s=null!==t;this.ueTools.eyeTrack(t),r.sendToCloud({request:"face_detect",content:s})}),r.message.pipe(o.takeUntil(n)).subscribe(t=>{const r=function(t){try{const s=JSON.parse(t);return e.get(s,"signal")?[s]:e.isArray(s)?s:e.values(s)}catch(e){return R.error("message transfer fail",e),[]}}(t),o=[],n=[];e.forEach(r,e=>{switch(e.signal){case"osc":n.push(e);break;case"audio":"pcm"===e.command?this.ueTools.speech(e.content):"interrupted"===e.command&&this.ueTools.interrupted();default:o.push(e)}}),o.length>0&&s.emit("cloud",o),n.length>0&&this.ueTools.movement(n)}),r.close.pipe(o.takeUntil(n)).subscribe(async e=>{switch(s.emit("channel",e),e){case"close":case"no_permissions":r.onDestroy();break;case"reconnect":try{await this.replaceAccessToken();const e=await this.getProxySocketURL(t);s.connected?r.reconnect(e):R.error(`The browser(${r.id}) leaves before reconnecting to the cloud`)}catch(e){r.onDestroy(),s.emit("channel","net_error"),R.error(e)}}}),r.linkStart.pipe(o.takeUntil(n)).subscribe(()=>{s.emit("channel","open")}),n.subscribe(()=>{r.onClose()});const i=await this.getProxySocketURL(t);s.connected?r.start(i):R.error(`The browser(${r.id}) leaves before connecting to the cloud`)}inspectClientSocket(){const t=Array.from(this.browserSocketMap);e.forEach(t,([e,t])=>{e.disconnected&&(t.next(),t.complete(),this.browserSocketMap.delete(e),R.info(`The browser(${e.id}) should be closed. Auto clean service.`))})}getStatusInfo(){return{core:0===this.state?"未啟動":1===this.state?"服務中":"已結束",channel:Array.from(this.browserSocketMap).map(([e])=>e.__cloudProxy.getStatusInfo())}}onDestroy(){this.state=2,this.apiProxy.onDestroy(),this.chat.onDestroy(),this.ueTools.onDestroy(),this.destorySub.next(),this.destorySub.complete(),R.code(k["服務結束"])}}exports.aiiaCore=function(t){if(_){if("string"!=typeof e.get(t,"license",void 0))throw new Error(m["未提供憑證"]);!function(e){const{info:t,error:s,warn:r,debug:o,fatal:n,code:i}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);return i||(D.code=()=>{}),o||(D.debug=()=>{}),t||(D.info=()=>{}),r||(D.warn=()=>{}),s||(D.error=()=>{}),n||(D.fatal=()=>{}),N}(e.get(t,"debug",!0))(new L),R.code(k["開始初始化必要模組"]);const s=new E(e.assign({},t));R.code(k["指定模組初始化完成"],"config");const r=new v;R.code(k["指定模組初始化完成"],"storage");const o=new re(s,r,e.get(t,"experiment"));return R.code(k["指定模組初始化完成"],"core"),o.start(),R.code(k["啟動服務"],"main core"),()=>{o.onDestroy()}}throw new Error(m["環境錯誤(nodejs)"])};
package/dist/node.d.ts CHANGED
@@ -15,6 +15,32 @@ export declare interface AiiaProjectItem {
15
15
  name: string;
16
16
  }
17
17
 
18
+ /**
19
+ * [zh]
20
+ * 自動啟動完整服務
21
+ *
22
+ * @property options.unrealFolder 放置應用程式的相對或絕對路徑(有資訊則會自動檢查 UE 狀態並啟動)
23
+ * @property options.openUrl 自動開啟網頁
24
+ * @property options.mode 開啟的模式
25
+ * @property options.foreground 強制瀏覽器至於前景
26
+ * @property options.remoteDebug 遠程調控
27
+ */
28
+ declare interface AutomationOptions {
29
+ unrealFolder?: string;
30
+ openUrl?: string;
31
+ mode?: "kiosk";
32
+ foreground?: "always";
33
+ remoteDebug?: number;
34
+ }
35
+
36
+ export declare interface ExperimentOptions {
37
+ interaction?: {
38
+ port?: number;
39
+ host?: string;
40
+ };
41
+ automaion?: AutomationOptions;
42
+ }
43
+
18
44
  declare interface InitOptions {
19
45
  /**
20
46
  * [zh]
@@ -34,10 +60,10 @@ declare interface InitOptions {
34
60
  project?: string | Partial<AiiaProjectItem>;
35
61
  /**
36
62
  * [zh]
37
- * 指定的環境,預設值: production
63
+ * 指定的環境,預設值: developement
38
64
  *
39
65
  * [en]
40
- * Specific Env, default: production
66
+ * Specific Env, default: developement
41
67
  */
42
68
  env?: "developement" | "production";
43
69
  /**
@@ -109,15 +135,17 @@ declare interface InitOptions {
109
135
  */
110
136
  llmSampleRate?: number;
111
137
  /**
138
+ * @deprecated
139
+ *
112
140
  * [zh]
113
- * 人臉辨識
141
+ * 人臉辨識(已棄用,請改用 `cameraDetection`),同時設定會以 `cameraDetection` 為主
114
142
  *
115
143
  * @property perSecond: 每秒檢測幾次, 預設: 每秒 3 次
116
144
  *
117
145
  * @property confidence: 分數(0 ~ 100), 預設: 80
118
146
  *
119
147
  * [en]
120
- * facial recognition
148
+ * Face recognition (deprecated, please use `cameraDetection` instead), and the settings will be based on `cameraDetection`
121
149
  *
122
150
  * @property perSecond: Detection times per second, default: 3 times per second
123
151
  *
@@ -127,6 +155,25 @@ declare interface InitOptions {
127
155
  perSecond?: number;
128
156
  confidence?: number;
129
157
  };
158
+ /**
159
+ * [zh]
160
+ * 攝影機辨識設定
161
+ *
162
+ * @property perSecond: 每秒檢測幾次, 預設: 每秒 3 次
163
+ *
164
+ * @property confidence: 人臉辨識分數(0 ~ 100), 預設: 80
165
+ *
166
+ * [en]
167
+ * Camera Identification Settings
168
+ *
169
+ * @property perSecond: Detection times per second, default: 3 times per second
170
+ *
171
+ * @property confidence: Face recognition score(0 ~ 100), default: 80
172
+ */
173
+ cameraDetection?: {
174
+ perSecond?: number;
175
+ confidence?: number;
176
+ };
130
177
  /**
131
178
  * [zh]
132
179
  * 清除字幕,可指定時間(毫秒),預設 5000 毫秒後清除
@@ -166,7 +213,9 @@ declare interface InitOptions {
166
213
 
167
214
  declare type LoggerLevel = "info" | "error" | "warn" | "debug" | "fatal" | "code";
168
215
 
169
- export declare type NodeInitOptions = Omit<InitOptions, "project" | "ws_url" | "worklet_url" | "mediaStream" | "chunkTimeInSeconds" | "llmSampleRate" | "autoClearUserSubtitle" | "autoClearAiiaSubtitle" | "faceDetection">;
216
+ export declare type NodeInitOptions = Omit<InitOptions, "project" | "ws_url" | "worklet_url" | "mediaStream" | "chunkTimeInSeconds" | "llmSampleRate" | "autoClearUserSubtitle" | "autoClearAiiaSubtitle" | "faceDetection" | "cameraDetection"> & {
217
+ experiment?: ExperimentOptions;
218
+ };
170
219
 
171
220
  declare type Webserver = Server<any, any> | Server_2<any, any> | Http2SecureServer<any, any, any, any> | Http2Server<any, any, any, any>;
172
221
 
package/dist/node.mjs CHANGED
@@ -1 +1 @@
1
- import{assign as e,forEach as t,get as s,map as o,omit as r,isArray as i}from"lodash-es";import n from"moment";import{abs as c,matrix as a,multiply as h}from"mathjs";import{Server as l}from"socket.io";import{Subject as u,fromEvent as d,takeUntil as S,take as b}from"rxjs";import p from"axios";import _ from"ws";import g from"fs";import y from"os";import f from"path";import{Client as E}from"node-osc";var m;!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(m||(m={}));const O="access_token",D="undefined"!=typeof process&&null!=process.versions&&null!=process.versions.node;class N{config;get webserver(){return this.config.webserver}get environment(){return this.config.env}get license(){return this.config.license??""}get port(){return function(e,t){const s=t??NaN;if(e){const t=parseInt(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.config.port??3e3)}get ws_url(){return this.config.ws_url??""}get worklet_url(){return this.config.worklet_url??""}get project(){const{project:t}=e({},this.config);return void 0===t?{specific:!1}:"string"==typeof t?{name:t,id:t,specific:!1}:e({},t,{specific:!0})}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}return{api:`https://aiia-content-management-${e}-21193779403.asia-east1.run.app`,socket:`wss://graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`}}get debug(){return this.config.debug??!0}get mediaStream(){return this.config.mediaStream}get chunkTimeInSeconds(){return"number"==typeof this.config.chunkTimeInSeconds&&this.config.chunkTimeInSeconds>0?this.config.chunkTimeInSeconds:.3}get llmSampleRate(){return"number"==typeof this.config.llmSampleRate&&this.config.llmSampleRate>0?this.config.llmSampleRate:16e3}get faceDetection(){return e({perSecond:3,confidence:80},this.config.faceDetection)}get autoClearSubtitle(){return{userDelayTime:this.config.autoClearUserSubtitle??5e3,aiiaDelayTime:this.config.autoClearAiiaSubtitle??NaN}}get eyeTrackEnable(){return this.config.eyeTrackEnable??!1}get eyeTrackCorrection(){return e({x_axis_px:0,y_axis_px:0},this.config.correction)}constructor(e){this.config=e}}var R;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線連端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(R||(R={}));const k={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},C=[];function A(e){return C.push(e),A}const w=new Proxy(k,{get(e,s,o){if("string"==typeof s)switch(s){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...o)=>{const r=n().format("yyyy-MM-DD[T]HH:mm:ss");t(C,e=>{e.next(s,r,...o)}),e[s](`[Aiia::${s}_${r}_]`,...o)};default:return}},set:(e,t,s,o)=>!0});class x{config;server;memberSub;get port(){return this.config.port}get webserver(){return this.config.webserver}get memberChange(){return this.memberSub.asObservable()}constructor(e){this.config=e,this.memberSub=new u}start(){if(this.server=new l(this.webserver,{cors:{origin:"*"}}),this.server.on("connection",e=>{this.memberSub.next({type:"in",socket:e}),w.debug("client on",e.id),e.on("disconnect",t=>{this.memberSub.next({type:"out",socket:e}),w.debug("client off",t)})}),void 0===this.webserver){const e=this.port;this.server.listen(e),w.info("Websocket server run on",e)}else w.info("Websocket server rely your webserver.")}onDestroy(){this.server&&this.server.close()}}class M{instance;controller;constructor(e){this.controller=new AbortController,this.instance=p.create({baseURL:e,signal:this.controller.signal})}async getAccessToken(e,t){const o={license_number:e,token_value:t??""},r=await this.instance.post("/api/v1/license/agent_token",o).then(e=>s(e.data,"token_value","")).catch(()=>null);if(null===r)throw new Error(m["網絡錯誤"]);if(""===r)throw new Error(m["獲取權杖失敗"]);return r}async getProjectList(e){const t=await this.instance.post("/api/v1/license/projects",{license_number:e}).then(e=>o(s(e,"data.projects",[]),({id:e,sub_project_name:t})=>({id:e,name:t}))).catch(e=>null);if(null===t)throw new Error(m["網絡錯誤"]);if(0===t.length)throw new Error(m["沒有可用專案"]);return t}onDestroy(){this.controller.abort()}}var L;!function(e){e[e.MDN_NORMAL_CLOSURE=1e3]="MDN_NORMAL_CLOSURE",e[e.MDN_GOING_AWAY=1001]="MDN_GOING_AWAY",e[e.MDN_PROTOCOL_ERROR=1002]="MDN_PROTOCOL_ERROR",e[e.MDN_UNSUPPORTED_DATA=1003]="MDN_UNSUPPORTED_DATA",e[e.MDN_RESERVED_1004=1004]="MDN_RESERVED_1004",e[e.MDN_NO_STATUS_RECEIVED=1005]="MDN_NO_STATUS_RECEIVED",e[e.MDN_ABNORMAL_CLOSURE=1006]="MDN_ABNORMAL_CLOSURE",e[e.MDN_INVALID_FRAME_PAYLOAD=1007]="MDN_INVALID_FRAME_PAYLOAD",e[e.MDN_POLICY_VIOLATION=1008]="MDN_POLICY_VIOLATION",e[e.MDN_MESSAGE_TOO_BIG=1009]="MDN_MESSAGE_TOO_BIG",e[e.MDN_MANDATORY_EXTENSION=1010]="MDN_MANDATORY_EXTENSION",e[e.MDN_INTERNAL_ERROR=1011]="MDN_INTERNAL_ERROR",e[e.MDN_SERVICE_RESTART=1012]="MDN_SERVICE_RESTART",e[e.MDN_TRY_AGAIN_LATER=1013]="MDN_TRY_AGAIN_LATER",e[e.MDN_BAD_GATEWAY=1014]="MDN_BAD_GATEWAY",e[e.MDN_TLS_HANDSHAKE=1015]="MDN_TLS_HANDSHAKE",e[e.WHEN_BROWSER_CLOSE=4001]="WHEN_BROWSER_CLOSE",e[e.MANUAL_CLOSE=4002]="MANUAL_CLOSE",e[e.WS_5000_NORAML_CLOSURE=4100]="WS_5000_NORAML_CLOSURE",e[e.WS_5001_UNAUTHORIZED=4101]="WS_5001_UNAUTHORIZED",e[e.WS_5002_LICENSE_EXPIRED=4102]="WS_5002_LICENSE_EXPIRED",e[e.WS_5003_TOKEN_EXPIRED=4103]="WS_5003_TOKEN_EXPIRED"}(L||(L={}));class I{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}isConnected;constructor(){this.isConnected=!1,this.messageSub=new u,this.linkStartSub=new u,this.closeSub=new u,this.destorySub=new u}start(e){this.socket=new _(e,{autoPong:!0}),d(this.socket,"open").pipe(S(this.destorySub)).subscribe(()=>{this.isConnected=!0,w.code(R["啟動服務"],"proxy"),this.linkStartSub.next()}),d(this.socket,"message").pipe(S(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),d(this.socket,"error").pipe(S(this.destorySub)).subscribe(e=>{const{error:t}=e;w.error("Proxy has some problem",t)}),d(this.socket,"close").pipe(S(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s,type:o,wasClean:r}=e;switch(t){case L.WHEN_BROWSER_CLOSE:w.code(R["無副作用的關閉連線"]);break;case L.MDN_NORMAL_CLOSURE:case L.MDN_GOING_AWAY:this.closeSub.next("close"),w.code(R["安全的關閉連線"]);break;case L.MDN_NO_STATUS_RECEIVED:case L.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),w.code(R["與雲端的服務中斷"],{code:t,reason:s,type:o,wasClean:r});break;case L.WS_5001_UNAUTHORIZED:case L.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),w.code(R["沒有授權,請聯繫Graphen"]);break;case L.WS_5000_NORAML_CLOSURE:case L.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),w.code(R["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),w.code(R["未預期的斷線,請聯繫Graphen"],{code:t,reason:s,type:o,wasClean:r})}})}reconnect(e){this.onDestroy(),this.destorySub=new u,this.start(e)}send(e){this.isConnected&&void 0!==this.socket&&this.socket.send(e,e=>{e&&w.error("send message fail",e?.message,e)})}sendToCloud(e){this.send(JSON.stringify(e))}onClose(){this.socket?.close(L.WHEN_BROWSER_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}const P=new class{AppName;platform;isMAC;isWIN;isLINUX;saveFolder;homedir;appFolder;EOL;constructor(e){switch(this.AppName=e,this.platform=process.platform,this.homedir=y.homedir(),this.EOL=y.EOL,this.platform){case"darwin":this.isMAC=!0,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=f.join(this.homedir,"Library","Application Support");break;case"win32":this.isMAC=!1,this.isWIN=!0,this.isLINUX=!1,this.saveFolder=process.env.APPDATA||f.join(this.homedir,"AppData","Roaming");break;case"linux":this.isMAC=!1,this.isWIN=!1,this.isLINUX=!0,this.saveFolder=process.env.XDG_CONFIG_HOME||f.join(this.homedir,".config");break;default:console.log(`當前系統是未知的平台: ${this.platform}`),this.isMAC=!1,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=this.homedir}this.appFolder=f.join(this.saveFolder,this.AppName),this.folderCheck(this.appFolder)}addPath(...e){return f.join(...e)}folderCheck(e){g.mkdirSync(e,{recursive:!0})}fileCheck(e){g.existsSync(e)||g.writeFileSync(e,"",{encoding:"utf8"})}}("aiia-sdk");class T{type="node";filePath;fileName;constructor(){this.fileName="storage.json",this.filePath=P.addPath(P.appFolder,this.fileName),P.fileCheck(this.filePath)}getFileDataSync(){const e=g.readFileSync(this.filePath,"utf8");try{return JSON.parse(e)}catch(e){return{}}}setFileDataSync(e){try{const t=JSON.stringify(e);g.writeFileSync(this.filePath,t,"utf8")}catch(e){}}async getFileData(){return new Promise(e=>{g.readFile(this.filePath,"utf8",(t,s)=>{if(t)e({});else try{e(JSON.parse(s))}catch(t){e({})}})})}async setFileData(e){return new Promise(t=>{try{const s=JSON.stringify(e);g.writeFile(this.filePath,s,"utf8",e=>{t()})}catch(e){}})}async getItem(e){return(await this.getFileData())[e]??null}async setItem(e,t){const s=await this.getFileData();s[e]=t,await this.setFileData(s)}async removeItem(e){const t=await this.getFileData(),s=r(t,e);await this.setFileData(s)}async clear(){await this.setFileData({})}getItemSync(e){return this.getFileDataSync()[e]??null}setItemSync(e,t){const s=this.getFileDataSync();s[e]=t,this.setFileDataSync(s)}removeItemSync(e){const t=r(this.getFileDataSync(),e);this.setFileDataSync(t)}clearSync(){this.setFileDataSync({})}}class v{config;constructor(e){this.config=e,w.info("UEProxy, config=",e)}start(){w.info("UEProxy, start proxy")}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"osc"===t.signal){w.info("UEProxy, payload=",t);try{let e=t.content;const s=new E(e.host,e.port);w.debug("UEProxy, client open to port "+e.port),s.send(e.message,e=>{e&&w.error(e),s.close(),w.debug("UEProxy, client closed")})}catch(e){w.error(e)}}}cloudToObj(e){try{return JSON.parse(e)}catch(e){w.error("message transfer fail",e)}}onDestroy(){w.info("UEProxy, destroy proxy")}}class U{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}messageQueue=[];isConnected;constructor(){this.isConnected=!1,this.messageSub=new u,this.linkStartSub=new u,this.closeSub=new u,this.destorySub=new u}url="ws://localhost:8080";start(){this.socket=new _(this.url,{autoPong:!0}),d(this.socket,"open").pipe(S(this.destorySub)).subscribe(()=>{this.isConnected=!0,w.code(R["啟動服務"],"proxy"),this.flushMessageQueue(),this.linkStartSub.next()}),d(this.socket,"message").pipe(S(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),d(this.socket,"error").pipe(S(this.destorySub)).subscribe(e=>{const{error:t}=e;w.error("Proxy has some problem",t)}),d(this.socket,"close").pipe(S(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s}=e;switch(t){case L.MANUAL_CLOSE:w.code(R["無副作用的關閉連線"]);break;case L.WS_5000_NORAML_CLOSURE:case L.MDN_NORMAL_CLOSURE:case L.MDN_GOING_AWAY:this.closeSub.next("close"),w.code(R["安全的關閉連線"]);break;case L.MDN_NO_STATUS_RECEIVED:case L.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),w.code(R["與雲端的服務中斷"],{code:t,reason:s});break;case L.WS_5001_UNAUTHORIZED:case L.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),w.code(R["沒有授權,請聯繫Graphen"]);break;case L.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),w.code(R["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),w.code(R["未預期的斷線,請聯繫Graphen"],{code:t,reason:s})}})}reconnect(){this.onDestroy(),this.destorySub=new u,this.start()}flushMessageQueue(){for(;this.messageQueue.length>0&&this.isConnected&&void 0!==this.socket;){const e=this.messageQueue.shift();e&&this.socket.send(e)}}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"audio"===t.signal&&"pcm"===t.command&&(w.info("AudioSocketProxy, payload=",t),this.isConnected&&void 0!==this.socket))try{const e=t.content,s=Buffer.allocUnsafe(2*e.length);for(let t=0;t<e.length;t++)s.writeInt16LE(e[t],2*t);this.socket.send(s)}catch(e){w.error(e)}}cloudToObj(e){try{return JSON.parse(e)}catch(e){w.error("message transfer fail",e)}}onClose(){this.socket?.close(L.MANUAL_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}class W{socket;messageSub;linkStartSub;destorySub;closeSub;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}messageQueue=[];isConnected;constructor(){this.isConnected=!1,this.messageSub=new u,this.linkStartSub=new u,this.closeSub=new u,this.destorySub=new u}url="ws://localhost:8081";start(){this.socket=new _(this.url,{autoPong:!0}),d(this.socket,"open").pipe(S(this.destorySub)).subscribe(()=>{this.isConnected=!0,w.code(R["啟動服務"],"proxy"),this.flushMessageQueue(),this.linkStartSub.next()}),d(this.socket,"message").pipe(S(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),d(this.socket,"error").pipe(S(this.destorySub)).subscribe(e=>{const{error:t}=e;w.error("Proxy has some problem",t)}),d(this.socket,"close").pipe(S(this.destorySub)).subscribe(e=>{this.isConnected=!1;const{code:t,reason:s}=e;switch(t){case L.MANUAL_CLOSE:w.code(R["無副作用的關閉連線"]);break;case L.WS_5000_NORAML_CLOSURE:case L.MDN_NORMAL_CLOSURE:case L.MDN_GOING_AWAY:this.closeSub.next("close"),w.code(R["安全的關閉連線"]);break;case L.MDN_NO_STATUS_RECEIVED:case L.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),w.code(R["與雲端的服務中斷"],{code:t,reason:s});break;case L.WS_5001_UNAUTHORIZED:case L.WS_5002_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),w.code(R["沒有授權,請聯繫Graphen"]);break;case L.WS_5003_TOKEN_EXPIRED:this.closeSub.next("reconnect"),w.code(R["自動重新連線連端服務"]);break;default:this.closeSub.next("close"),w.code(R["未預期的斷線,請聯繫Graphen"],{code:t,reason:s})}})}reconnect(){this.onDestroy(),this.destorySub=new u,this.start()}flushMessageQueue(){for(;this.messageQueue.length>0&&this.isConnected&&void 0!==this.socket;){const e=this.messageQueue.shift();e&&this.socket.send(e)}}send(e){let t=this.cloudToObj(e);if(t&&t.signal&&"audio"===t.signal&&"interrupted"===t.command&&(w.info("AudioInterruptSocketProxy, payload=",t),this.isConnected&&void 0!==this.socket))try{this.socket.send(t.command)}catch(e){w.error(e)}}cloudToObj(e){try{return JSON.parse(e)}catch(e){w.error("message transfer fail",e)}}onClose(){this.socket?.close(L.MANUAL_CLOSE)}onDestroy(){this.destorySub.next(),this.destorySub.complete()}}class F{next(e,t,...s){const o=`log_${n().format("yyyy-MM-DD")}.txt`,r=P.addPath(P.appFolder,o);try{const o=s.reduce((e,t)=>{switch(typeof t){case"string":case"number":case"bigint":return`${e} ${t.toString()}`;case"boolean":return`${e} ${t?"true":"false"}`;case"object":try{return`${e} ${JSON.stringify(t)}`}catch(t){return e}case"symbol":case"undefined":case"function":return`${e} [${typeof t}]`}},"");g.appendFileSync(r,`LOG(${e})__${t}__::${o}${P.EOL}`,{encoding:"utf8"})}catch(e){}}}class G{config;stroage;apiProxy;chat;udpProxy;audioProxy;audioIntrProxy;isStart;channelMap;destorySub;access_token;eyeBallUdp;get accessToken(){return this.access_token}constructor(e,t){this.config=e,this.stroage=t,this.isStart=!1,this.destorySub=new u,this.channelMap=new Map,this.access_token=t.getItemSync(O),this.apiProxy=new M(this.config.endPoint.api),w.code(R["指定模組初始化完成"],"api"),this.chat=new x(this.config),w.code(R["指定模組初始化完成"],"chat"),this.udpProxy=new v(this.config),w.code(R["指定模組初始化完成"],"udp"),this.audioProxy=new U,w.code(R["指定模組初始化完成"],"audio"),this.audioIntrProxy=new W,w.code(R["指定模組初始化完成"],"audio intr"),e.eyeTrackEnable&&(this.eyeBallUdp=new E("127.0.0.1",6745),w.code(R["指定模組初始化完成"],"eye ball track")),this.openChannel=this.openChannel.bind(this)}async start(){if(this.isStart)throw new Error(m["重複初始化"]);this.isStart=!0,await this.replaceAccessToken(),this.chat.memberChange.pipe(S(this.destorySub)).subscribe(({type:e,socket:t})=>{switch(e){case"in":{const e=new I,r=new u,n=(o=t.handshake.query.uuid,i(o)?s(o,"[0]",void 0):o??void 0);void 0===n?(this.getProjectList().then(e=>{t.emit("list",e)}),d(t,"uuid").pipe(b(1),S(r)).subscribe(s=>{this.openChannel(s,t,e,r)})):this.openChannel(n,t,e,r),this.channelMap.set(t.id,()=>{r.next(),r.complete()});break}case"out":this.channelMap.has(t.id)&&(this.channelMap.get(t.id)(),this.channelMap.delete(t.id))}var o}),this.chat.start(),w.code(R["啟動服務"],"chat"),this.udpProxy.start(),w.code(R["啟動服務"],"udp")}async replaceAccessToken(){const e=await this.apiProxy.getAccessToken(this.config.license,this.access_token);return this.access_token=e,this.stroage.setItemSync(O,e),e}async getProjectList(){return await this.apiProxy.getProjectList(this.config.license)}getProxySocketURL(e){return`${this.config.endPoint.socket}/v1/chat/project/${this.accessToken}/${e}`}openChannel(t,s,o,r){d(s,"cloud").pipe(S(r)).subscribe(e=>{o.send(e)});const{x_axis_px:i,y_axis_px:n}=this.config.eyeTrackCorrection;d(s,"face").pipe(S(r)).subscribe(t=>{const s=t,r=null!==s;if(r&&void 0!==this.eyeBallUdp){const{x:t,y:o}=function(t,s){const{maxX:o,maxY:r,minX:i,minY:n}=e({maxX:25,minX:-25,maxY:50,minY:-50},s),l=c(o)+c(i),u=c(r)+c(n),{clientHeight:d,clientWidth:S,originX:b,originY:p,width:_,height:g}=t,y=a([[l/S,0,i],[0,u/d,n],[0,0,1]]),f=b+_/2,E=d-(p+g/2),m=a([[f>S?S:f<0?0:f],[E>d?d:E<0?0:E],[1]]),O=h(y,m);return{x:O.get([0,0]),y:O.get([1,0])}}(s);this.eyeBallUdp.send(["/",t+i,o+n],e=>{null!=e&&w.error("eye track error:",e)})}o.sendToCloud({request:"face_detect",content:r})}),o.message.pipe(S(r)).subscribe(e=>{s.emit("cloud",e),this.udpProxy.send(e),this.audioProxy.send(e),this.audioIntrProxy.send(e)}),o.close.pipe(S(r)).subscribe(async e=>{switch(s.emit("channel",e),e){case"close":case"no_permissions":o.onDestroy();break;case"reconnect":await this.replaceAccessToken(),o.reconnect(this.getProxySocketURL(t))}}),o.linkStart.pipe(S(r)).subscribe(()=>{s.emit("channel","open")}),r.subscribe(()=>{o.onClose(),this.audioProxy.onClose(),this.audioIntrProxy.onClose()}),o.start(this.getProxySocketURL(t)),this.audioProxy.start(),this.audioIntrProxy.start()}onDestroy(){this.apiProxy.onDestroy(),this.chat.onDestroy(),this.udpProxy.onDestroy(),this.destorySub.next(),this.destorySub.complete(),this.eyeBallUdp?.close(),w.code(R["服務結束"])}}function j(t){if(D){if(void 0===s(t,"license",void 0))throw new Error(m["未提供憑證"]);!function(e){const{info:t,error:s,warn:o,debug:r,fatal:i,code:n}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);return n||(k.code=()=>{}),r||(k.debug=()=>{}),t||(k.info=()=>{}),o||(k.warn=()=>{}),s||(k.error=()=>{}),i||(k.fatal=()=>{}),A}(s(t,"debug",!0))(new F),w.code(R["開始初始化必要模組"]);const o=new N(e({},t));w.code(R["指定模組初始化完成"],"config");const r=new T;w.code(R["指定模組初始化完成"],"storage");const i=new G(o,r);return w.code(R["指定模組初始化完成"],"core"),i.start(),w.code(R["啟動服務"]),()=>{i.onDestroy()}}throw new Error(m["環境錯誤(nodejs)"])}export{j as aiiaCore};
1
+ import{get as e,assign as t,forEach as s,map as o,omit as r,pick as n,keys as i,isInteger as a,find as c,some as h,values as l,filter as u,isArray as d}from"lodash-es";import p from"moment";import{abs as f,matrix as g,multiply as m}from"mathjs";import{Server as y}from"socket.io";import{Subject as S,fromEvent as b,takeUntil as _,take as w,interval as E}from"rxjs";import D from"axios";import O from"ws";import N from"fs";import k from"os";import R from"path";import{Client as C}from"node-osc";import{spawn as A,exec as M}from"child_process";import P from"systeminformation";import{getWindows as T}from"@nut-tree-fork/nut-js";import*as I from"readline";import{createServer as x}from"net";var L;!function(e){e["網絡錯誤"]="ERROR CODE: 500",e["環境錯誤(browser)"]="ERROR CODE: 1001",e["Worklet模組載入失敗"]="ERROR CODE: 1002",e["未獲得媒體裝置權限"]="ERROR CODE: 1003",e["實例已摧毀"]="ERROR CODE: 1004",e["環境錯誤(nodejs)"]="ERROR CODE: 2001",e["未提供憑證"]="ERROR CODE: 2002",e["獲取權杖失敗"]="ERROR CODE: 2003",e["沒有可用專案"]="ERROR CODE: 2004",e["重複初始化"]="ERROR CODE: 2005"}(L||(L={}));const v="access_token",$="undefined"!=typeof process&&null!=process.versions&&null!=process.versions.node;function F(e){return new Promise(t=>{setTimeout(t,e)})}class W{config;get webserver(){return this.safeRead("webserver")}get environment(){const e=this.safeRead("env");return"string"==typeof e?e:"developement"}get license(){return this.safeRead("license")??""}get port(){return function(e,t){const s="number"==typeof t?t:NaN;if(e){const t=Number(e);return isNaN(t)?s:t}return s}(process.env.PORT,this.safeRead("port")??3e3)}get ws_url(){const e=this.safeRead("ws_url");return"string"==typeof e?e:""}get worklet_url(){const e=this.safeRead("worklet_url");return"string"==typeof e?e:""}get project(){const t=this.safeRead("project");if(void 0===t)return{specific:!1};if("string"==typeof t)return{name:t,id:t,specific:!1};{const s=e(t,"id",void 0),o=e(t,"name",void 0);return{id:"string"==typeof s?s:void 0,name:"string"==typeof o?o:void 0,specific:!0}}}get endPoint(){let e="dev";switch(this.environment){case"production":e="prod";break;case"test":e="test"}const t=`graphen-agentic-workflow-${e}-21193779403.asia-east1.run.app`;return{api:`https://${t}`,socket:`wss://${t}`}}get debug(){return this.safeRead("debug")??!0}get mediaStream(){return this.safeRead("mediaStream")}get chunkTimeInSeconds(){const e=this.safeRead("chunkTimeInSeconds");return"number"==typeof e&&e>0?e:.3}get llmSampleRate(){const e=this.safeRead("llmSampleRate");return"number"==typeof e&&e>0?e:16e3}get detection(){return t({perSecond:3,confidence:80},this.safeRead("faceDetection"),this.safeRead("cameraDetection"))}get autoClearSubtitle(){const e=this.safeRead("autoClearUserSubtitle"),t=this.safeRead("autoClearAiiaSubtitle");return{userDelayTime:"number"==typeof e?e:5e3,aiiaDelayTime:"number"==typeof t?t:NaN}}get eyeTrackEnable(){return Boolean(this.safeRead("eyeTrackEnable"))}get eyeTrackCorrection(){return t({x_axis_px:0,y_axis_px:0},this.safeRead("correction"))}constructor(e){this.config=e}safeRead(t){return e(this.config,t,void 0)}}var U;!function(e){e["通道、雲端服務、麥克風都已就緒"]="CODE: 1000",e["未取得媒體裝置權限,請重新設定或忽略此訊息"]="CODE: 1001",e["請訂閱專案列表"]="CODE: 1002",e["初始化必要依賴"]="CODE: 1010",e["指定依賴已完成"]="CODE: 1011",e["啟動SDK"]="CODE: 1012",e["結束SDK"]="CODE: 1013",e["已啟動SDK"]="CODE: 1014",e["安全的關閉連線"]="CODE: 2000",e["無副作用的關閉連線"]="CODE: 2001",e["與雲端的服務中斷"]="CODE: 2002",e["沒有授權,請聯繫Graphen"]="CODE: 2003",e["自動重新連線雲端服務"]="CODE: 2004",e["未預期的斷線,請聯繫Graphen"]="CODE: 2005",e["開始初始化必要模組"]="CODE: 2010",e["指定模組初始化完成"]="CODE: 2011",e["啟動服務"]="CODE: 2012",e["服務結束"]="CODE: 2013"}(U||(U={}));const j={code:console.log,debug:console.log,info:console.log,warn:console.warn,error:console.error,fatal:console.error},G=[];function B(e){return G.push(e),B}const Y=new Proxy(j,{get(e,t,o){if("string"==typeof t)switch(t){case"debug":case"info":case"error":case"warn":case"fatal":case"code":return(...o)=>{const r=p().format("yyyy-MM-DD[T]HH:mm:ss:sss");s(G,e=>{e.next(t,r,...o)}),e[t](`[Aiia::${t}_${r}_]`,...o)};default:return}},set:(e,t,s,o)=>!0});class H{config;server;memberSub;get port(){return this.config.port}get webserver(){return this.config.webserver}get memberChange(){return this.memberSub.asObservable()}constructor(e){this.config=e,this.memberSub=new S}start(){if(this.server=new y(this.webserver,{cors:{origin:"*"}}),this.server.on("connection",e=>{Y.debug("client on",e.id),this.memberSub.next({type:"in",socket:e}),e.on("disconnect",t=>{Y.debug("client off",e.id,"reason",t),this.memberSub.next({type:"out",socket:e})})}),void 0===this.webserver){const e=this.port;this.server.listen(e),Y.info("Websocket server run on",e)}else Y.info("Websocket server rely your webserver.")}onDestroy(){this.server&&this.server.close()}}class K{instance;controller;constructor(e){this.controller=new AbortController,this.instance=D.create({baseURL:e,signal:this.controller.signal})}async getAccessToken(t,s){const o=performance.now(),r={license_number:t,token_value:s??""},n=await this.instance.post("/api/v1/license/agent_token",r).then(t=>e(t.data,"token_value","")).catch(()=>null);if(Y.debug("Complete certification",`Took ${performance.now()-o} milliseconds.`),null===n)throw new Error(L["網絡錯誤"]);if(""===n)throw new Error(L["獲取權杖失敗"]);return n}async getProjectList(t){const s=performance.now(),r=await this.instance.post("/api/v1/license/projects",{license_number:t}).then(t=>o(e(t,"data.projects",[]),({id:e,sub_project_name:t,avatar:s})=>{const{name:o,url:r}=s;return{avatar:{name:o,url:r},id:e,name:t}})).catch(e=>null);if(Y.debug("Got Project List",`Took ${performance.now()-s} milliseconds.`),null===r)throw new Error(L["網絡錯誤"]);if(0===r.length)throw new Error(L["沒有可用專案"]);return r}onDestroy(){this.controller.abort()}}var X;!function(e){e[e.MDN_NORMAL_CLOSURE=1e3]="MDN_NORMAL_CLOSURE",e[e.MDN_GOING_AWAY=1001]="MDN_GOING_AWAY",e[e.MDN_PROTOCOL_ERROR=1002]="MDN_PROTOCOL_ERROR",e[e.MDN_UNSUPPORTED_DATA=1003]="MDN_UNSUPPORTED_DATA",e[e.MDN_RESERVED_1004=1004]="MDN_RESERVED_1004",e[e.MDN_NO_STATUS_RECEIVED=1005]="MDN_NO_STATUS_RECEIVED",e[e.MDN_ABNORMAL_CLOSURE=1006]="MDN_ABNORMAL_CLOSURE",e[e.MDN_INVALID_FRAME_PAYLOAD=1007]="MDN_INVALID_FRAME_PAYLOAD",e[e.MDN_POLICY_VIOLATION=1008]="MDN_POLICY_VIOLATION",e[e.MDN_MESSAGE_TOO_BIG=1009]="MDN_MESSAGE_TOO_BIG",e[e.MDN_MANDATORY_EXTENSION=1010]="MDN_MANDATORY_EXTENSION",e[e.MDN_INTERNAL_ERROR=1011]="MDN_INTERNAL_ERROR",e[e.MDN_SERVICE_RESTART=1012]="MDN_SERVICE_RESTART",e[e.MDN_TRY_AGAIN_LATER=1013]="MDN_TRY_AGAIN_LATER",e[e.MDN_BAD_GATEWAY=1014]="MDN_BAD_GATEWAY",e[e.MDN_TLS_HANDSHAKE=1015]="MDN_TLS_HANDSHAKE",e[e.WHEN_BROWSER_CLOSE=4001]="WHEN_BROWSER_CLOSE",e[e.WS_4100_NORAML_CLOSURE=4100]="WS_4100_NORAML_CLOSURE",e[e.WS_4101_UNAUTHORIZED=4101]="WS_4101_UNAUTHORIZED",e[e.WS_4102_LICENSE_EXPIRED=4102]="WS_4102_LICENSE_EXPIRED",e[e.WS_4103_TOKEN_EXPIRED=4103]="WS_4103_TOKEN_EXPIRED",e[e.WS_4104_LLM_SESSION_ERROR=4104]="WS_4104_LLM_SESSION_ERROR",e[e.WS_4105_TOO_MANY_CONNECTIONS=4105]="WS_4105_TOO_MANY_CONNECTIONS"}(X||(X={}));class V{id;uuid;socket;messageSub;linkStartSub;destorySub;closeSub;isFinished;debugInfo;lastCloseLog;get message(){return this.messageSub.asObservable()}get close(){return this.closeSub.asObservable()}get linkStart(){return this.linkStartSub.asObservable()}get isConnected(){return this.socket?.readyState===O.OPEN}constructor(e){this.id=e,this.messageSub=new S,this.linkStartSub=new S,this.closeSub=new S,this.destorySub=new S,this.isFinished=!1,this.uuid=crypto.randomUUID(),this.debugInfo=`browser(${this.id}), proxy(${this.uuid})`,Y.debug(`create proxy for browser(${this.id})`)}start(e){this.socket=new O(e,{autoPong:!0}),b(this.socket,"open").pipe(_(this.destorySub)).subscribe(()=>{Y.code(U["啟動服務"],"proxy",this.debugInfo),this.linkStartSub.next()}),b(this.socket,"message").pipe(_(this.destorySub)).subscribe(e=>{const{data:t}=e;this.messageSub.next(t)}),b(this.socket,"error").pipe(_(this.destorySub)).subscribe(e=>{const{error:t}=e;Y.error(this.debugInfo,"Proxy has some problem",t)}),b(this.socket,"close").pipe(_(this.destorySub)).subscribe(e=>{const{code:t,reason:s,type:o,wasClean:r}=e;switch(this.lastCloseLog=`最近一次關閉原因: ${s}, 錯誤碼: ${t}, Type: ${o}`,t){case X.WHEN_BROWSER_CLOSE:Y.code(U["無副作用的關閉連線"],this.debugInfo),this.onDestroy();break;case X.MDN_NORMAL_CLOSURE:case X.MDN_GOING_AWAY:this.closeSub.next("close"),Y.code(U["安全的關閉連線"],this.debugInfo);break;case X.MDN_NO_STATUS_RECEIVED:case X.MDN_ABNORMAL_CLOSURE:this.closeSub.next("close"),Y.code(U["與雲端的服務中斷"],this.debugInfo,{code:t,reason:s,type:o,wasClean:r});break;case X.WS_4100_NORAML_CLOSURE:case X.WS_4103_TOKEN_EXPIRED:case X.WS_4104_LLM_SESSION_ERROR:case X.WS_4105_TOO_MANY_CONNECTIONS:this.closeSub.next("reconnect"),Y.code(U["自動重新連線雲端服務"],this.debugInfo,`socket code: ${t}`);break;case X.WS_4101_UNAUTHORIZED:case X.WS_4102_LICENSE_EXPIRED:this.closeSub.next("no_permissions"),Y.code(U["沒有授權,請聯繫Graphen"],this.debugInfo,`socket code: ${t}`);break;default:this.closeSub.next("close"),Y.code(U["未預期的斷線,請聯繫Graphen"],this.debugInfo,{code:t,reason:s,type:o,wasClean:r})}})}reconnect(e){this.isFinished||(this.destorySub.next(),this.destorySub.complete(),this.destorySub=new S,this.start(e))}send(e){const t=`currentTime: ${p().format("yyyy-MM-DD[T]HH:mm:ss:sss")}`;this.isConnected?this.socket.send(e,s=>{s&&Y.error("send message fail",this.debugInfo,t,e.slice(0,100),s)}):Y.warn("The socket is not connected",this.debugInfo,t,e.slice(0,100))}sendToCloud(e){this.send(JSON.stringify(e))}onClose(){if(this.socket)switch(this.socket.readyState){case 0:Y.debug("Once the cloud connection is complete, the connection will be disconnected."),this.linkStart.pipe(_(this.destorySub),w(1)).subscribe(()=>{this.socket?.close(X.WHEN_BROWSER_CLOSE)});break;case 1:Y.debug("Close safely, no side effects"),this.socket.close(X.WHEN_BROWSER_CLOSE);break;case 2:case 3:Y.debug("The connection have been lost.")}}getStatusInfo(){const e=(()=>{if(!this.socket)return"雲端服務尚未啟動";{const e=this.socket.readyState;switch(e){case 0:return"正在連線雲端服務中";case 1:return"雲端服務正常運作";case 2:return"正在關閉雲端服務";case 3:return"雲端服務已關閉";default:return`未知的 Websocket 狀態: ${e}`}}})(),t=this.lastCloseLog?`${e}, ${this.lastCloseLog}`:e;return`瀏覽器(${this.id}) - 雲端代理(${this.uuid}), ${t}`}onDestroy(){this.isFinished=!0,this.destorySub.next(),this.destorySub.complete(),Y.debug(`Destroy cloud channel(${this.debugInfo})`)}}const J=new class{AppName;platform;isMAC;isWIN;isLINUX;saveFolder;homedir;appFolder;EOL;constructor(e){switch(this.AppName=e,this.platform=process.platform,this.homedir=k.homedir(),this.EOL=k.EOL,this.platform){case"darwin":this.isMAC=!0,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=R.join(this.homedir,"Library","Application Support");break;case"win32":this.isMAC=!1,this.isWIN=!0,this.isLINUX=!1,this.saveFolder=process.env.APPDATA||R.join(this.homedir,"AppData","Roaming");break;case"linux":this.isMAC=!1,this.isWIN=!1,this.isLINUX=!0,this.saveFolder=process.env.XDG_CONFIG_HOME||R.join(this.homedir,".config");break;default:console.log(`當前系統是未知的平台: ${this.platform}`),this.isMAC=!1,this.isWIN=!1,this.isLINUX=!1,this.saveFolder=this.homedir}this.appFolder=R.join(this.saveFolder,this.AppName),this.folderCheck(this.appFolder)}addPath(...e){return R.join(...e)}folderCheck(e){N.mkdirSync(e,{recursive:!0})}createFolder(e){const t=this.addPath(this.appFolder,e);return this.folderCheck(t),t}fileCheck(e,t=""){N.existsSync(e)||N.writeFileSync(e,t,{encoding:"utf8"})}}("aiia-sdk");class Z{type="node";filePath;fileName;constructor(){this.fileName="storage.json",this.filePath=J.addPath(J.appFolder,this.fileName),J.fileCheck(this.filePath)}getFileDataSync(){const e=N.readFileSync(this.filePath,"utf8");try{return JSON.parse(e)}catch(e){return{}}}setFileDataSync(e){try{const t=JSON.stringify(e);N.writeFileSync(this.filePath,t,"utf8")}catch(e){}}async getFileData(){return new Promise(e=>{N.readFile(this.filePath,"utf8",(t,s)=>{if(t)e({});else try{e(JSON.parse(s))}catch(t){e({})}})})}async setFileData(e){return new Promise(t=>{try{const s=JSON.stringify(e);N.writeFile(this.filePath,s,"utf8",e=>{t()})}catch(e){}})}async getItem(e){return(await this.getFileData())[e]??null}async setItem(e,t){const s=await this.getFileData();s[e]=t,await this.setFileData(s)}async removeItem(e){const t=await this.getFileData(),s=r(t,e);await this.setFileData(s)}async clear(){await this.setFileData({})}getItemSync(e){return this.getFileDataSync()[e]??null}setItemSync(e,t){const s=this.getFileDataSync();s[e]=t,this.setFileDataSync(s)}removeItemSync(e){const t=r(this.getFileDataSync(),e);this.setFileDataSync(t)}clearSync(){this.setFileDataSync({})}}const q={[U["通道、雲端服務、麥克風都已就緒"]]:"通道、雲端服務、麥克風都已就緒",[U["未取得媒體裝置權限,請重新設定或忽略此訊息"]]:"未取得媒體裝置權限,請重新設定或忽略此訊息",[U["請訂閱專案列表"]]:"請訂閱專案列表",[U["初始化必要依賴"]]:"初始化必要依賴",[U["指定依賴已完成"]]:"指定依賴已完成",[U["啟動SDK"]]:"啟動SDK",[U["結束SDK"]]:"結束SDK",[U["已啟動SDK"]]:"已啟動SDK",[U["安全的關閉連線"]]:"安全的關閉連線",[U["無副作用的關閉連線"]]:"無副作用的關閉連線",[U["與雲端的服務中斷"]]:"與雲端的服務中斷",[U["沒有授權,請聯繫Graphen"]]:"沒有授權,請聯繫Graphen",[U["自動重新連線雲端服務"]]:"自動重新連線雲端服務",[U["未預期的斷線,請聯繫Graphen"]]:"未預期的斷線,請聯繫Graphen",[U["開始初始化必要模組"]]:"開始初始化必要模組",[U["指定模組初始化完成"]]:"指定模組初始化完成",[U["啟動服務"]]:"啟動服務",[U["服務結束"]]:"服務結束",[L["網絡錯誤"]]:"網絡錯誤",[L["環境錯誤(browser)"]]:"環境錯誤",[L["Worklet模組載入失敗"]]:"Worklet模組載入失敗",[L["未獲得媒體裝置權限"]]:"未獲得媒體裝置權限",[L["實例已摧毀"]]:"實例已摧毀",[L["環境錯誤(nodejs)"]]:"nodejs",[L["未提供憑證"]]:"未提供憑證",[L["獲取權杖失敗"]]:"獲取權杖失敗",[L["沒有可用專案"]]:"沒有可用專案",[L["重複初始化"]]:"重複初始化"},z=new RegExp(/^(ERROR )?CODE: [0-9]{3,4}$/);class Q{path;constructor(e){this.path=e?J.createFolder(e):J.appFolder}next(e,t,...s){const o=`log_${p().format("yyyy-MM-DD")}.txt`,r=J.addPath(this.path,o),n="code"===e?e=>z.test(e)?`${e}(${q[e]})`:e:e=>e;try{const o=s.reduce((e,t)=>{if(t instanceof Error){let s=`Error[${t.message??"No Message"}]${J.EOL}`;return s+=t.stack??"unknow stack","string"==typeof t.cause&&(s+=`${J.EOL}cause: ${t.cause}`),`${e}${J.EOL}${s}${J.EOL}`}switch(typeof t){case"string":return`${e} ${n(t)}`;case"number":case"bigint":return`${e} ${t.toString()}`;case"boolean":return`${e} ${t?"true":"false"}`;case"object":try{return`${e} ${JSON.stringify(t)}`}catch(t){return e}case"symbol":case"undefined":case"function":return`${e} [${typeof t}]`}},"");N.appendFileSync(r,`LOG(${e})__${t}__::${o}${J.EOL}`,{encoding:"utf8"})}catch(e){}}}const ee="127.0.0.1",te={AvatarPort:"9876",ActionPort:"6969",LocationPort:"4060",LookPort:"6745",BackGroundPort:"2400"},se={StreamAudioPort:"8080",CleanAudioPort:"8081"};function oe(){return{...te,...se}}class re{clientMap;eyeBallUdp;x_axis_px;y_axis_px;defPortMap;constructor(){this.clientMap=new Map,this.x_axis_px=0,this.y_axis_px=0,this.defPortMap={};const e=n(te,"AvatarPort","ActionPort","LocationPort","BackGroundPort");s(i(e),t=>{const s=t,o=e[s];this.defPortMap[o]=s})}enableUDPClient(e){s(i(e),t=>{const s=new C(ee,e[t]);this.clientMap.set(t,s)})}enableEyeTrack(e){this.eyeBallUdp=new C(ee,e.port),this.x_axis_px=e.x_axis_px,this.y_axis_px=e.y_axis_px}eyeTrack(e){if(void 0!==this.eyeBallUdp){const s=null!==e,{x:o,y:r}=(()=>{if(s){const{x:s,y:o}=function(e,s){const{maxX:o,maxY:r,minX:n,minY:i}=t({maxX:25,minX:-25,maxY:50,minY:-50},s),a=f(o)+f(n),c=f(r)+f(i),{clientHeight:h,clientWidth:l,originX:u,originY:d,width:p,height:y}=e,S=g([[a/l,0,n],[0,c/h,i],[0,0,1]]),b=g([[u+p/2],[h-(d+y/2)],[1]]),_=m(S,b);return{x:_.get([0,0]),y:_.get([1,0])}}(e);return{x:this.toFloat(s+this.x_axis_px),y:this.toFloat(o+this.y_axis_px)}}return{x:this.toFloat(7),y:this.toFloat(20)}})();this.eyeBallUdp.send(["/",o,r],e=>{null!=e&&Y.error("Eye track error:",e)})}}movement(t){o(t,({content:t})=>{const s=e(t,"port",""),o=e(t,"message"),r=this.defPortMap[s.toString()];this.clientMap.has(r)&&this.clientMap.get(r).send(o,e=>{null!=e&&Y.error("Occur error when send message to UE",e)})})}toFloat(e){return a(e)?e+1e-5:e}onDestroy(){s(Array.from(this.clientMap),([e,t])=>{t.close()}),Y.info("Close all UnrealEngine's UDP")}}class ne{destroy;speechWS;interruptWS;get speechIsConnected(){return this.speechWS?.readyState===O.OPEN}get intrIsConnected(){return this.interruptWS?.readyState===O.OPEN}constructor(){this.destroy=new S}connectSocket(e){this.speechWS=this.getSocket(e.speech,"speech"),this.interruptWS=this.getSocket(e.interrupt,"interrupt")}getSocket(e,t){const s=new O(`ws://${ee}:${e}`);return s.onerror=s=>{const{message:o,error:r,type:n}=s;Y.error(`UE Tool(${t}) fail: ${o}`,r,{port:e,type:n})},s.onclose=s=>{const{code:o,reason:r,type:n}=s;Y.debug(`UE Tool(${t}) close`,{code:o,reason:r,type:n,port:e})},s}interrupted(){this.intrIsConnected&&this.interruptWS.send("interrupted")}speech(e){if(this.speechIsConnected){const t=Buffer.allocUnsafe(2*e.length);for(let s=0;s<e.length;s++)t.writeInt16LE(e[s],2*s);this.speechWS.send(t)}}onDestroy(){this.speechWS?.close(),this.interruptWS?.close(),this.destroy.next(),this.destroy.complete(),Y.info("Close all UnrealEngine's WebSocket")}}class ie{config;udpManager;socketManager;movement;eyeTrack;interrupted;speech;constructor(e){this.config=e,this.udpManager=new re,this.socketManager=new ne,this.movement=()=>{},this.eyeTrack=()=>{},this.interrupted=()=>{},this.speech=()=>{}}onStart(e){const o={},r=oe(),n=t(oe(),e);s(i(r),e=>{const t=n[e],s=Number(t);o[e]=isNaN(s)?Number(r[e]):s});const{StreamAudioPort:a,CleanAudioPort:c,LookPort:h,...l}=o;this.udpManager.enableUDPClient(l),this.socketManager.connectSocket({speech:a,interrupt:c}),this.movement=this.udpManager.movement.bind(this.udpManager),this.eyeTrack=this.udpManager.eyeTrack.bind(this.udpManager),this.interrupted=this.socketManager.interrupted.bind(this.socketManager),this.speech=this.socketManager.speech.bind(this.socketManager),this.config.eyeTrackEnable&&this.udpManager.enableEyeTrack({...this.config.eyeTrackCorrection,port:h})}onDestroy(){this.udpManager.onDestroy(),this.socketManager.onDestroy(),Y.info("UE tools destroy")}}async function ae(e){return new Promise(t=>{N.stat(e,e=>{t(e?[!1,e]:[!0])})})}async function ce(e,t,...s){return J.isWIN?new Promise(o=>{const r=[`cd ${e} && ${t} `,...s].join(" "),n=M(r);n.once("error",e=>{o([!1,e])}),n.once("spawn",()=>{n.unref(),o([!0])})}):Promise.resolve([!1,new Error("The function isn't implement in this system")])}async function he(){try{return await P.networkConnections()}catch(e){return console.error("檢查 Port 時發生錯誤:",e),[]}}async function le(e){const t=e.toString(),s=await he();return h(s,e=>e.localPort===t)}async function ue(e){const t=await T();let s=null;const o=new RegExp(e.toLocaleLowerCase());for(const e of t){const t=await e.title;if(o.test(t.toLocaleLowerCase())){s=e;break}}return s}function de(e){return[2e3,2e3,2e3,1e3,1e3,2e3,3e3,5e3][e]??[6e3,4e3][e%2]}function pe(){if(J.isWIN){const e="UEProject_5_6";return{proc:e,name:`${e}.exe`}}return null}class fe{config;ueConfig;destroySub;constructor(e){this.destroySub=new S;const s={isKiosk:!1,browserKeeping:!1},{unrealFolder:o,openUrl:r,mode:n,foreground:i,remoteDebug:a}=t({},e);if("string"==typeof o&&""!==o.trim()){s.path=o;const e=pe();e&&(this.ueConfig=new ge(o,e.proc))}"string"==typeof r&&""!==r.trim()&&(s.url=r),"string"==typeof n&&""!==n.trim()&&(s.isKiosk="kiosk"===n),"string"==typeof i&&""!==i.trim()&&(s.browserKeeping="always"===i),"number"==typeof a&&(s.remoteDebug=a),this.config=s}async exec(){let e=!1,t=await this.isUnrealRunning();t||(this.ueConfig&&await this.ueConfig.getSafePortTable(),t=await this.openUnreal()),t&&(e=await this.isUnrealInService()),e&&await this.openBrowser();return this.ueConfig?await this.ueConfig.getPortTable():oe()}async isUnrealRunning(){const e=pe();if(null===e)return!1;const{proc:t}=e,s=await async function(...e){const t=e.length>0?e.join(", "):"*";return await P.processLoad(t).catch(()=>[])}(t);if(s.length<=0)return!1;const o=t.toLocaleLowerCase(),r=c(s,e=>e.proc.toLocaleLowerCase()===o);return!!(r&&"number"==typeof r.pid&&r.pids.length>=2)}async isUnrealInService(e=12){let t=!1,s=0;const{StreamAudioPort:o}=this.ueConfig?await this.ueConfig.getPortTable():oe();for(;!t&&s<e;)t=await le(o),await F(de(s)),s++;return t}async openUnreal(){const e=pe();if(null===e)return!1;const{path:t}=this.config;if(void 0===t)return!1;const s=J.addPath(t,e.name),[o,r]=await ae(s);if(!o)return Y.error(r),!1;const[n,i]=await async function(e,...t){return J.isWIN?new Promise(s=>{const o=A(e,t,{detached:!0,stdio:"ignore"});o.once("error",e=>{s([!1,e])}),o.once("spawn",()=>{o.unref(),s([!0])})}):Promise.resolve([!1,new Error("The function isn't implement in this system")])}(s);return i&&Y.error(i),n}async openBrowser(){const{url:e,isKiosk:t,remoteDebug:s}=this.config;if(void 0===e)return!1;const o=J.isWIN?{edge:["C:/Program Files (x86)/Microsoft/Edge/Application","msedge.exe"],chrome:["C:/Program Files/Google/Chrome/Application","chrome.exe"],chrome2:[`${J.homedir}/AppData/Local/Google/Chrome/Application`,"chrome.exe"]}:null;if(null===o)return!1;const{proc:r}=pe(),{chrome:n,chrome2:i,edge:a}=o,[[c],[h],[l]]=await Promise.all([ae(J.addPath(...n)),ae(J.addPath(...a)),ae(J.addPath(...i))]);if(c||l){const o=[];if(t&&(o.push("--kiosk"),o.push(`--auto-select-desktop-capture-source="${r}"`)),void 0!==s){o.push(`--remote-debugging-port=${s}`);const e=J.createFolder("google");o.push(`--user-data-dir=${e}`)}o.push(e);const[a,h]=c?n:i,[l,u]=await ce(a,h,...o);return l?(this.setAppInForeground("chrome"),!0):(Y.error(u),!1)}if(h){const o=[];if(t&&(o.push("--kiosk"),o.push(`--auto-select-desktop-capture-source="${r}"`)),void 0!==s){o.push(`--remote-debugging-port=${s}`);const e=J.createFolder("edge");o.push(`--user-data-dir=${e}`)}o.push(e);const[n,i]=a,[c,h]=await ce(n,i,...o);return c?(this.setAppInForeground("edge"),!0):(Y.error(h),!1)}return Y.info("Can't find browser"),!1}async setAppInForeground(e){await F(3e3);const t=await ue(e);t?.focus(),this.config.browserKeeping&&E(1e4).pipe(_(this.destroySub)).subscribe(()=>{ue(e).then(e=>{e?.focus()})})}async getStateInfo(){const e=await this.isUnrealRunning();return{isOpen:e,isReady:!!e&&await this.isUnrealInService(1)}}onDestroy(){this.destroySub.next(),this.destroySub.complete()}}class ge{folder;file;constructor(e,t){this.folder=J.addPath(e,t,"config"),this.file=J.addPath(this.folder,"config.json"),J.folderCheck(this.folder),J.fileCheck(this.file,JSON.stringify(oe()))}async getSafePortTable(){const e=oe(),t=await he(),r=await async function(e,t){const o=t??await he(),r={};return s(i(e),t=>{const s=h(o,({localPort:s})=>s===e[t]);r[t]=s}),r}(e,t);if(h(l(r),e=>e)){const n=new Set(u(o(t,e=>Number(e.localPort)),e=>!isNaN(e)));s(i(r),t=>{const s=t;if(r[s]){const t=Number(e[s]);let o=1,r=NaN;for(;isNaN(r)&&o<=200;){const e=t+o;n.has(e)?o++:(n.add(e),r=e)}isNaN(r)||(e[s]=r.toString())}})}await this.setPortTable(e)}getPortTable(){return new Promise(e=>{N.readFile(this.file,"utf8",(t,s)=>{if(t)e(oe());else try{e(JSON.parse(s))}catch(t){e(oe())}})})}async setPortTable(e){try{const t=JSON.stringify(e);await new Promise(e=>{N.writeFile(this.file,t,"utf8",t=>{e()})})}catch(e){}}}function me(...e){switch(e[0]){case"--status":return"服務狀態確認\n 用法: status [額外參數]\n 範例:\n status\n";case"--log":return"日誌讀取(無參數時,會返回可用日誌)\n 用法: log [額外參數]\n\n 範例:\n log --y\n log --yesterday\n log --specific=2025-11-28\n 可用參數:\n today : 讀取今天的日誌\n t : 讀取今天的日誌\n yesterday : 讀取昨天的日誌\n y : 讀取昨天的日誌\n specific=<yyyy-MM-dd> : 讀取指定日期的日誌\n s=<yyyy-MM-dd> : 讀取指定日期的日誌\n";case"--exit":case"--close":return"安全離開\n 用法 1: exit\n 用法 2: close\n";default:return"用法: [可用指令] [額外參數]\n 範例:\n help\n help --log\n 可用指令列表:\n help : 獲得可用的指令,使用 [--可用指令] 獲得該指令的詳細說明。\n status : 顯示當前的服務狀態\n log : 讀取今天的日誌\n exit : 關閉連線\n close : exit 的別稱,關閉連線\n"}}function ye(e){return async(...t)=>{const{core:s,channel:o}=e.getStatusInfo(),r=o.reduce((e,t,s)=>e+` channel ${s+1} - ${t}\n`,"\n");let n=`服務狀態\n 核心狀態 : ${s}\n 通道列表 : ${0===o.length?"無服務通道":r}\n`;if(e.automaion){const{isOpen:t,isReady:s}=await e.automaion.getStateInfo();n+=" Unreal : ",n+=t?s?"已啟動,服務已就緒":"已啟動,但服務未就緒":"尚未啟動",n+="\n"}return n}}function Se(...e){const t=e[0];if(!t)return new Promise(e=>{N.readdir(J.appFolder,(t,s)=>{e(t?"沒有可用日誌":s.filter(e=>e.endsWith(".txt")).join("\n"))})});{let e=null;switch(t){case"--today":case"--t":e=p();break;case"--yesterday":case"--y":e=p().add(-1);break;default:{const s=new RegExp(/^--(specific|s)=([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})$/),o=t.match(s);if(o){const[t,s,r]=o[2].split("-");e=p(`${t}-${s.padStart(2,"0")}-${r.padStart(2,"0")}`);break}return"請使用 -- 開始,或使用 help --log 查看幫助"}}if(!e.isValid())return"指定了無效的日期,請再次確認";{const t=`log_${e.format("yyyy-MM-DD")}.txt`,s=J.addPath(J.appFolder,t);try{return N.readFileSync(s,"utf8")}catch(e){return"找不到日誌"}}}}class be{server;constructor(t,s){const o=function(e){return e?{help:me,status:ye(e),log:Se}:{}}(s),{port:r,host:n}=t;this.server=x(t=>{console.log(`⚙️ 新的連線來自: ${t.remoteAddress}`);const s=I.createInterface({input:t,output:t,prompt:"Aiia Admin > "});t.setEncoding("utf8"),t.write("歡迎使用Aiia Admin!\n"),t.write("使用 `help` 獲得幫助\n"),s.prompt(),s.on("line",async r=>{const[n,...i]=r.trim().split(/\s/),a=n.toLocaleLowerCase();if("exit"===a||"close"===a)t.write("👋 切斷連線...\n"),t.end();else{const r=e(o,a,null);if(r){const e=await r(...i);t.write("\n"+e+"\n")}else t.write(`"${n}"是無效的指令.\n`);s.prompt()}}),t.on("end",()=>{console.log(`⚙️ 切斷連線: ${t.remoteAddress}`)})}),this.server.listen(r,n,()=>{console.log(`連線指令: telnet 127.0.0.1 ${r}`)})}onDestroy(){this.server.close()}}class _e{config;stroage;expiment;apiProxy;chat;ueTools;automaion;state;browserSocketMap;destorySub;access_token;timestamp;socketAddr;get accessToken(){return this.access_token}constructor(e,t,s){this.config=e,this.stroage=t,this.expiment=s,this.state=0,this.destorySub=new S,this.browserSocketMap=new Map,this.timestamp=0,this.access_token=t.getItemSync(v);const{api:o,socket:r}=e.endPoint;this.socketAddr=r,this.apiProxy=new K(o),Y.code(U["指定模組初始化完成"],"api"),this.chat=new H(e),Y.code(U["指定模組初始化完成"],"chat"),this.ueTools=new ie(e),Y.code(U["指定模組初始化完成"],"udp tools"),this.openChannel=this.openChannel.bind(this),this.initService()}async start(){if(this.state>=1)throw new Error(L["重複初始化"]);this.state=1,await this.replaceAccessToken(),this.chat.memberChange.pipe(_(this.destorySub)).subscribe(({type:t,socket:s})=>{const o=s;switch(t){case"in":{const t=new V(s.id),n=new S;o.__cloudProxy=t;const i=(r=s.handshake.query.uuid,d(r)?e(r,"[0]",void 0):r??void 0);void 0===i?(this.getProjectList().then(e=>{s.emit("list",e)}).catch(e=>{Y.error(e),s.emit("list",null)}),b(s,"uuid").pipe(w(1),_(n)).subscribe(e=>{this.openChannel(e,o,t,n)})):this.openChannel(i,o,t,n),this.browserSocketMap.set(o,n);break}case"out":if(this.browserSocketMap.has(o)){const e=this.browserSocketMap.get(o);e.next(),e.complete(),this.browserSocketMap.delete(o)}}var r}),this.chat.start(),Y.code(U["啟動服務"],"chat");const{interaction:s}=t({},this.expiment);if(void 0!==s&&"number"==typeof s.port){const{port:e,host:t}=s,o=new be({port:e,host:t},this);this.destorySub.subscribe(()=>{o.onDestroy()})}}initService(){const{automaion:e}=t({},this.expiment);if(void 0!==e){const t=new fe(e);t.exec().then(e=>{this.ueTools.onStart(e)}),this.destorySub.subscribe(()=>{t.onDestroy()}),this.automaion=t}else this.ueTools.onStart();E(18e4).pipe(_(this.destorySub)).subscribe(()=>{this.inspectClientSocket()})}async replaceAccessToken(){try{const e=await this.apiProxy.getAccessToken(this.config.license,this.access_token),t=this.access_token;this.access_token=e,this.timestamp=0,this.stroage.setItemSync(v,e),this.stroage.setItemSync(p().format("yyyy-MM-DD[T]HH:mm:ss:sss"),t)}catch(e){throw Y.error(e),e}}async getProjectList(){try{return await this.apiProxy.getProjectList(this.config.license)}catch(e){throw Y.error(e),e}}async getProxySocketURL(e){const t=Date.now()-this.timestamp;return t<5e3&&await F(5100-t),this.timestamp=Date.now(),`${this.socketAddr}/v1/chat/project/${this.accessToken}/${e}`}async openChannel(t,o,r,n){b(o,"cloud").pipe(_(n)).subscribe(e=>{r.sendToCloud(e)}),b(o,"face").pipe(_(n)).subscribe(e=>{const t=e,s=null!==t;this.ueTools.eyeTrack(t),r.sendToCloud({request:"face_detect",content:s})}),r.message.pipe(_(n)).subscribe(t=>{const r=function(t){try{const s=JSON.parse(t);return e(s,"signal")?[s]:d(s)?s:l(s)}catch(e){return Y.error("message transfer fail",e),[]}}(t),n=[],i=[];s(r,e=>{switch(e.signal){case"osc":i.push(e);break;case"audio":"pcm"===e.command?this.ueTools.speech(e.content):"interrupted"===e.command&&this.ueTools.interrupted();default:n.push(e)}}),n.length>0&&o.emit("cloud",n),i.length>0&&this.ueTools.movement(i)}),r.close.pipe(_(n)).subscribe(async e=>{switch(o.emit("channel",e),e){case"close":case"no_permissions":r.onDestroy();break;case"reconnect":try{await this.replaceAccessToken();const e=await this.getProxySocketURL(t);o.connected?r.reconnect(e):Y.error(`The browser(${r.id}) leaves before reconnecting to the cloud`)}catch(e){r.onDestroy(),o.emit("channel","net_error"),Y.error(e)}}}),r.linkStart.pipe(_(n)).subscribe(()=>{o.emit("channel","open")}),n.subscribe(()=>{r.onClose()});const i=await this.getProxySocketURL(t);o.connected?r.start(i):Y.error(`The browser(${r.id}) leaves before connecting to the cloud`)}inspectClientSocket(){const e=Array.from(this.browserSocketMap);s(e,([e,t])=>{e.disconnected&&(t.next(),t.complete(),this.browserSocketMap.delete(e),Y.info(`The browser(${e.id}) should be closed. Auto clean service.`))})}getStatusInfo(){return{core:0===this.state?"未啟動":1===this.state?"服務中":"已結束",channel:Array.from(this.browserSocketMap).map(([e])=>e.__cloudProxy.getStatusInfo())}}onDestroy(){this.state=2,this.apiProxy.onDestroy(),this.chat.onDestroy(),this.ueTools.onDestroy(),this.destorySub.next(),this.destorySub.complete(),Y.code(U["服務結束"])}}function we(s){if($){if("string"!=typeof e(s,"license",void 0))throw new Error(L["未提供憑證"]);!function(e){const{info:t,error:s,warn:o,debug:r,fatal:n,code:i}=function(e){if(void 0===e||"all"===e)return{code:!0,error:!0,warn:!0,info:!0,debug:!0,fatal:!0};if("none"===e)return{error:!1,code:!1,warn:!1,info:!1,debug:!1,fatal:!1};if("boolean"==typeof e)return{code:e,error:e,warn:e,info:e,debug:e,fatal:e};{const t=new Set(e);return{code:t.has("code"),debug:t.has("debug"),fatal:t.has("fatal"),error:t.has("error"),warn:t.has("warn"),info:t.has("info")}}}(e);return i||(j.code=()=>{}),r||(j.debug=()=>{}),t||(j.info=()=>{}),o||(j.warn=()=>{}),s||(j.error=()=>{}),n||(j.fatal=()=>{}),B}(e(s,"debug",!0))(new Q),Y.code(U["開始初始化必要模組"]);const o=new W(t({},s));Y.code(U["指定模組初始化完成"],"config");const r=new Z;Y.code(U["指定模組初始化完成"],"storage");const n=new _e(o,r,e(s,"experiment"));return Y.code(U["指定模組初始化完成"],"core"),n.start(),Y.code(U["啟動服務"],"main core"),()=>{n.onDestroy()}}throw new Error(L["環境錯誤(nodejs)"])}export{we as aiiaCore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphen.ai/aiia-sdk",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "keywords": [],
5
5
  "author": {
6
6
  "name": "Jay Huang",
@@ -60,6 +60,7 @@
60
60
  "@types/node": "^24.2.1",
61
61
  "@types/node-osc": "^9.1.0",
62
62
  "@types/ws": "^8.18.1",
63
+ "@unrs/resolver-binding-darwin-arm64": "^1.11.1",
63
64
  "jest": "^30.1.3",
64
65
  "jest-environment-jsdom": "^30.1.2",
65
66
  "rollup": "^4.46.2",
@@ -70,6 +71,7 @@
70
71
  },
71
72
  "dependencies": {
72
73
  "@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
74
+ "@nut-tree-fork/nut-js": "^4.2.6",
73
75
  "axios": "^1.11.0",
74
76
  "lodash-es": "^4.17.21",
75
77
  "mathjs": "^14.8.1",
@@ -78,6 +80,7 @@
78
80
  "rxjs": "^7.8.2",
79
81
  "socket.io": "^4.8.1",
80
82
  "socket.io-client": "^4.8.1",
83
+ "systeminformation": "^5.27.11",
81
84
  "ws": "^8.18.3"
82
85
  }
83
86
  }