codicent-app-sdk 0.7.2 → 0.7.9

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.
@@ -27,6 +27,14 @@ interface RealtimeEvent {
27
27
  [key: string]: any;
28
28
  };
29
29
  }
30
+ /** Single entry in the tool call debug log */
31
+ export interface ToolCallLogEntry {
32
+ timestamp: string;
33
+ name: string;
34
+ args: Record<string, unknown>;
35
+ result?: unknown;
36
+ error?: string;
37
+ }
30
38
  export interface RealtimeVoice {
31
39
  items: ItemType[];
32
40
  realtimeEvents: RealtimeEvent[];
@@ -49,6 +57,8 @@ export interface RealtimeVoice {
49
57
  setUsername: (username: string) => void;
50
58
  updateInstructions: (instructions: string) => void;
51
59
  setLanguage: (language: string) => void;
60
+ toolCallLog: ToolCallLogEntry[];
61
+ clearToolCallLog: () => void;
52
62
  }
53
63
  declare const useRealtimeVoiceAI: (codicentService: CodicentService, tools: {
54
64
  definition: ToolDefinitionType;
@@ -1 +1 @@
1
- {"version":3,"file":"useRealtimeVoiceAI.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRealtimeVoiceAI.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAK/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,sBAAsB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,kBAAkB,oBACL,eAAe,SACzB;IAAE,UAAU,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,EAAE,UACtD,MAAM,aACH,MAAM,KAChB,aAAa,GAAG,SAysBlB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"useRealtimeVoiceAI.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRealtimeVoiceAI.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAK/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC/B;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,sBAAsB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,QAAA,MAAM,kBAAkB,oBACL,eAAe,SACzB;IAAE,UAAU,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,EAAE,UACtD,MAAM,aACH,MAAM,KAChB,aAAa,GAAG,SAquBlB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("../utils/wav_renderer.js");require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js");var n=require("../lib/wavtools/lib/wav_stream_player.js"),r=require("../lib/wavtools/lib/wav_recorder.js"),a=require("../config/index.js");exports.default=(o,s,i,c)=>{const l=!!a.getConfigValue("APP_CONFIG"),u=!!a.getConfigValue("APP_BUTTONS");l||console.warn("APP_CONFIG is not set. Voice AI will not be available."),u||console.warn("APP_BUTTONS is not set. Voice AI will not be available.");const d=a.getConfigValue("APP_CONFIG"),p=a.getConfigValue("APP_BUTTONS");a.getConfigValue("API_BASE_URL").replace(/\/$/,""),a.getConfigValue("USE_REALTIME_SESSION_ENDPOINT"),a.getConfigValue("REALTIME_SESSION_ENDPOINT");const f=i||a.getConfigValue("REALTIME_VOICE_MODEL")||"alloy",g=c||a.getConfigValue("REALTIME_VOICE_PROVIDER")||"openai",m=["alloy","shimmer","echo","verse","nova","fable","onyx"],y=m.includes(f)?f:"alloy";f!==y&&console.warn(`[codicent-app-sdk] Voice "${f}" is not supported in the current SDK version. Supported voices: ${m.join(", ")}. Falling back to "${y}".`);const _=e.useRef(new r.WavRecorder({sampleRate:24e3})),S=e.useRef(new n.WavStreamPlayer({sampleRate:24e3})),w=e.useRef(null),v=e.useRef(null),h=e.useRef(null),C=e.useRef(null),O=e.useRef(!1),R=e.useRef(null),I=e.useRef(null),T=e.useRef(null),E=e.useRef((new Date).toISOString()),[N,b]=e.useState([]),[A,V]=e.useState([]),[P,k]=e.useState(!1),[D,L]=e.useState(!1),[M,x]=e.useState(!1),[F,J]=e.useState(!1),$=e.useRef(0),j=e.useRef(0),[q,U]=e.useState(""),[B,W]=e.useState("en-US"),z=e.useRef(new Map),G=e.useRef(null),[H,K]=e.useState((()=>l&&u&&p&&d&&d.apps&&d.apps[p]?d.apps[p].voiceInstructions||d.REALTIME_VOICE_INSTRUCTIONS||"":d&&d.REALTIME_VOICE_INSTRUCTIONS||"")),Q=e.useCallback((e=>{const t=E.current,n=new Date(t).valueOf(),r=new Date(e).valueOf()-n,a=Math.floor(r/10)%100,o=Math.floor(r/1e3)%60,s=e=>{let t=e+"";for(;t.length<2;)t="0"+t;return t};return`${s(Math.floor(r/6e4)%60)}:${s(o)}.${s(a)}`}),[]),X=e.useCallback((async()=>{try{E.current=(new Date).toISOString(),k(!0),V([]),b([]),z.current.clear();const e=await o.getRealtimeSessionToken(y,g);if(!e)throw new Error("No ephemeral key returned from session endpoint");const t=new RTCPeerConnection;w.current=t,h.current||(h.current=new Audio,h.current.autoplay=!0),t.ontrack=e=>{h.current&&e.streams[0]&&(h.current.srcObject=e.streams[0])};const n=await navigator.mediaDevices.getUserMedia({audio:!0});C.current=n;const r=n.getTracks()[0];t.addTrack(r,n);const i=t.createDataChannel("oai-events");v.current=i,i.addEventListener("message",(e=>{try{const t=JSON.parse(e.data);if("session.created"===t.type);else if("conversation.item.created"===t.type)b((e=>[...e,t.item]));else if("conversation.item.input_audio_transcription.completed"===t.type)b((e=>{const n=[...e],r=n.findIndex((e=>e.id===t.item_id));return-1!==r&&n[r].formatted&&(n[r].formatted.transcript=t.transcript),n}));else if("response.audio_transcript.delta"===t.type);else if("response.audio_transcript.done"===t.type);else if("response.output_item.added"===t.type){const e=t.item;"function_call"===e?.type&&(console.log("[Voice AI] Function call initiated:",e.name),z.current.set(e.id,{name:e.name||"",arguments:"",call_id:e.call_id||e.id}))}else if("response.function_call_arguments.delta"===t.type){const e=t.item_id,n=t.delta,r=z.current.get(e);r&&n&&(r.arguments+=n,z.current.set(e,r))}else if("response.function_call_arguments.done"===t.type){const e=t.item_id,n=z.current.get(e);if(n){console.log(`[Voice AI] Executing tool: ${n.name}`);const t=s.find((e=>e.definition.name===n.name));if(t){let e={};try{e=n.arguments?JSON.parse(n.arguments):{}}catch(t){console.error("[Voice AI] Failed to parse tool arguments:",t),e={}}Promise.resolve(t.handler(e)).then((e=>{console.log(`[Voice AI] Tool ${n.name} completed:`,e),i&&"open"===i.readyState&&(i.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify(e)}})),i.send(JSON.stringify({type:"response.create"})))})).catch((e=>{console.error(`[Voice AI] Tool ${n.name} failed:`,e),i&&"open"===i.readyState&&(i.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify({error:e.message||"Tool execution failed"})}})),i.send(JSON.stringify({type:"response.create"})))}))}else console.error(`[Voice AI] Tool not found: ${n.name}`);z.current.delete(e)}}else"error"===t.type&&console.error("Server error:",t.error)}catch(t){console.warn("Invalid message:",e.data)}})),i.onopen=()=>{console.log("Data channel opened, configuring session");const e=G.current||H;G.current=null,console.log("[Voice AI] Sending session.update with instructions length:",e.length,"preview:",e.substring(0,100));const t="azure"===g.toLowerCase();i.send(JSON.stringify({type:"session.update",session:{...t?{type:"realtime"}:{},instructions:e.replace("{{name}}",q).replace("{{language}}",B).replace("{{time}}",(new Date).toISOString()),...t?{}:{modalities:["text","audio"]},input_audio_transcription:{model:"whisper-1"},turn_detection:{type:"server_vad",threshold:.5,prefix_padding_ms:300,silence_duration_ms:500},voice:y,temperature:.8,max_response_output_tokens:4096,...t?{}:{input_audio_format:"pcm16",output_audio_format:"pcm16"},tools:s.map((e=>({type:"function",...e.definition})))}})),L(!0)},i.onclose=()=>{console.log("Data channel closed"),L(!1)};const c=await t.createOffer();await t.setLocalDescription(c);let l="gpt-4o-realtime-preview",u=null;try{const e=a.getConfigValue("REALTIME_CONFIG_ENDPOINT"),t="azure"===g.toLowerCase();if(e||t){const e=await o.getRealtimeConfig(g);e&&e.model&&(l=e.model),e&&e.url&&(u=e.url)}}catch(e){console.warn("Failed to fetch realtime config, using default model:",e)}g.toLowerCase();const d=u??`https://api.openai.com/v1/realtime?model=${l}`,p={Authorization:`Bearer ${e}`,"Content-Type":"application/sdp"},f=await fetch(d,{method:"POST",body:c.sdp,headers:p});if(!f.ok)throw new Error(`Failed to get SDP answer: ${f.statusText}`);const m={type:"answer",sdp:await f.text()};await t.setRemoteDescription(m),x(!1);const O=_.current,R=S.current;await O.begin(),await R.connect()}catch(e){throw console.error("[codicent-app-sdk] Failed to establish WebRTC connection:",e),k(!1),e}}),[o,y,H,q,B,s]),Y=e.useCallback((async()=>{k(!1),L(!1),V([]),b([]),z.current.clear(),G.current=null,v.current&&(v.current.close(),v.current=null),w.current&&(w.current.close(),w.current=null),C.current&&(C.current.getTracks().forEach((e=>e.stop())),C.current=null),h.current&&(h.current.pause(),h.current.srcObject=null);const e=_.current;await e.end();const t=S.current;await t.interrupt()}),[]),Z=e.useCallback((async e=>{const t=v.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"conversation.item.delete",item_id:e}))}),[]),ee=e.useCallback((async()=>{J(!0);const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"input_audio_buffer.commit"}))}),[]),te=e.useCallback((async()=>{J(!1);const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"response.create"}))}),[]),ne=e.useCallback((async e=>{const t=v.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"session.update",session:{turn_detection:"none"===e?null:{type:"server_vad"}}})),x("none"===e)}),[]);e.useEffect((()=>{if(q&&D){const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"session.update",session:{..."azure"===g.toLowerCase()?{type:"realtime"}:{},instructions:H.replace("{{name}}",q).replace("{{language}}",B).replace("{{time}}",(new Date).toISOString())}}))}}),[H,q,B,D]),e.useEffect((()=>{let e=!0;const n=_.current,r=R.current;let a=null;const o=S.current,s=I.current;let i=null;const c=()=>{if(e){if(r&&(r.width&&r.height||(r.width=r.offsetWidth,r.height=r.offsetHeight),a=a||r.getContext("2d"),a)){a.clearRect(0,0,r.width,r.height);const e=n.recording?n.getFrequencies("voice"):{values:new Float32Array([0])},o=1-Math.max(...e.values);$.current=o,t.WavRenderer.drawCircularBars(r,a,e.values,"#0099ff",20,0,8)}if(s&&(s.width&&s.height||(s.width=s.offsetWidth,s.height=s.offsetHeight),i=i||s.getContext("2d"),i)){i.clearRect(0,0,s.width,s.height);const e=o.analyser?o.getFrequencies("voice"):{values:new Float32Array([0])},n=1-Math.max(...e.values);j.current=n,t.WavRenderer.drawCircularBars(s,i,e.values,"#009900",20,0,8)}window.requestAnimationFrame(c)}};return c(),()=>{e=!1}}),[]),e.useEffect((()=>{!O.current&&s&&H&&(O.current=!0)}),[s,H]);const re=e.useCallback((e=>{K(e);const t=v.current;if(t&&"open"===t.readyState){const n=e.replace("{{name}}",q).replace("{{language}}",B).replace("{{time}}",(new Date).toISOString());t.send(JSON.stringify({type:"session.update",session:{instructions:n}}))}else G.current=e}),[q,B]);return e.useMemo((()=>{if(l&&u)return{items:N,realtimeEvents:A,isConnected:P,isSessionReady:D,canPushToTalk:M,isRecording:F,clientCanvasRef:R,serverCanvasRef:I,eventsScrollRef:T,formatTime:Q,connectConversation:X,disconnectConversation:Y,deleteConversationItem:Z,startRecording:ee,stopRecording:te,changeTurnEndType:ne,getRecorderLevel:()=>$.current,getStreamLevel:()=>j.current,setUsername:U,updateInstructions:re,setLanguage:W}}),[l,u,N,A,P,D,M,F,R,I,T])};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("../utils/wav_renderer.js");require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js");var n=require("../lib/wavtools/lib/wav_stream_player.js"),r=require("../lib/wavtools/lib/wav_recorder.js"),a=require("../config/index.js");exports.default=(o,s,i,c)=>{const l=!!a.getConfigValue("APP_CONFIG"),u=!!a.getConfigValue("APP_BUTTONS");l||console.warn("APP_CONFIG is not set. Voice AI will not be available."),u||console.warn("APP_BUTTONS is not set. Voice AI will not be available.");const d=a.getConfigValue("APP_CONFIG"),p=a.getConfigValue("APP_BUTTONS");a.getConfigValue("API_BASE_URL").replace(/\/$/,""),a.getConfigValue("USE_REALTIME_SESSION_ENDPOINT"),a.getConfigValue("REALTIME_SESSION_ENDPOINT");const f=i||a.getConfigValue("REALTIME_VOICE_MODEL")||"alloy",g=c||a.getConfigValue("REALTIME_VOICE_PROVIDER")||"openai",m=["alloy","shimmer","echo","verse","nova","fable","onyx"],y=m.includes(f)?f:"alloy";f!==y&&console.warn(`[codicent-app-sdk] Voice "${f}" is not supported in the current SDK version. Supported voices: ${m.join(", ")}. Falling back to "${y}".`);const S=e.useRef(new r.WavRecorder({sampleRate:24e3})),_=e.useRef(new n.WavStreamPlayer({sampleRate:24e3})),w=e.useRef(null),v=e.useRef(null),h=e.useRef(null),O=e.useRef(null),C=e.useRef(!1),I=e.useRef(null),R=e.useRef(null),T=e.useRef(null),E=e.useRef((new Date).toISOString()),[N,b]=e.useState([]),[A,V]=e.useState([]),[P,D]=e.useState([]),[k,L]=e.useState(!1),[x,M]=e.useState(!1),[J,F]=e.useState(!1),[$,j]=e.useState(!1),q=e.useRef(0),U=e.useRef(0),[B,W]=e.useState(""),[G,z]=e.useState("en-US"),H=e.useRef(new Map),K=e.useRef(null),[Q,X]=e.useState((()=>l&&u&&p&&d&&d.apps&&d.apps[p]?d.apps[p].voiceInstructions||d.REALTIME_VOICE_INSTRUCTIONS||"":d&&d.REALTIME_VOICE_INSTRUCTIONS||"")),Y=e.useCallback((e=>{const t=E.current,n=new Date(t).valueOf(),r=new Date(e).valueOf()-n,a=Math.floor(r/10)%100,o=Math.floor(r/1e3)%60,s=e=>{let t=e+"";for(;t.length<2;)t="0"+t;return t};return`${s(Math.floor(r/6e4)%60)}:${s(o)}.${s(a)}`}),[]),Z=e.useCallback((async()=>{try{E.current=(new Date).toISOString(),L(!0),V([]),b([]),H.current.clear();const e=await o.getRealtimeSessionToken(y,g);if(!e)throw new Error("No ephemeral key returned from session endpoint");const t=new RTCPeerConnection;w.current=t,h.current||(h.current=new Audio,h.current.autoplay=!0),t.ontrack=e=>{h.current&&e.streams[0]&&(h.current.srcObject=e.streams[0])};const n=await navigator.mediaDevices.getUserMedia({audio:!0});O.current=n;const r=n.getTracks()[0];t.addTrack(r,n);const i=t.createDataChannel("oai-events");v.current=i,i.addEventListener("message",(e=>{try{const t=JSON.parse(e.data);if("session.created"===t.type);else if("conversation.item.created"===t.type)b((e=>[...e,t.item]));else if("conversation.item.input_audio_transcription.completed"===t.type)b((e=>{const n=[...e],r=n.findIndex((e=>e.id===t.item_id));return-1!==r&&n[r].formatted&&(n[r].formatted.transcript=t.transcript),n}));else if("response.audio_transcript.delta"===t.type);else if("response.audio_transcript.done"===t.type);else if("response.output_item.added"===t.type){const e=t.item;"function_call"===e?.type&&(console.log("[Voice AI] Function call initiated:",e.name),H.current.set(e.id,{name:e.name||"",arguments:"",call_id:e.call_id||e.id}))}else if("response.function_call_arguments.delta"===t.type){const e=t.item_id,n=t.delta,r=H.current.get(e);r&&n&&(r.arguments+=n,H.current.set(e,r))}else if("response.function_call_arguments.done"===t.type){const e=t.item_id,n=H.current.get(e);if(n){console.log(`[Voice AI] Executing tool: ${n.name}`);const t=s.find((e=>e.definition.name===n.name));if(t){let e={};try{e=n.arguments?JSON.parse(n.arguments):{}}catch(t){console.error("[Voice AI] Failed to parse tool arguments:",t),e={}}const r={timestamp:(new Date).toISOString(),name:n.name,args:e};Promise.resolve(t.handler(e)).then((e=>{console.log(`[Voice AI] Tool ${n.name} completed:`,e),D((t=>[...t,{...r,result:e}])),i&&"open"===i.readyState&&(i.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify(e)}})),i.send(JSON.stringify({type:"response.create"})))})).catch((e=>{console.error(`[Voice AI] Tool ${n.name} failed:`,e),D((t=>[...t,{...r,error:e.message||"Tool execution failed"}])),i&&"open"===i.readyState&&(i.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify({error:e.message||"Tool execution failed"})}})),i.send(JSON.stringify({type:"response.create"})))}))}else console.error(`[Voice AI] Tool not found: ${n.name}`),D((e=>[...e,{timestamp:(new Date).toISOString(),name:n.name,args:{},error:"Tool not found"}]));H.current.delete(e)}}else"error"===t.type&&console.error("Server error:",JSON.stringify(t.error))}catch(t){console.warn("Invalid message:",e.data)}})),i.onopen=()=>{console.log("Data channel opened, configuring session");const e=K.current||Q;K.current=null,console.log("[Voice AI] Sending session.update with instructions length:",e.length,"preview:",e.substring(0,100)),i.send(JSON.stringify({type:"session.update",session:{instructions:e.replace("{{name}}",B).replace("{{language}}",G).replace("{{time}}",(new Date).toISOString()),modalities:["text","audio"],input_audio_transcription:{model:"whisper-1"},turn_detection:{type:"server_vad",threshold:.5,prefix_padding_ms:300,silence_duration_ms:500},voice:y,temperature:.8,max_response_output_tokens:4096,input_audio_format:"pcm16",output_audio_format:"pcm16",tools:s.map((e=>({type:"function",...e.definition})))}})),M(!0)},i.onclose=()=>{console.log("Data channel closed"),M(!1)};const c=await t.createOffer();await t.setLocalDescription(c);let l="gpt-4o-realtime-preview",u=null;try{const e=a.getConfigValue("REALTIME_CONFIG_ENDPOINT"),t="azure"===g.toLowerCase();if(e||t){const e=await o.getRealtimeConfig(g);e&&e.model&&(l=e.model),e&&e.url&&(u=e.url)}}catch(e){console.warn("Failed to fetch realtime config, using default model:",e)}const d=u??`https://api.openai.com/v1/realtime?model=${l}`,p={Authorization:`Bearer ${e}`,"Content-Type":"application/sdp"},f=await fetch(d,{method:"POST",body:c.sdp,headers:p});if(!f.ok)throw new Error(`Failed to get SDP answer: ${f.statusText}`);const m={type:"answer",sdp:await f.text()};await t.setRemoteDescription(m),F(!1);const C=S.current,I=_.current;await C.begin(),await I.connect()}catch(e){throw console.error("[codicent-app-sdk] Failed to establish WebRTC connection:",e),L(!1),e}}),[o,y,Q,B,G,s]),ee=e.useCallback((async()=>{L(!1),M(!1),V([]),b([]),H.current.clear(),K.current=null,v.current&&(v.current.close(),v.current=null),w.current&&(w.current.close(),w.current=null),O.current&&(O.current.getTracks().forEach((e=>e.stop())),O.current=null),h.current&&(h.current.pause(),h.current.srcObject=null);const e=S.current;await e.end();const t=_.current;await t.interrupt()}),[]),te=e.useCallback((async e=>{const t=v.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"conversation.item.delete",item_id:e}))}),[]),ne=e.useCallback((async()=>{j(!0);const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"input_audio_buffer.commit"}))}),[]),re=e.useCallback((async()=>{j(!1);const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"response.create"}))}),[]),ae=e.useCallback((async e=>{const t=v.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"session.update",session:{turn_detection:"none"===e?null:{type:"server_vad"}}})),F("none"===e)}),[]);e.useEffect((()=>{if(B&&x){const e=v.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"session.update",session:{..."azure"===g.toLowerCase()?{type:"realtime"}:{},instructions:Q.replace("{{name}}",B).replace("{{language}}",G).replace("{{time}}",(new Date).toISOString())}}))}}),[Q,B,G,x]),e.useEffect((()=>{let e=!0;const n=S.current,r=I.current;let a=null;const o=_.current,s=R.current;let i=null;const c=()=>{if(e){if(r&&(r.width&&r.height||(r.width=r.offsetWidth,r.height=r.offsetHeight),a=a||r.getContext("2d"),a)){a.clearRect(0,0,r.width,r.height);const e=n.recording?n.getFrequencies("voice"):{values:new Float32Array([0])},o=1-Math.max(...e.values);q.current=o,t.WavRenderer.drawCircularBars(r,a,e.values,"#0099ff",20,0,8)}if(s&&(s.width&&s.height||(s.width=s.offsetWidth,s.height=s.offsetHeight),i=i||s.getContext("2d"),i)){i.clearRect(0,0,s.width,s.height);const e=o.analyser?o.getFrequencies("voice"):{values:new Float32Array([0])},n=1-Math.max(...e.values);U.current=n,t.WavRenderer.drawCircularBars(s,i,e.values,"#009900",20,0,8)}window.requestAnimationFrame(c)}};return c(),()=>{e=!1}}),[]),e.useEffect((()=>{!C.current&&s&&Q&&(C.current=!0)}),[s,Q]);const oe=e.useCallback((e=>{X(e);const t=v.current;if(t&&"open"===t.readyState){const n=e.replace("{{name}}",B).replace("{{language}}",G).replace("{{time}}",(new Date).toISOString());t.send(JSON.stringify({type:"session.update",session:{instructions:n}}))}else K.current=e}),[B,G]);return e.useMemo((()=>{if(l&&u)return{items:N,realtimeEvents:A,isConnected:k,isSessionReady:x,canPushToTalk:J,isRecording:$,clientCanvasRef:I,serverCanvasRef:R,eventsScrollRef:T,formatTime:Y,connectConversation:Z,disconnectConversation:ee,deleteConversationItem:te,startRecording:ne,stopRecording:re,changeTurnEndType:ae,getRecorderLevel:()=>q.current,getStreamLevel:()=>U.current,setUsername:W,updateInstructions:oe,setLanguage:z,toolCallLog:P,clearToolCallLog:()=>D([])}}),[l,u,N,A,P,k,x,J,$,I,R,T,Z,ee,oe,Y,te,ne,re,ae,W,z])};
@@ -1 +1 @@
1
- {"version":3,"file":"useTools.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,QAAA,MAAM,QAAQ,QACP,eAAe,YACX,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,8BAG7B,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,qBAGhC,gBAAgB,SACf,MAAM,qBACK,MAAM,IAAI,gBAGd,WAAW,WA2oB1B,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"useTools.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,QAAA,MAAM,QAAQ,QACP,eAAe,YACX,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,8BAG7B,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,qBAGhC,gBAAgB,SACf,MAAM,qBACK,MAAM,IAAI,gBAGd,WAAW,WAksB1B,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("../config/index.js");require("../utils/MessageContent.js"),require("../node_modules/tinycolor2/esm/tinycolor.js"),require("react/jsx-runtime"),require("../_virtual/index.js"),require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var s=require("../utils/device.js");require("exceljs");exports.default=(a,r=e=>{console.log(e)},i=e=>{console.log(e)},n,o,c=()=>{console.log("stopVoiceSession")},p)=>e.useMemo((()=>{const e=[],d=(t,s)=>{e.push({definition:t,handler:s})},l=async()=>{const e=t.getConfigValue("APP_CONFIG"),s=t.getConfigValue("APP_BUTTONS");return[...s&&e.apps[s]?e.apps[s].tasks:[],...await a.getAppTasks()]};d({name:"stop_voice_session",description:"Stops the voice session.",parameters:{type:"object",properties:{}}},(async()=>(console.log("TOOL: stop_voice_session"),r(void 0),c(),{ok:!0}))),d({name:"inject_script",description:"Injects a script into the html/canvas view.",parameters:{type:"object",properties:{script:{type:"string",description:"The script to inject"}},required:["script"]}},(async({script:e})=>(console.log("TOOL: inject_script",e),i(e),{ok:!0}))),d({name:"post_codicent_message",description:"Posts a message to Codicent, only when requested by the user.",parameters:{type:"object",properties:{content:{type:"string",description:'The codicent message text, e.g. "remember to call Linda"'},parent_id:{type:"string",description:'The parent message id of a previous message that you will add a new revision to, by setting parent_id to the origin message being "edited"'}},required:["content"]}},(async({content:e,parent_id:t})=>await a.sendMessage(e,t))),d({name:"update_codicent_message",description:"Posts an edited message, which replaces the old message.",parameters:{type:"object",properties:{content:{type:"string",description:'The updated codicent message text, e.g. "remember to call Johan", usually including the tags from the original message.'},parent_id:{type:"string",description:"The message id of a previous/old message to be updated."}},required:["content","parent_id"]}},(async({content:e,parent_id:t})=>await a.sendMessage(e,t))),d({name:"get_messages_list",description:"Gets messages tagged with given tag name, optionally limited by num_messages. The messages are listed in chronological order, latest first.",parameters:{type:"object",properties:{num_messages:{type:"integer",description:"The number of matching messages to return"},search_text:{type:"string",description:"Search text to filter messages by"},tag_name:{type:"string",description:"Name of tag to find messages by"},after_timestamp:{type:"string",description:"Only return messages after this timestamp"},before_timestamp:{type:"string",description:"Only return messages before this timestamp"}},required:["num_messages"]}},(async({num_messages:e,search_text:t,tag_name:s,after_timestamp:r,before_timestamp:i})=>{let n=await a.getMessages(s?[s]:[],void 0,e);t&&(n=n.filter((e=>e.content.includes(t)))),r&&(n=n.filter((e=>e.createdAt>r))),i&&(n=n.filter((e=>e.createdAt<i)));const o=n.slice(0,e);return console.log("get_messages_list result:",o),o})),d({name:"chat_with_codicent_ai",description:"Sends a message to Codicent AI and gets a response message back. Remember that you can always ask codicent via this tool to find out more about what you can achieve from chatting with the codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"Text of message to the codicent"}},required:["message"]}},(async({message:e})=>await a.chat(e))),d({name:"create_todo",description:"Saves a todo message in Codicent.",parameters:{type:"object",properties:{text:{type:"string",description:"Text of todo"}},required:["text"]}},(async({text:e})=>await a.sendMessage("#todo "+e))),d({name:"place_order",description:"Places and order with the Codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"The text message of the order as spoken by the user."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#do Jag kommer ge dig en text (nedan) på vad jag ska beställa. Använd bara den text du får av mig, inga tools.\n Du ska skapa en tabell med detaljerad information inklusive lagerstatus \n för varje artikel i beställningen. Svara först när du har all info på plats. Ställ inga frågor på vägen utan använd det du får av mig.\n \n ----\n EXEMPEL PÅ SVAR:\n ----\n Här är en tabell med detaljerad information inklusive lagerstatus för varje artikel i beställningen:\n \n Artikel\tHyresobjekt\tNamn\tLagertyp\tGrupp\tNamn.1\tSRA/SBEF\tLeverantör\tLevnr\tDepå\tLager\tReserverat\tUthyrt nu\tTillgängligt nu\tMärke/typ\tTillverkarnr\tTillv.år\tInköpsdato\tInköpspris\tVikt\n Elverk\t12345\tElverk 1\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör A\t001\tDepå 1\t5\t0\t1\t4\tHonda EU22i\t123456\t2020\t2020-01-01\t10000 SEK\t21 kg\n Elverk\t12345\tElverk 2\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör B\t002\tDepå 2\t3\t0\t1\t2\tYamaha EF2000iS\t654321\t2019\t2019-05-01\t9000 SEK\t20 kg\n Observera att denna tabell är baserad på den information som finns tillgänglig i lagerstatusen. Om du behöver ytterligare detaljer eller om något specifikt saknas, vänligen meddela mig.\n ----\n \n --- här följer texten på vad jag vill beställa ---\n \n \n ${e}`))),d({name:"remember_info",description:"Saves info in your internal memory for future use. Use this tool to save insights on how to perform our conversations and tasks related to them.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve our future conversations."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#index #insights\n\n${e}`))),d({name:"save_to_crm",description:"Saves CRM related info to your memory (logbook).",parameters:{type:"object",properties:{message:{type:"string",description:"CRM log entry description/contents."},company:{type:"string",description:"Company name for CRM log entry."},contact:{type:"string",description:"Contact name for CRM log entry."}},required:["message","company"]}},(async({message:e,company:s,contact:r})=>{const i=a.codicent||t.getConfigValue("APP_NAME")||"";return i||console.warn("[save_to_crm] No codicent target — message will be posted without @mention"),await a.sendMessage(`#crm\n\nFöretag: ${s}\n\nInfo: ${e}${r?"\n\nKontakt: "+r:""}`,void 0,i)})),d({name:"set_page_html",description:"Sets the innerHtml of the div covering the full app page. You can display any html/javascript/style/mermaid content here for the user to see ;-). This view is usually referred to as the canvas, or tavlan in Swedish.",parameters:{type:"object",properties:{html:{type:"string",description:"The inner html to set the page to."}},required:["html"]}},(async({html:e})=>(r(e),n.play(),{ok:!0}))),d({name:"hide_page_html",description:"Hides the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>(r(void 0),{ok:!0}))),d({name:"get_page_html",description:"Gets the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>({html:o}))),d({name:"get_tasks",description:"Gets the list of possible tasks that can be performed. This lists task names and their id:s. Use get_task to get the task instructions given a task id.",parameters:{type:"object",properties:{},required:[]}},(async e=>(await l()).map((e=>({id:e.id,name:e.title}))))),d({name:"get_task",description:"Gets the task instructions for a given task id. Use get_tasks to get a list of possible tasks (with their id:s).",parameters:{type:"object",properties:{task_id:{type:"string",description:"The task id to get instructions for."}},required:["task_id"]}},(async({task_id:e})=>{const t=(await l()).find((t=>t.id===e));return t?t.content:"Task not found"})),d({name:"get_chat_conversation",description:"Gets a chat conversation (message thread) by message ID. Returns all messages in the conversation thread in chronological order.",parameters:{type:"object",properties:{message_id:{type:"string",description:"The ID of any message in the conversation to retrieve the full thread."}},required:["message_id"]}},(async({message_id:e})=>{try{return{ok:!0,messages:await a.getChatHistory(e)}}catch(e){return{ok:!1,error:e.message||"Failed to get chat conversation"}}})),d({name:"get_gps_location",description:"Gets the current GPS location of the device. Returns latitude and longitude coordinates. Use this when the user asks about nearby places, directions, or anything location-dependent.",parameters:{type:"object",properties:{},required:[]}},(async e=>{try{const e=await s.getGpsLocation();return e?{ok:!0,latitude:e.coords.latitude,longitude:e.coords.longitude,accuracy:e.coords.accuracy}:{ok:!1,error:"GPS not available"}}catch{return{ok:!1,error:"Failed to get GPS location"}}}));const{additionalTools:g=[],mergeStrategy:m="merge"}=p||{};return"replace"===m?g:"builtin-only"===m?e:[...e,...g]}),[a,r,i,o,n,p]);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("../config/index.js");require("../utils/MessageContent.js"),require("../node_modules/tinycolor2/esm/tinycolor.js"),require("react/jsx-runtime"),require("../_virtual/index.js"),require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var s=require("../utils/device.js");require("exceljs");exports.default=(a,r=e=>{console.log(e)},n=e=>{console.log(e)},i,o,c=()=>{console.log("stopVoiceSession")},d)=>e.useMemo((()=>{const e=[],p=(t,s)=>{e.push({definition:t,handler:s})},g=async()=>{const e=t.getConfigValue("APP_CONFIG"),s=t.getConfigValue("APP_BUTTONS");return[...s&&e.apps[s]?e.apps[s].tasks:[],...await a.getAppTasks()]};p({name:"stop_voice_session",description:"Stops the voice session.",parameters:{type:"object",properties:{}}},(async()=>(console.log("TOOL: stop_voice_session"),r(void 0),c(),{ok:!0}))),p({name:"inject_script",description:"Injects a script into the html/canvas view.",parameters:{type:"object",properties:{script:{type:"string",description:"The script to inject"}},required:["script"]}},(async({script:e})=>(console.log("TOOL: inject_script",e),n(e),{ok:!0}))),p({name:"post_codicent_message",description:"Posts a message to Codicent. Include any relevant #tags at the start of the content, e.g. '#crm\\n\\nFöretag: ...' or '#todo Buy milk'.",parameters:{type:"object",properties:{content:{type:"string",description:"The full message text to post, including any leading #tags. Example: '#crm\\n\\nFöretag: Ahlsell Gävle\\nKontakt: Johan Lind, Säljchef'"},parent_id:{type:"string",description:'The parent message id of a previous message that you will add a new revision to, by setting parent_id to the origin message being "edited"'}},required:["content"]}},(async({content:e,parent_id:s})=>{const r=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage(e,s,r)})),p({name:"update_codicent_message",description:"Posts an edited message, which replaces the old message.",parameters:{type:"object",properties:{content:{type:"string",description:'The updated codicent message text, e.g. "remember to call Johan", usually including the tags from the original message.'},parent_id:{type:"string",description:"The message id of a previous/old message to be updated."}},required:["content","parent_id"]}},(async({content:e,parent_id:s})=>{const r=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage(e,s,r)})),p({name:"get_messages_list",description:"Gets messages tagged with given tag name, optionally limited by num_messages. The messages are listed in chronological order, latest first.",parameters:{type:"object",properties:{num_messages:{type:"integer",description:"The number of matching messages to return"},search_text:{type:"string",description:"Search text to filter messages by"},tag_name:{type:"string",description:"Name of tag to find messages by"},after_timestamp:{type:"string",description:"Only return messages after this timestamp"},before_timestamp:{type:"string",description:"Only return messages before this timestamp"}},required:["num_messages"]}},(async({num_messages:e,search_text:t,tag_name:s,after_timestamp:r,before_timestamp:n})=>{let i=await a.getMessages(s?[s]:[],void 0,e);t&&(i=i.filter((e=>e.content.includes(t)))),r&&(i=i.filter((e=>e.createdAt>r))),n&&(i=i.filter((e=>e.createdAt<n)));const o=i.slice(0,e);return console.log("get_messages_list result:",o),o})),p({name:"chat_with_codicent_ai",description:"Sends a message to Codicent AI and gets a response message back. Remember that you can always ask codicent via this tool to find out more about what you can achieve from chatting with the codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"Text of message to the codicent"}},required:["message"]}},(async({message:e})=>await a.chat(e))),p({name:"create_todo",description:"Saves a todo message in Codicent.",parameters:{type:"object",properties:{text:{type:"string",description:"Text of todo"}},required:["text"]}},(async({text:e})=>{const s=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage("#todo "+e,void 0,s)})),p({name:"place_order",description:"Places and order with the Codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"The text message of the order as spoken by the user."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#do Jag kommer ge dig en text (nedan) på vad jag ska beställa. Använd bara den text du får av mig, inga tools.\n Du ska skapa en tabell med detaljerad information inklusive lagerstatus \n för varje artikel i beställningen. Svara först när du har all info på plats. Ställ inga frågor på vägen utan använd det du får av mig.\n \n ----\n EXEMPEL PÅ SVAR:\n ----\n Här är en tabell med detaljerad information inklusive lagerstatus för varje artikel i beställningen:\n \n Artikel\tHyresobjekt\tNamn\tLagertyp\tGrupp\tNamn.1\tSRA/SBEF\tLeverantör\tLevnr\tDepå\tLager\tReserverat\tUthyrt nu\tTillgängligt nu\tMärke/typ\tTillverkarnr\tTillv.år\tInköpsdato\tInköpspris\tVikt\n Elverk\t12345\tElverk 1\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör A\t001\tDepå 1\t5\t0\t1\t4\tHonda EU22i\t123456\t2020\t2020-01-01\t10000 SEK\t21 kg\n Elverk\t12345\tElverk 2\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör B\t002\tDepå 2\t3\t0\t1\t2\tYamaha EF2000iS\t654321\t2019\t2019-05-01\t9000 SEK\t20 kg\n Observera att denna tabell är baserad på den information som finns tillgänglig i lagerstatusen. Om du behöver ytterligare detaljer eller om något specifikt saknas, vänligen meddela mig.\n ----\n \n --- här följer texten på vad jag vill beställa ---\n \n \n ${e}`))),p({name:"remember_info",description:"Saves info in your internal memory for future use. Use this tool to save insights on how to perform our conversations and tasks related to them.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve our future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),p({name:"add_codicent_memory",description:"Saves info in your internal memory for future use. Use this to save insights on how to perform conversations and tasks.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),p({name:"add_to_codicent_memory",description:"Saves info in your internal memory for future use. Use this to save insights on how to perform conversations and tasks.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t.getConfigValue("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),p({name:"save_to_crm",description:"Saves CRM related info to your memory (logbook).",parameters:{type:"object",properties:{message:{type:"string",description:"CRM log entry description/contents."},company:{type:"string",description:"Company name for CRM log entry."},contact:{type:"string",description:"Contact name for CRM log entry."}},required:["message","company"]}},(async({message:e,company:s,contact:r})=>{const n=a.codicent||t.getConfigValue("APP_NAME")||"";return n||console.warn("[save_to_crm] No codicent target — message will be posted without @mention"),await a.sendMessage(`#crm\n\nFöretag: ${s}\n\nInfo: ${e}${r?"\n\nKontakt: "+r:""}`,void 0,n)})),p({name:"set_page_html",description:"Sets the innerHtml of the div covering the full app page. You can display any html/javascript/style/mermaid content here for the user to see ;-). This view is usually referred to as the canvas, or tavlan in Swedish.",parameters:{type:"object",properties:{html:{type:"string",description:"The inner html to set the page to."}},required:["html"]}},(async({html:e})=>(r(e),i.play(),{ok:!0}))),p({name:"hide_page_html",description:"Hides the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>(r(void 0),{ok:!0}))),p({name:"get_page_html",description:"Gets the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>({html:o}))),p({name:"get_tasks",description:"Gets the list of possible tasks that can be performed. This lists task names and their id:s. Use get_task to get the task instructions given a task id.",parameters:{type:"object",properties:{},required:[]}},(async e=>(await g()).map((e=>({id:e.id,name:e.title}))))),p({name:"get_task",description:"Gets the task instructions for a given task id. Use get_tasks to get a list of possible tasks (with their id:s).",parameters:{type:"object",properties:{task_id:{type:"string",description:"The task id to get instructions for."}},required:["task_id"]}},(async({task_id:e})=>{const t=(await g()).find((t=>t.id===e));return t?t.content:"Task not found"})),p({name:"get_chat_conversation",description:"Gets a chat conversation (message thread) by message ID. Returns all messages in the conversation thread in chronological order.",parameters:{type:"object",properties:{message_id:{type:"string",description:"The ID of any message in the conversation to retrieve the full thread."}},required:["message_id"]}},(async({message_id:e})=>{try{return{ok:!0,messages:await a.getChatHistory(e)}}catch(e){return{ok:!1,error:e.message||"Failed to get chat conversation"}}})),p({name:"get_gps_location",description:"Gets the current GPS location of the device. Returns latitude and longitude coordinates. Use this when the user asks about nearby places, directions, or anything location-dependent.",parameters:{type:"object",properties:{},required:[]}},(async e=>{try{const e=await s.getGpsLocation();return e?{ok:!0,latitude:e.coords.latitude,longitude:e.coords.longitude,accuracy:e.coords.accuracy}:{ok:!1,error:"GPS not available"}}catch{return{ok:!1,error:"Failed to get GPS location"}}}));const{additionalTools:l=[],mergeStrategy:m="merge"}=d||{};return"replace"===m?l:"builtin-only"===m?e:[...e,...l]}),[a,r,n,o,i,c,d]);
@@ -1 +1 @@
1
- {"version":3,"file":"CrmPage.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPage.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAWjF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAyExF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CAqWhF,CAAC;AAEF,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"CrmPage.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPage.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAWjF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAyExF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CA8YhF,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),r=require("react"),t=require("@fluentui/react-components"),n=require("@fluentui/react-icons"),o=require("../services/codicent.js");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js"),require("../components/Spinner.js");var i=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var s=require("../components/VoiceIcon.js"),a=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js");var u=require("../config/index.js");require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js"),require("exceljs"),require("../components/FileThumbnail.js");var c=require("../components/MessageInput.js"),l=require("../components/UploadFile.js");require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/Content.js"),require("../components/AiInput.js"),require("../components/SearchBox.js"),require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js");var d=require("../components/Page.js");require("../components/QrCodeDialog.js"),require("../components/QrScanner.js");var m=require("../hooks/useLocalization.js");require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js"),require("../components/ListView.js"),require("../components/RecordModal.js"),require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js"),require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./ListPage.js"),require("./CrmPagePersistent.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var p=require("react-router-dom");require("./Purchase.js"),require("./QrScan.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var j=require("../hooks/useToaster.js"),g=require("../node_modules/lodash/lodash.js");const q=t.makeStyles({main:{flexGrow:1,...t.shorthands.padding("10px"),overflowY:"auto"},container:{maxWidth:"640px",margin:"0 auto",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",height:"100%",position:"relative"},root:{height:"100%"},item:{height:"auto",width:"auto",flexShrink:1,marginBottom:"1rem"},chat:{flexGrow:1,boxSizing:"border-box",overflow:"hidden",border:"none"},attachmentBar:{display:"none"},iconButton:{width:"24px",height:"24px"},imageCard:{alignContent:"center",borderRadius:t.tokens.borderRadiusLarge,height:"240px",textAlign:"left",position:"relative"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},backButton:{marginBottom:"10px"}}),h=({state:h,voice:f})=>{const v=q(),{t:x}=m.default(),S=p.useNavigate(),[k,C]=r.useState(!1),b=r.useRef(null),A=r.useRef(null),w=r.useRef(null),[y]=r.useState(null),[P,B]=r.useState([]),{service:I}=h,[U,M]=p.useSearchParams(),[_,E]=r.useState(""),[F,N]=r.useState(void 0),R=j.default(),[T,D]=r.useState(void 0),[L,V]=r.useState("info"),O=r.useCallback((e=>{for(const r of e)I.getFileInfo(r).then((e=>{B((r=>[...r,e]))}))}),[I,B]),$=r.useCallback((e=>{const r=new FormData;r.append("file",e),I.uploadFile(e.name,r).then((e=>{O([e])})).catch((()=>{R.notify(x("Error"),x("Error uploading pasted image"),"")}))}),[I,O,R,x]),H=e=>{C(e)};r.useEffect((()=>{const e=U.get("text"),r=U.get("url");e&&(E(e),U.delete("text")),r&&(U.delete("url"),E(((e||"")+" "+r).trim())),M(U,{replace:!0})}),[U,M,E,I,O]);const z=r.useCallback((async e=>{if(!e||e.length<10)return;D(x("Analyserar...")),V("info");const r=await I.chat((e=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${e}`)(e));if(r){const e=new a.default(r.content);try{const r=JSON.parse(e.content);console.log("json",r);let t=0;for(const e in r)"true"===r[e]&&t++;D(x(4===t?"Allt är med":r.shortSummary||"Något saknas")),V(4===t?"success":"warning")}catch{D("Error: "+e.content),V("error")}}}),[I]),G=r.useMemo((()=>g.lodashExports.debounce(z,2e3)),[z]);return r.useEffect((()=>()=>{G.cancel()}),[G]),e.jsx(d.Page,{hideHeader:!0,children:e.jsxs("div",{className:v.container,style:{backgroundImage:`url(${u.getConfigValue("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[f&&u.getConfigValue("SHOW_VOICE_BUTTON")&&e.jsx("div",{className:v.voiceIcon,children:e.jsx(s.default,{voice:f})}),e.jsx("div",{className:v.backButton,children:e.jsx(t.Button,{appearance:"subtle",icon:e.jsx(n.ArrowLeft24Regular,{}),onClick:()=>S(-1),children:x("Tillbaka")})}),e.jsx(i.default,{title:u.getConfigValue("APP_SAVE_TITLE")?x(u.getConfigValue("APP_SAVE_TITLE")):x("Spara")}),e.jsx(c.default,{defaultText:_,onSend:e=>{e=`${e=`#crm\n${e.replace("#log ","")}\nSALESPERSON: ${h.context.name||h.context.user?.email||h.context.nickname||"-"}`}${y?" "+y:""}${P.length>0?"\n":""}${P.map((e=>`#file:${e.id}`)).join(" ")}`,I.sendMessage(e).then((()=>{R.notify(x("Meddelande"),x("Meddelandet är sparat."),"")}))},files:P,onFilesChange:B,isUploading:k,onUploadImage:()=>{A.current?.triggerUpload()},onUploadCamera:()=>{w.current?.triggerUpload()},getImageUrl:o.CodicentService.getImageUrl,hasLocation:!!y,disableSend:!0,rows:9,placeholder:x("Beskriv CRM-aktivitet här..."),onChange:e=>{e!==F&&(N(e),e&&e.trim().length>0?G(e):(D(void 0),G.cancel()))},onImagePasted:$}),T&&e.jsxs("div",{className:v.review,children:[e.jsx("h3",{children:x("Analys")}),e.jsx(t.MessageBar,{intent:L,layout:"multiline",children:e.jsx(t.MessageBarBody,{children:x(T)})})]}),e.jsx(l.default,{ref:b,onFileUploaded:O,onUploading:H,multiple:!0,codicentService:I}),e.jsx(l.default,{codicentService:I,ref:A,onFileUploaded:O,onUploading:H,multiple:!0,accept:"image/*"}),e.jsx(l.default,{codicentService:I,ref:w,onFileUploaded:O,onUploading:H,multiple:!0,accept:"image/*",capture:"environment"})]})})};exports.CrmPage=h,exports.default=h;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),r=require("react"),t=require("@fluentui/react-components"),o=require("@fluentui/react-icons"),n=require("../services/codicent.js");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js"),require("../components/Spinner.js");var s=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var a=require("../components/VoiceIcon.js"),i=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js");var u=require("../config/index.js");require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js"),require("exceljs"),require("../components/FileThumbnail.js");var l=require("../components/MessageInput.js"),c=require("../components/UploadFile.js");require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/Content.js"),require("../components/AiInput.js"),require("../components/SearchBox.js"),require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js");var d=require("../components/Page.js");require("../components/QrCodeDialog.js"),require("../components/QrScanner.js");var m=require("../hooks/useLocalization.js");require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js"),require("../components/ListView.js"),require("../components/RecordModal.js"),require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js"),require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./ListPage.js"),require("./CrmPagePersistent.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var p=require("react-router-dom");require("./Purchase.js"),require("./QrScan.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var j=require("../hooks/useToaster.js"),g=require("../node_modules/lodash/lodash.js");const q=t.makeStyles({main:{flexGrow:1,...t.shorthands.padding("10px"),overflowY:"auto"},container:{maxWidth:"640px",margin:"0 auto",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",height:"100%",position:"relative"},root:{height:"100%"},item:{height:"auto",width:"auto",flexShrink:1,marginBottom:"1rem"},chat:{flexGrow:1,boxSizing:"border-box",overflow:"hidden",border:"none"},attachmentBar:{display:"none"},iconButton:{width:"24px",height:"24px"},imageCard:{alignContent:"center",borderRadius:t.tokens.borderRadiusLarge,height:"240px",textAlign:"left",position:"relative"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},backButton:{marginBottom:"10px"}}),h=({state:h,voice:f})=>{const x=q(),{t:v}=m.default(),S=p.useNavigate(),[k,C]=r.useState(!1),b=r.useRef(null),w=r.useRef(null),y=r.useRef(null),[A]=r.useState(null),[B,I]=r.useState([]),{service:P}=h,[U,M]=p.useSearchParams(),[_,E]=r.useState(""),[F,L]=r.useState(void 0),R=j.default(),[T,N]=r.useState(void 0),[D,O]=r.useState("info"),V=r.useCallback((e=>{for(const r of e)P.getFileInfo(r).then((e=>{I((r=>[...r,e]))}))}),[P,I]),$=r.useCallback((e=>{const r=new FormData;r.append("file",e),P.uploadFile(e.name,r).then((e=>{V([e])})).catch((()=>{R.notify(v("Error"),v("Error uploading pasted image"),"")}))}),[P,V,R,v]),z=e=>{C(e)};r.useEffect((()=>{const e=U.get("text"),r=U.get("url");e&&(E(e),U.delete("text")),r&&(U.delete("url"),E(((e||"")+" "+r).trim())),M(U,{replace:!0})}),[U,M,E,P,V]);const H=r.useCallback((async e=>{if(!e||e.length<10)return;N(v("Analyserar...")),O("info");const r=await P.chat((e=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${e}`)(e));if(r){const e=new i.default(r.content);try{const r=JSON.parse(e.content);console.log("json",r);let t=0;for(const e in r)"true"===r[e]&&t++;N(v(4===t?"Allt är med":r.shortSummary||"Något saknas")),O(4===t?"success":"warning")}catch{N("Error: "+e.content),O("error")}}}),[P]),G=r.useMemo((()=>g.lodashExports.debounce(H,2e3)),[H]);return r.useEffect((()=>()=>{G.cancel()}),[G]),e.jsxs(d.Page,{hideHeader:!0,children:[e.jsxs("div",{className:x.container,style:{backgroundImage:`url(${u.getConfigValue("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[f&&u.getConfigValue("SHOW_VOICE_BUTTON")&&e.jsx("div",{className:x.voiceIcon,children:e.jsx(a.default,{voice:f})}),e.jsx("div",{className:x.backButton,children:e.jsx(t.Button,{appearance:"subtle",icon:e.jsx(o.ArrowLeft24Regular,{}),onClick:()=>S(-1),children:v("Tillbaka")})}),e.jsx(s.default,{title:u.getConfigValue("APP_SAVE_TITLE")?v(u.getConfigValue("APP_SAVE_TITLE")):v("Spara")}),e.jsx(l.default,{defaultText:_,onSend:e=>{e=`${e=`#crm\n${e.replace("#log ","")}\nSALESPERSON: ${h.context.name||h.context.user?.email||h.context.nickname||"-"}`}${A?" "+A:""}${B.length>0?"\n":""}${B.map((e=>`#file:${e.id}`)).join(" ")}`,P.sendMessage(e).then((()=>{R.notify(v("Meddelande"),v("Meddelandet är sparat."),"")}))},files:B,onFilesChange:I,isUploading:k,onUploadImage:()=>{w.current?.triggerUpload()},onUploadCamera:()=>{y.current?.triggerUpload()},getImageUrl:n.CodicentService.getImageUrl,hasLocation:!!A,disableSend:!0,rows:9,placeholder:v("Beskriv CRM-aktivitet här..."),onChange:e=>{e!==F&&(L(e),e&&e.trim().length>0?G(e):(N(void 0),G.cancel()))},onImagePasted:$}),T&&e.jsxs("div",{className:x.review,children:[e.jsx("h3",{children:v("Analys")}),e.jsx(t.MessageBar,{intent:D,layout:"multiline",children:e.jsx(t.MessageBarBody,{children:v(T)})})]}),e.jsx(c.default,{ref:b,onFileUploaded:V,onUploading:z,multiple:!0,codicentService:P}),e.jsx(c.default,{codicentService:P,ref:w,onFileUploaded:V,onUploading:z,multiple:!0,accept:"image/*"}),e.jsx(c.default,{codicentService:P,ref:y,onFileUploaded:V,onUploading:z,multiple:!0,accept:"image/*",capture:"environment"})]}),f?.toolCallLog&&f.toolCallLog.length>0&&e.jsxs("div",{style:{padding:"8px 16px",background:"#1e1e2e",borderTop:"1px solid #444",maxHeight:220,overflowY:"auto"},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:4},children:[e.jsx("span",{style:{color:"#aaa",fontSize:11,fontFamily:"monospace"},children:"Voice tool calls"}),e.jsx(t.Button,{size:"small",appearance:"subtle",style:{color:"#aaa",fontSize:11},onClick:()=>f.clearToolCallLog?.(),children:"Clear"})]}),f.toolCallLog.map(((r,t)=>e.jsxs("div",{style:{fontFamily:"monospace",fontSize:11,color:r.error?"#f87171":"#86efac",marginBottom:6,whiteSpace:"pre-wrap",wordBreak:"break-all"},children:[e.jsxs("span",{style:{color:"#94a3b8"},children:[new Date(r.timestamp).toLocaleTimeString()," "]}),e.jsx("strong",{children:r.name})," → ",r.error?`ERROR: ${r.error}`:JSON.stringify(r.args,null,0).substring(0,300)]},t)))]})]})};exports.CrmPage=h,exports.default=h;
@@ -1 +1 @@
1
- {"version":3,"file":"CrmPagePersistent.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPagePersistent.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAajF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAoHxF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CA2T1F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"CrmPagePersistent.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPagePersistent.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAajF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAoHxF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CAoW1F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),r=require("react"),t=require("@fluentui/react-components"),o=require("@fluentui/react-icons"),n=require("../services/codicent.js");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js"),require("../components/Spinner.js");var s=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var i=require("../components/VoiceIcon.js"),a=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js");var l=require("../config/index.js");require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var u=require("../utils/device.js");require("exceljs"),require("../components/FileThumbnail.js");var c=require("../components/MessageInput.js"),d=require("../components/UploadFile.js");require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/Content.js"),require("../components/AiInput.js"),require("../components/SearchBox.js"),require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js");var p=require("../components/Page.js");require("../components/QrCodeDialog.js"),require("../components/QrScanner.js");var m=require("../hooks/useLocalization.js");require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js"),require("../components/ListView.js"),require("../components/RecordModal.js"),require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js"),require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./ListPage.js"),require("./CrmPage.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var j=require("react-router-dom");require("./Purchase.js"),require("./QrScan.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var g=require("../hooks/useToaster.js"),h=require("../node_modules/lodash/lodash.js");const f=t.makeStyles({main:{flexGrow:1,...t.shorthands.padding("10px"),overflowY:"auto"},container:{display:"flex",flexDirection:"row",height:"100%",...t.shorthands.gap("16px"),...t.shorthands.padding("10px")},containerMobile:{display:"flex",flexDirection:"column",height:"100%",...t.shorthands.gap("16px"),...t.shorthands.padding("10px")},inputSection:{display:"flex",flexDirection:"column",flexGrow:1,maxWidth:"640px",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",position:"relative"},inputSectionMobile:{display:"flex",flexDirection:"column",width:"100%",position:"relative"},historySection:{flexBasis:"300px",flexShrink:0,display:"flex",flexDirection:"column",...t.shorthands.gap("8px"),overflowY:"auto",...t.shorthands.padding("8px")},historySectionMobile:{display:"flex",flexDirection:"column",...t.shorthands.gap("8px"),overflowY:"auto",...t.shorthands.padding("8px"),maxHeight:"300px"},historyCard:{...t.shorthands.padding("12px"),cursor:"pointer","&:hover":{backgroundColor:t.tokens.colorNeutralBackground1Hover}},historyCardContent:{display:"flex",flexDirection:"column",...t.shorthands.gap("4px")},historyDate:{fontSize:t.tokens.fontSizeBase200,color:t.tokens.colorNeutralForeground3},historyText:{fontSize:t.tokens.fontSizeBase300,wordBreak:"break-word",whiteSpace:"pre-wrap",maxHeight:"100px",overflow:"hidden",textOverflow:"ellipsis"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},historyTitle:{fontSize:t.tokens.fontSizeBase400,fontWeight:t.tokens.fontWeightSemibold,marginBottom:"8px"},emptyState:{textAlign:"center",color:t.tokens.colorNeutralForeground3,padding:"20px"},backButton:{marginBottom:"10px"}}),x=({state:x,voice:q})=>{const S=f(),{t:v}=m.default(),k=j.useNavigate(),[y,C]=r.useState(!1),w=r.useRef(null),b=r.useRef(null),A=r.useRef(null),[M]=r.useState(null),[N,B]=r.useState([]),{service:D}=x,[T,P]=j.useSearchParams(),[I,E]=r.useState(""),[U,_]=r.useState(void 0),F=g.default(),[R,$]=r.useState(void 0),[L,O]=r.useState("info"),[V,z]=r.useState([]),[H,W]=r.useState(!1),G=u.isMobileDevice(),J=r.useCallback((e=>{for(const r of e)D.getFileInfo(r).then((e=>{B((r=>[...r,e]))}))}),[D,B]),Q=r.useCallback((e=>{const r=new FormData;r.append("file",e),D.uploadFile(e.name,r).then((e=>{J([e])})).catch((()=>{F.notify(v("Error"),v("Error uploading pasted image"),"")}))}),[D,J,F,v]),Y=e=>{C(e)},K=r.useCallback((async()=>{W(!0);try{const e=await D.getMessages(["crm"]);z(e)}catch(e){console.error("Error loading CRM messages:",e),F.notify(v("Error"),v("Failed to load CRM messages"),"","error")}finally{W(!1)}}),[D,F,v]);r.useEffect((()=>{K()}),[K]),r.useEffect((()=>{const e=T.get("text"),r=T.get("url");e&&(E(e),T.delete("text")),r&&(T.delete("url"),E(((e||"")+" "+r).trim())),P(T,{replace:!0})}),[T,P,E,D,J]);const X=e=>{e=`${e=`#crm\n${e.replace("#log ","")}\nSALESPERSON: ${x.context.name||x.context.user?.email||x.context.nickname||"-"}`}${M?" "+M:""}${N.length>0?"\n":""}${N.map((e=>`#file:${e.id}`)).join(" ")}`,D.sendMessage(e).then((()=>{F.notify(v("Meddelande"),v("Meddelandet är sparat."),""),K(),_(""),$(void 0)}))},Z=r.useCallback((async e=>{if(!e||e.length<10)return;$(v("Analyserar...")),O("info");const r=await D.chat((e=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${e}`)(e));if(r){const e=new a.default(r.content);try{const r=JSON.parse(e.content);console.log("json",r);let t=0;for(const e in r)"true"===r[e]&&t++;$(v(4===t?"Allt är med":r.shortSummary||"Något saknas")),O(4===t?"success":"warning")}catch{$("Error: "+e.content),O("error")}}}),[D,v]),ee=r.useMemo((()=>h.lodashExports.debounce(Z,2e3)),[Z]);r.useEffect((()=>()=>{ee.cancel()}),[ee]);const re=e=>{const r=new Date,t=new Date(e),o=r.getTime()-t.getTime(),n=Math.floor(o/6e4),s=Math.floor(o/36e5),i=Math.floor(o/864e5);return n<1?v("Just now"):n<60?`${n} ${v("minutes ago")}`:s<24?`${s} ${v("hours ago")}`:i<7?`${i} ${v("days ago")}`:t.toLocaleDateString()},te=e=>{const r=e.split("\n").filter((e=>!e.startsWith("#")&&!e.startsWith("SALESPERSON:")&&e.trim().length>0)).join("\n");return r.substring(0,150)+(r.length>150?"...":"")},oe=()=>e.jsxs("div",{className:G?S.historySectionMobile:S.historySection,children:[e.jsx(t.Text,{className:S.historyTitle,children:v("Tidigare sparade")}),H?e.jsx("div",{className:S.emptyState,children:e.jsx(t.Spinner,{size:"small"})}):0===V.length?e.jsx("div",{className:S.emptyState,children:e.jsx(t.Text,{children:v("Inga sparade meddelanden än")})}):V.map((r=>e.jsx(t.Card,{className:S.historyCard,onClick:()=>(e=>{const r=te(e.content);_(r)})(r),children:e.jsxs("div",{className:S.historyCardContent,children:[e.jsx(t.Text,{className:S.historyDate,children:re(r.createdAt)}),e.jsx(t.Text,{className:S.historyText,children:te(r.content)})]})},r.id)))]});return e.jsx(p.Page,{hideHeader:!0,children:e.jsxs("div",{className:G?S.containerMobile:S.container,children:[!G&&oe(),e.jsxs("div",{className:G?S.inputSectionMobile:S.inputSection,style:G?void 0:{backgroundImage:`url(${l.getConfigValue("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[q&&l.getConfigValue("SHOW_VOICE_BUTTON")&&e.jsx("div",{className:S.voiceIcon,children:e.jsx(i.default,{voice:q})}),e.jsx("div",{className:S.backButton,children:e.jsx(t.Button,{appearance:"subtle",icon:e.jsx(o.ArrowLeft24Regular,{}),onClick:()=>k(-1),children:v("Tillbaka")})}),e.jsx(s.default,{title:l.getConfigValue("APP_SAVE_TITLE")?v(l.getConfigValue("APP_SAVE_TITLE")):v("Spara")}),e.jsx(c.default,{defaultText:I,onSend:X,files:N,onFilesChange:B,isUploading:y,onUploadImage:()=>{b.current?.triggerUpload()},onUploadCamera:()=>{A.current?.triggerUpload()},getImageUrl:n.CodicentService.getImageUrl,hasLocation:!!M,disableSend:!0,rows:9,placeholder:v("Beskriv CRM-aktivitet här..."),onChange:e=>{e!==U&&(_(e),e&&e.trim().length>0?ee(e):($(void 0),ee.cancel()))},onImagePasted:Q}),R&&e.jsxs("div",{className:S.review,children:[e.jsx("h3",{children:v("Analys")}),e.jsx(t.MessageBar,{intent:L,layout:"multiline",children:e.jsx(t.MessageBarBody,{children:v(R)})})]}),e.jsx(d.default,{ref:w,onFileUploaded:J,onUploading:Y,multiple:!0,codicentService:D}),e.jsx(d.default,{codicentService:D,ref:b,onFileUploaded:J,onUploading:Y,multiple:!0,accept:"image/*"}),e.jsx(d.default,{codicentService:D,ref:A,onFileUploaded:J,onUploading:Y,multiple:!0,accept:"image/*",capture:"environment"})]}),G&&oe()]})})};exports.CrmPagePersistent=x,exports.default=x;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),r=require("react"),t=require("@fluentui/react-components"),o=require("@fluentui/react-icons"),n=require("../services/codicent.js");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js"),require("../components/Spinner.js");var s=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var i=require("../components/VoiceIcon.js"),a=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js");var l=require("../config/index.js");require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var c=require("../utils/device.js");require("exceljs"),require("../components/FileThumbnail.js");var u=require("../components/MessageInput.js"),d=require("../components/UploadFile.js");require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/Content.js"),require("../components/AiInput.js"),require("../components/SearchBox.js"),require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js");var p=require("../components/Page.js");require("../components/QrCodeDialog.js"),require("../components/QrScanner.js");var m=require("../hooks/useLocalization.js");require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js"),require("../components/ListView.js"),require("../components/RecordModal.js"),require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js"),require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./ListPage.js"),require("./CrmPage.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var g=require("react-router-dom");require("./Purchase.js"),require("./QrScan.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var j=require("../hooks/useToaster.js"),h=require("../node_modules/lodash/lodash.js");const f=t.makeStyles({main:{flexGrow:1,...t.shorthands.padding("10px"),overflowY:"auto"},container:{display:"flex",flexDirection:"row",height:"100%",...t.shorthands.gap("16px"),...t.shorthands.padding("10px")},containerMobile:{display:"flex",flexDirection:"column",height:"100%",...t.shorthands.gap("16px"),...t.shorthands.padding("10px")},inputSection:{display:"flex",flexDirection:"column",flexGrow:1,maxWidth:"640px",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",position:"relative"},inputSectionMobile:{display:"flex",flexDirection:"column",width:"100%",position:"relative"},historySection:{flexBasis:"300px",flexShrink:0,display:"flex",flexDirection:"column",...t.shorthands.gap("8px"),overflowY:"auto",...t.shorthands.padding("8px")},historySectionMobile:{display:"flex",flexDirection:"column",...t.shorthands.gap("8px"),overflowY:"auto",...t.shorthands.padding("8px"),maxHeight:"300px"},historyCard:{...t.shorthands.padding("12px"),cursor:"pointer","&:hover":{backgroundColor:t.tokens.colorNeutralBackground1Hover}},historyCardContent:{display:"flex",flexDirection:"column",...t.shorthands.gap("4px")},historyDate:{fontSize:t.tokens.fontSizeBase200,color:t.tokens.colorNeutralForeground3},historyText:{fontSize:t.tokens.fontSizeBase300,wordBreak:"break-word",whiteSpace:"pre-wrap",maxHeight:"100px",overflow:"hidden",textOverflow:"ellipsis"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},historyTitle:{fontSize:t.tokens.fontSizeBase400,fontWeight:t.tokens.fontWeightSemibold,marginBottom:"8px"},emptyState:{textAlign:"center",color:t.tokens.colorNeutralForeground3,padding:"20px"},backButton:{marginBottom:"10px"}}),x=({state:x,voice:q})=>{const S=f(),{t:v}=m.default(),y=g.useNavigate(),[k,C]=r.useState(!1),w=r.useRef(null),b=r.useRef(null),A=r.useRef(null),[M]=r.useState(null),[B,T]=r.useState([]),{service:N}=x,[D,I]=g.useSearchParams(),[P,E]=r.useState(""),[U,F]=r.useState(void 0),_=j.default(),[L,R]=r.useState(void 0),[$,z]=r.useState("info"),[O,V]=r.useState([]),[H,W]=r.useState(!1),G=c.isMobileDevice(),J=r.useCallback((e=>{for(const r of e)N.getFileInfo(r).then((e=>{T((r=>[...r,e]))}))}),[N,T]),Y=r.useCallback((e=>{const r=new FormData;r.append("file",e),N.uploadFile(e.name,r).then((e=>{J([e])})).catch((()=>{_.notify(v("Error"),v("Error uploading pasted image"),"")}))}),[N,J,_,v]),Q=e=>{C(e)},K=r.useCallback((async()=>{W(!0);try{const e=await N.getMessages(["crm"]);V(e)}catch(e){console.error("Error loading CRM messages:",e),_.notify(v("Error"),v("Failed to load CRM messages"),"","error")}finally{W(!1)}}),[N,_,v]);r.useEffect((()=>{K()}),[K]),r.useEffect((()=>{const e=D.get("text"),r=D.get("url");e&&(E(e),D.delete("text")),r&&(D.delete("url"),E(((e||"")+" "+r).trim())),I(D,{replace:!0})}),[D,I,E,N,J]);const X=e=>{e=`${e=`#crm\n${e.replace("#log ","")}\nSALESPERSON: ${x.context.name||x.context.user?.email||x.context.nickname||"-"}`}${M?" "+M:""}${B.length>0?"\n":""}${B.map((e=>`#file:${e.id}`)).join(" ")}`,N.sendMessage(e).then((()=>{_.notify(v("Meddelande"),v("Meddelandet är sparat."),""),K(),F(""),R(void 0)}))},Z=r.useCallback((async e=>{if(!e||e.length<10)return;R(v("Analyserar...")),z("info");const r=await N.chat((e=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${e}`)(e));if(r){const e=new a.default(r.content);try{const r=JSON.parse(e.content);console.log("json",r);let t=0;for(const e in r)"true"===r[e]&&t++;R(v(4===t?"Allt är med":r.shortSummary||"Något saknas")),z(4===t?"success":"warning")}catch{R("Error: "+e.content),z("error")}}}),[N,v]),ee=r.useMemo((()=>h.lodashExports.debounce(Z,2e3)),[Z]);r.useEffect((()=>()=>{ee.cancel()}),[ee]);const re=e=>{const r=new Date,t=new Date(e),o=r.getTime()-t.getTime(),n=Math.floor(o/6e4),s=Math.floor(o/36e5),i=Math.floor(o/864e5);return n<1?v("Just now"):n<60?`${n} ${v("minutes ago")}`:s<24?`${s} ${v("hours ago")}`:i<7?`${i} ${v("days ago")}`:t.toLocaleDateString()},te=e=>{const r=e.split("\n").filter((e=>!e.startsWith("#")&&!e.startsWith("SALESPERSON:")&&e.trim().length>0)).join("\n");return r.substring(0,150)+(r.length>150?"...":"")},oe=()=>e.jsxs("div",{className:G?S.historySectionMobile:S.historySection,children:[e.jsx(t.Text,{className:S.historyTitle,children:v("Tidigare sparade")}),H?e.jsx("div",{className:S.emptyState,children:e.jsx(t.Spinner,{size:"small"})}):0===O.length?e.jsx("div",{className:S.emptyState,children:e.jsx(t.Text,{children:v("Inga sparade meddelanden än")})}):O.map((r=>e.jsx(t.Card,{className:S.historyCard,onClick:()=>(e=>{const r=te(e.content);F(r)})(r),children:e.jsxs("div",{className:S.historyCardContent,children:[e.jsx(t.Text,{className:S.historyDate,children:re(r.createdAt)}),e.jsx(t.Text,{className:S.historyText,children:te(r.content)})]})},r.id)))]});return e.jsxs(p.Page,{hideHeader:!0,children:[e.jsxs("div",{className:G?S.containerMobile:S.container,children:[!G&&oe(),e.jsxs("div",{className:G?S.inputSectionMobile:S.inputSection,style:G?void 0:{backgroundImage:`url(${l.getConfigValue("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[q&&l.getConfigValue("SHOW_VOICE_BUTTON")&&e.jsx("div",{className:S.voiceIcon,children:e.jsx(i.default,{voice:q})}),e.jsx("div",{className:S.backButton,children:e.jsx(t.Button,{appearance:"subtle",icon:e.jsx(o.ArrowLeft24Regular,{}),onClick:()=>y(-1),children:v("Tillbaka")})}),e.jsx(s.default,{title:l.getConfigValue("APP_SAVE_TITLE")?v(l.getConfigValue("APP_SAVE_TITLE")):v("Spara")}),e.jsx(u.default,{defaultText:P,onSend:X,files:B,onFilesChange:T,isUploading:k,onUploadImage:()=>{b.current?.triggerUpload()},onUploadCamera:()=>{A.current?.triggerUpload()},getImageUrl:n.CodicentService.getImageUrl,hasLocation:!!M,disableSend:!0,rows:9,placeholder:v("Beskriv CRM-aktivitet här..."),onChange:e=>{e!==U&&(F(e),e&&e.trim().length>0?ee(e):(R(void 0),ee.cancel()))},onImagePasted:Y}),L&&e.jsxs("div",{className:S.review,children:[e.jsx("h3",{children:v("Analys")}),e.jsx(t.MessageBar,{intent:$,layout:"multiline",children:e.jsx(t.MessageBarBody,{children:v(L)})})]}),e.jsx(d.default,{ref:w,onFileUploaded:J,onUploading:Q,multiple:!0,codicentService:N}),e.jsx(d.default,{codicentService:N,ref:b,onFileUploaded:J,onUploading:Q,multiple:!0,accept:"image/*"}),e.jsx(d.default,{codicentService:N,ref:A,onFileUploaded:J,onUploading:Q,multiple:!0,accept:"image/*",capture:"environment"})]}),G&&oe()]}),q?.toolCallLog&&q.toolCallLog.length>0&&e.jsxs("div",{style:{padding:"8px 16px",background:"#1e1e2e",borderTop:"1px solid #444",maxHeight:220,overflowY:"auto"},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:4},children:[e.jsx(t.Text,{style:{color:"#aaa",fontSize:11,fontFamily:"monospace"},children:"Voice tool calls"}),e.jsx(t.Button,{size:"small",appearance:"subtle",style:{color:"#aaa",fontSize:11},onClick:()=>q.clearToolCallLog?.(),children:"Clear"})]}),q.toolCallLog.map(((r,t)=>e.jsxs("div",{style:{fontFamily:"monospace",fontSize:11,color:r.error?"#f87171":"#86efac",marginBottom:6,whiteSpace:"pre-wrap",wordBreak:"break-all"},children:[e.jsxs("span",{style:{color:"#94a3b8"},children:[new Date(r.timestamp).toLocaleTimeString()," "]}),e.jsx("strong",{children:r.name})," → ",r.error?`ERROR: ${r.error}`:JSON.stringify(r.args,null,0).substring(0,300)]},t)))]})]})};exports.CrmPagePersistent=x,exports.default=x;
@@ -27,6 +27,14 @@ interface RealtimeEvent {
27
27
  [key: string]: any;
28
28
  };
29
29
  }
30
+ /** Single entry in the tool call debug log */
31
+ export interface ToolCallLogEntry {
32
+ timestamp: string;
33
+ name: string;
34
+ args: Record<string, unknown>;
35
+ result?: unknown;
36
+ error?: string;
37
+ }
30
38
  export interface RealtimeVoice {
31
39
  items: ItemType[];
32
40
  realtimeEvents: RealtimeEvent[];
@@ -49,6 +57,8 @@ export interface RealtimeVoice {
49
57
  setUsername: (username: string) => void;
50
58
  updateInstructions: (instructions: string) => void;
51
59
  setLanguage: (language: string) => void;
60
+ toolCallLog: ToolCallLogEntry[];
61
+ clearToolCallLog: () => void;
52
62
  }
53
63
  declare const useRealtimeVoiceAI: (codicentService: CodicentService, tools: {
54
64
  definition: ToolDefinitionType;
@@ -1 +1 @@
1
- {"version":3,"file":"useRealtimeVoiceAI.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRealtimeVoiceAI.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAK/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,sBAAsB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,kBAAkB,oBACL,eAAe,SACzB;IAAE,UAAU,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,EAAE,UACtD,MAAM,aACH,MAAM,KAChB,aAAa,GAAG,SAysBlB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"useRealtimeVoiceAI.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRealtimeVoiceAI.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAK/E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,GAAG,CAAC;KACZ,CAAC;CACH;AAED;;GAEG;AACH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC/B;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACjD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,sBAAsB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,QAAA,MAAM,kBAAkB,oBACL,eAAe,SACzB;IAAE,UAAU,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,EAAE,UACtD,MAAM,aACH,MAAM,KAChB,aAAa,GAAG,SAquBlB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- import{useRef as e,useState as t,useCallback as n,useEffect as r,useMemo as o}from"react";import{WavRenderer as a}from"../utils/wav_renderer.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import{WavStreamPlayer as s}from"../lib/wavtools/lib/wav_stream_player.js";import{WavRecorder as i}from"../lib/wavtools/lib/wav_recorder.js";import{getConfigValue as c}from"../config/index.js";const l=(l,u,d,p)=>{const m=!!c("APP_CONFIG"),f=!!c("APP_BUTTONS");m||console.warn("APP_CONFIG is not set. Voice AI will not be available."),f||console.warn("APP_BUTTONS is not set. Voice AI will not be available.");const g=c("APP_CONFIG"),y=c("APP_BUTTONS");c("API_BASE_URL").replace(/\/$/,""),c("USE_REALTIME_SESSION_ENDPOINT"),c("REALTIME_SESSION_ENDPOINT");const _=d||c("REALTIME_VOICE_MODEL")||"alloy",w=p||c("REALTIME_VOICE_PROVIDER")||"openai",h=["alloy","shimmer","echo","verse","nova","fable","onyx"],S=h.includes(_)?_:"alloy";_!==S&&console.warn(`[codicent-app-sdk] Voice "${_}" is not supported in the current SDK version. Supported voices: ${h.join(", ")}. Falling back to "${S}".`);const v=e(new i({sampleRate:24e3})),O=e(new s({sampleRate:24e3})),I=e(null),T=e(null),N=e(null),E=e(null),C=e(!1),A=e(null),R=e(null),b=e(null),P=e((new Date).toISOString()),[D,L]=t([]),[x,F]=t([]),[J,M]=t(!1),[V,$]=t(!1),[k,U]=t(!1),[j,B]=t(!1),z=e(0),G=e(0),[q,W]=t(""),[H,K]=t("en-US"),Q=e(new Map),X=e(null),[Y,Z]=t((()=>m&&f&&y&&g&&g.apps&&g.apps[y]?g.apps[y].voiceInstructions||g.REALTIME_VOICE_INSTRUCTIONS||"":g&&g.REALTIME_VOICE_INSTRUCTIONS||"")),ee=n((e=>{const t=P.current,n=new Date(t).valueOf(),r=new Date(e).valueOf()-n,o=Math.floor(r/10)%100,a=Math.floor(r/1e3)%60,s=e=>{let t=e+"";for(;t.length<2;)t="0"+t;return t};return`${s(Math.floor(r/6e4)%60)}:${s(a)}.${s(o)}`}),[]),te=n((async()=>{try{P.current=(new Date).toISOString(),M(!0),F([]),L([]),Q.current.clear();const e=await l.getRealtimeSessionToken(S,w);if(!e)throw new Error("No ephemeral key returned from session endpoint");const t=new RTCPeerConnection;I.current=t,N.current||(N.current=new Audio,N.current.autoplay=!0),t.ontrack=e=>{N.current&&e.streams[0]&&(N.current.srcObject=e.streams[0])};const n=await navigator.mediaDevices.getUserMedia({audio:!0});E.current=n;const r=n.getTracks()[0];t.addTrack(r,n);const o=t.createDataChannel("oai-events");T.current=o,o.addEventListener("message",(e=>{try{const t=JSON.parse(e.data);if("session.created"===t.type);else if("conversation.item.created"===t.type)L((e=>[...e,t.item]));else if("conversation.item.input_audio_transcription.completed"===t.type)L((e=>{const n=[...e],r=n.findIndex((e=>e.id===t.item_id));return-1!==r&&n[r].formatted&&(n[r].formatted.transcript=t.transcript),n}));else if("response.audio_transcript.delta"===t.type);else if("response.audio_transcript.done"===t.type);else if("response.output_item.added"===t.type){const e=t.item;"function_call"===e?.type&&(console.log("[Voice AI] Function call initiated:",e.name),Q.current.set(e.id,{name:e.name||"",arguments:"",call_id:e.call_id||e.id}))}else if("response.function_call_arguments.delta"===t.type){const e=t.item_id,n=t.delta,r=Q.current.get(e);r&&n&&(r.arguments+=n,Q.current.set(e,r))}else if("response.function_call_arguments.done"===t.type){const e=t.item_id,n=Q.current.get(e);if(n){console.log(`[Voice AI] Executing tool: ${n.name}`);const t=u.find((e=>e.definition.name===n.name));if(t){let e={};try{e=n.arguments?JSON.parse(n.arguments):{}}catch(t){console.error("[Voice AI] Failed to parse tool arguments:",t),e={}}Promise.resolve(t.handler(e)).then((e=>{console.log(`[Voice AI] Tool ${n.name} completed:`,e),o&&"open"===o.readyState&&(o.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify(e)}})),o.send(JSON.stringify({type:"response.create"})))})).catch((e=>{console.error(`[Voice AI] Tool ${n.name} failed:`,e),o&&"open"===o.readyState&&(o.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify({error:e.message||"Tool execution failed"})}})),o.send(JSON.stringify({type:"response.create"})))}))}else console.error(`[Voice AI] Tool not found: ${n.name}`);Q.current.delete(e)}}else"error"===t.type&&console.error("Server error:",t.error)}catch(t){console.warn("Invalid message:",e.data)}})),o.onopen=()=>{console.log("Data channel opened, configuring session");const e=X.current||Y;X.current=null,console.log("[Voice AI] Sending session.update with instructions length:",e.length,"preview:",e.substring(0,100));const t="azure"===w.toLowerCase();o.send(JSON.stringify({type:"session.update",session:{...t?{type:"realtime"}:{},instructions:e.replace("{{name}}",q).replace("{{language}}",H).replace("{{time}}",(new Date).toISOString()),...t?{}:{modalities:["text","audio"]},input_audio_transcription:{model:"whisper-1"},turn_detection:{type:"server_vad",threshold:.5,prefix_padding_ms:300,silence_duration_ms:500},voice:S,temperature:.8,max_response_output_tokens:4096,...t?{}:{input_audio_format:"pcm16",output_audio_format:"pcm16"},tools:u.map((e=>({type:"function",...e.definition})))}})),$(!0)},o.onclose=()=>{console.log("Data channel closed"),$(!1)};const a=await t.createOffer();await t.setLocalDescription(a);let s="gpt-4o-realtime-preview",i=null;try{const e=c("REALTIME_CONFIG_ENDPOINT"),t="azure"===w.toLowerCase();if(e||t){const e=await l.getRealtimeConfig(w);e&&e.model&&(s=e.model),e&&e.url&&(i=e.url)}}catch(e){console.warn("Failed to fetch realtime config, using default model:",e)}w.toLowerCase();const d=i??`https://api.openai.com/v1/realtime?model=${s}`,p={Authorization:`Bearer ${e}`,"Content-Type":"application/sdp"},m=await fetch(d,{method:"POST",body:a.sdp,headers:p});if(!m.ok)throw new Error(`Failed to get SDP answer: ${m.statusText}`);const f={type:"answer",sdp:await m.text()};await t.setRemoteDescription(f),U(!1);const g=v.current,y=O.current;await g.begin(),await y.connect()}catch(e){throw console.error("[codicent-app-sdk] Failed to establish WebRTC connection:",e),M(!1),e}}),[l,S,Y,q,H,u]),ne=n((async()=>{M(!1),$(!1),F([]),L([]),Q.current.clear(),X.current=null,T.current&&(T.current.close(),T.current=null),I.current&&(I.current.close(),I.current=null),E.current&&(E.current.getTracks().forEach((e=>e.stop())),E.current=null),N.current&&(N.current.pause(),N.current.srcObject=null);const e=v.current;await e.end();const t=O.current;await t.interrupt()}),[]),re=n((async e=>{const t=T.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"conversation.item.delete",item_id:e}))}),[]),oe=n((async()=>{B(!0);const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"input_audio_buffer.commit"}))}),[]),ae=n((async()=>{B(!1);const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"response.create"}))}),[]),se=n((async e=>{const t=T.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"session.update",session:{turn_detection:"none"===e?null:{type:"server_vad"}}})),U("none"===e)}),[]);r((()=>{if(q&&V){const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"session.update",session:{..."azure"===w.toLowerCase()?{type:"realtime"}:{},instructions:Y.replace("{{name}}",q).replace("{{language}}",H).replace("{{time}}",(new Date).toISOString())}}))}}),[Y,q,H,V]),r((()=>{let e=!0;const t=v.current,n=A.current;let r=null;const o=O.current,s=R.current;let i=null;const c=()=>{if(e){if(n&&(n.width&&n.height||(n.width=n.offsetWidth,n.height=n.offsetHeight),r=r||n.getContext("2d"),r)){r.clearRect(0,0,n.width,n.height);const e=t.recording?t.getFrequencies("voice"):{values:new Float32Array([0])},o=1-Math.max(...e.values);z.current=o,a.drawCircularBars(n,r,e.values,"#0099ff",20,0,8)}if(s&&(s.width&&s.height||(s.width=s.offsetWidth,s.height=s.offsetHeight),i=i||s.getContext("2d"),i)){i.clearRect(0,0,s.width,s.height);const e=o.analyser?o.getFrequencies("voice"):{values:new Float32Array([0])},t=1-Math.max(...e.values);G.current=t,a.drawCircularBars(s,i,e.values,"#009900",20,0,8)}window.requestAnimationFrame(c)}};return c(),()=>{e=!1}}),[]),r((()=>{!C.current&&u&&Y&&(C.current=!0)}),[u,Y]);const ie=n((e=>{Z(e);const t=T.current;if(t&&"open"===t.readyState){const n=e.replace("{{name}}",q).replace("{{language}}",H).replace("{{time}}",(new Date).toISOString());t.send(JSON.stringify({type:"session.update",session:{instructions:n}}))}else X.current=e}),[q,H]);return o((()=>{if(m&&f)return{items:D,realtimeEvents:x,isConnected:J,isSessionReady:V,canPushToTalk:k,isRecording:j,clientCanvasRef:A,serverCanvasRef:R,eventsScrollRef:b,formatTime:ee,connectConversation:te,disconnectConversation:ne,deleteConversationItem:re,startRecording:oe,stopRecording:ae,changeTurnEndType:se,getRecorderLevel:()=>z.current,getStreamLevel:()=>G.current,setUsername:W,updateInstructions:ie,setLanguage:K}}),[m,f,D,x,J,V,k,j,A,R,b])};export{l as default};
1
+ import{useRef as e,useState as t,useCallback as n,useEffect as r,useMemo as o}from"react";import{WavRenderer as a}from"../utils/wav_renderer.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import{WavStreamPlayer as s}from"../lib/wavtools/lib/wav_stream_player.js";import{WavRecorder as i}from"../lib/wavtools/lib/wav_recorder.js";import{getConfigValue as c}from"../config/index.js";const l=(l,u,d,p)=>{const m=!!c("APP_CONFIG"),g=!!c("APP_BUTTONS");m||console.warn("APP_CONFIG is not set. Voice AI will not be available."),g||console.warn("APP_BUTTONS is not set. Voice AI will not be available.");const f=c("APP_CONFIG"),y=c("APP_BUTTONS");c("API_BASE_URL").replace(/\/$/,""),c("USE_REALTIME_SESSION_ENDPOINT"),c("REALTIME_SESSION_ENDPOINT");const _=d||c("REALTIME_VOICE_MODEL")||"alloy",w=p||c("REALTIME_VOICE_PROVIDER")||"openai",S=["alloy","shimmer","echo","verse","nova","fable","onyx"],h=S.includes(_)?_:"alloy";_!==h&&console.warn(`[codicent-app-sdk] Voice "${_}" is not supported in the current SDK version. Supported voices: ${S.join(", ")}. Falling back to "${h}".`);const v=e(new i({sampleRate:24e3})),O=e(new s({sampleRate:24e3})),I=e(null),T=e(null),N=e(null),E=e(null),C=e(!1),A=e(null),R=e(null),b=e(null),D=e((new Date).toISOString()),[P,L]=t([]),[x,J]=t([]),[F,M]=t([]),[V,$]=t(!1),[k,U]=t(!1),[j,B]=t(!1),[G,q]=t(!1),z=e(0),W=e(0),[H,K]=t(""),[Q,X]=t("en-US"),Y=e(new Map),Z=e(null),[ee,te]=t((()=>m&&g&&y&&f&&f.apps&&f.apps[y]?f.apps[y].voiceInstructions||f.REALTIME_VOICE_INSTRUCTIONS||"":f&&f.REALTIME_VOICE_INSTRUCTIONS||"")),ne=n((e=>{const t=D.current,n=new Date(t).valueOf(),r=new Date(e).valueOf()-n,o=Math.floor(r/10)%100,a=Math.floor(r/1e3)%60,s=e=>{let t=e+"";for(;t.length<2;)t="0"+t;return t};return`${s(Math.floor(r/6e4)%60)}:${s(a)}.${s(o)}`}),[]),re=n((async()=>{try{D.current=(new Date).toISOString(),$(!0),J([]),L([]),Y.current.clear();const e=await l.getRealtimeSessionToken(h,w);if(!e)throw new Error("No ephemeral key returned from session endpoint");const t=new RTCPeerConnection;I.current=t,N.current||(N.current=new Audio,N.current.autoplay=!0),t.ontrack=e=>{N.current&&e.streams[0]&&(N.current.srcObject=e.streams[0])};const n=await navigator.mediaDevices.getUserMedia({audio:!0});E.current=n;const r=n.getTracks()[0];t.addTrack(r,n);const o=t.createDataChannel("oai-events");T.current=o,o.addEventListener("message",(e=>{try{const t=JSON.parse(e.data);if("session.created"===t.type);else if("conversation.item.created"===t.type)L((e=>[...e,t.item]));else if("conversation.item.input_audio_transcription.completed"===t.type)L((e=>{const n=[...e],r=n.findIndex((e=>e.id===t.item_id));return-1!==r&&n[r].formatted&&(n[r].formatted.transcript=t.transcript),n}));else if("response.audio_transcript.delta"===t.type);else if("response.audio_transcript.done"===t.type);else if("response.output_item.added"===t.type){const e=t.item;"function_call"===e?.type&&(console.log("[Voice AI] Function call initiated:",e.name),Y.current.set(e.id,{name:e.name||"",arguments:"",call_id:e.call_id||e.id}))}else if("response.function_call_arguments.delta"===t.type){const e=t.item_id,n=t.delta,r=Y.current.get(e);r&&n&&(r.arguments+=n,Y.current.set(e,r))}else if("response.function_call_arguments.done"===t.type){const e=t.item_id,n=Y.current.get(e);if(n){console.log(`[Voice AI] Executing tool: ${n.name}`);const t=u.find((e=>e.definition.name===n.name));if(t){let e={};try{e=n.arguments?JSON.parse(n.arguments):{}}catch(t){console.error("[Voice AI] Failed to parse tool arguments:",t),e={}}const r={timestamp:(new Date).toISOString(),name:n.name,args:e};Promise.resolve(t.handler(e)).then((e=>{console.log(`[Voice AI] Tool ${n.name} completed:`,e),M((t=>[...t,{...r,result:e}])),o&&"open"===o.readyState&&(o.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify(e)}})),o.send(JSON.stringify({type:"response.create"})))})).catch((e=>{console.error(`[Voice AI] Tool ${n.name} failed:`,e),M((t=>[...t,{...r,error:e.message||"Tool execution failed"}])),o&&"open"===o.readyState&&(o.send(JSON.stringify({type:"conversation.item.create",item:{type:"function_call_output",call_id:n.call_id,output:JSON.stringify({error:e.message||"Tool execution failed"})}})),o.send(JSON.stringify({type:"response.create"})))}))}else console.error(`[Voice AI] Tool not found: ${n.name}`),M((e=>[...e,{timestamp:(new Date).toISOString(),name:n.name,args:{},error:"Tool not found"}]));Y.current.delete(e)}}else"error"===t.type&&console.error("Server error:",JSON.stringify(t.error))}catch(t){console.warn("Invalid message:",e.data)}})),o.onopen=()=>{console.log("Data channel opened, configuring session");const e=Z.current||ee;Z.current=null,console.log("[Voice AI] Sending session.update with instructions length:",e.length,"preview:",e.substring(0,100)),o.send(JSON.stringify({type:"session.update",session:{instructions:e.replace("{{name}}",H).replace("{{language}}",Q).replace("{{time}}",(new Date).toISOString()),modalities:["text","audio"],input_audio_transcription:{model:"whisper-1"},turn_detection:{type:"server_vad",threshold:.5,prefix_padding_ms:300,silence_duration_ms:500},voice:h,temperature:.8,max_response_output_tokens:4096,input_audio_format:"pcm16",output_audio_format:"pcm16",tools:u.map((e=>({type:"function",...e.definition})))}})),U(!0)},o.onclose=()=>{console.log("Data channel closed"),U(!1)};const a=await t.createOffer();await t.setLocalDescription(a);let s="gpt-4o-realtime-preview",i=null;try{const e=c("REALTIME_CONFIG_ENDPOINT"),t="azure"===w.toLowerCase();if(e||t){const e=await l.getRealtimeConfig(w);e&&e.model&&(s=e.model),e&&e.url&&(i=e.url)}}catch(e){console.warn("Failed to fetch realtime config, using default model:",e)}const d=i??`https://api.openai.com/v1/realtime?model=${s}`,p={Authorization:`Bearer ${e}`,"Content-Type":"application/sdp"},m=await fetch(d,{method:"POST",body:a.sdp,headers:p});if(!m.ok)throw new Error(`Failed to get SDP answer: ${m.statusText}`);const g={type:"answer",sdp:await m.text()};await t.setRemoteDescription(g),B(!1);const f=v.current,y=O.current;await f.begin(),await y.connect()}catch(e){throw console.error("[codicent-app-sdk] Failed to establish WebRTC connection:",e),$(!1),e}}),[l,h,ee,H,Q,u]),oe=n((async()=>{$(!1),U(!1),J([]),L([]),Y.current.clear(),Z.current=null,T.current&&(T.current.close(),T.current=null),I.current&&(I.current.close(),I.current=null),E.current&&(E.current.getTracks().forEach((e=>e.stop())),E.current=null),N.current&&(N.current.pause(),N.current.srcObject=null);const e=v.current;await e.end();const t=O.current;await t.interrupt()}),[]),ae=n((async e=>{const t=T.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"conversation.item.delete",item_id:e}))}),[]),se=n((async()=>{q(!0);const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"input_audio_buffer.commit"}))}),[]),ie=n((async()=>{q(!1);const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"response.create"}))}),[]),ce=n((async e=>{const t=T.current;t&&"open"===t.readyState&&t.send(JSON.stringify({type:"session.update",session:{turn_detection:"none"===e?null:{type:"server_vad"}}})),B("none"===e)}),[]);r((()=>{if(H&&k){const e=T.current;e&&"open"===e.readyState&&e.send(JSON.stringify({type:"session.update",session:{..."azure"===w.toLowerCase()?{type:"realtime"}:{},instructions:ee.replace("{{name}}",H).replace("{{language}}",Q).replace("{{time}}",(new Date).toISOString())}}))}}),[ee,H,Q,k]),r((()=>{let e=!0;const t=v.current,n=A.current;let r=null;const o=O.current,s=R.current;let i=null;const c=()=>{if(e){if(n&&(n.width&&n.height||(n.width=n.offsetWidth,n.height=n.offsetHeight),r=r||n.getContext("2d"),r)){r.clearRect(0,0,n.width,n.height);const e=t.recording?t.getFrequencies("voice"):{values:new Float32Array([0])},o=1-Math.max(...e.values);z.current=o,a.drawCircularBars(n,r,e.values,"#0099ff",20,0,8)}if(s&&(s.width&&s.height||(s.width=s.offsetWidth,s.height=s.offsetHeight),i=i||s.getContext("2d"),i)){i.clearRect(0,0,s.width,s.height);const e=o.analyser?o.getFrequencies("voice"):{values:new Float32Array([0])},t=1-Math.max(...e.values);W.current=t,a.drawCircularBars(s,i,e.values,"#009900",20,0,8)}window.requestAnimationFrame(c)}};return c(),()=>{e=!1}}),[]),r((()=>{!C.current&&u&&ee&&(C.current=!0)}),[u,ee]);const le=n((e=>{te(e);const t=T.current;if(t&&"open"===t.readyState){const n=e.replace("{{name}}",H).replace("{{language}}",Q).replace("{{time}}",(new Date).toISOString());t.send(JSON.stringify({type:"session.update",session:{instructions:n}}))}else Z.current=e}),[H,Q]);return o((()=>{if(m&&g)return{items:P,realtimeEvents:x,isConnected:V,isSessionReady:k,canPushToTalk:j,isRecording:G,clientCanvasRef:A,serverCanvasRef:R,eventsScrollRef:b,formatTime:ne,connectConversation:re,disconnectConversation:oe,deleteConversationItem:ae,startRecording:se,stopRecording:ie,changeTurnEndType:ce,getRecorderLevel:()=>z.current,getStreamLevel:()=>W.current,setUsername:K,updateInstructions:le,setLanguage:X,toolCallLog:F,clearToolCallLog:()=>M([])}}),[m,g,P,x,F,V,k,j,G,A,R,b,re,oe,le,ne,ae,se,ie,ce,K,X])};export{l as default};
@@ -1 +1 @@
1
- {"version":3,"file":"useTools.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,QAAA,MAAM,QAAQ,QACP,eAAe,YACX,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,8BAG7B,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,qBAGhC,gBAAgB,SACf,MAAM,qBACK,MAAM,IAAI,gBAGd,WAAW,WA2oB1B,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"useTools.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,cAAc,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,QAAA,MAAM,QAAQ,QACP,eAAe,YACX,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,8BAG7B,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,qBAGhC,gBAAgB,SACf,MAAM,qBACK,MAAM,IAAI,gBAGd,WAAW,WAksB1B,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -1 +1 @@
1
- import{useMemo as e}from"react";import{getConfigValue as t}from"../config/index.js";import"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"react/jsx-runtime";import"../_virtual/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import{getGpsLocation as s}from"../utils/device.js";import"exceljs";const a=(a,r=e=>{console.log(e)},i=e=>{console.log(e)},n,o,c=()=>{console.log("stopVoiceSession")},p)=>e((()=>{const e=[],d=(t,s)=>{e.push({definition:t,handler:s})},m=async()=>{const e=t("APP_CONFIG"),s=t("APP_BUTTONS");return[...s&&e.apps[s]?e.apps[s].tasks:[],...await a.getAppTasks()]};d({name:"stop_voice_session",description:"Stops the voice session.",parameters:{type:"object",properties:{}}},(async()=>(console.log("TOOL: stop_voice_session"),r(void 0),c(),{ok:!0}))),d({name:"inject_script",description:"Injects a script into the html/canvas view.",parameters:{type:"object",properties:{script:{type:"string",description:"The script to inject"}},required:["script"]}},(async({script:e})=>(console.log("TOOL: inject_script",e),i(e),{ok:!0}))),d({name:"post_codicent_message",description:"Posts a message to Codicent, only when requested by the user.",parameters:{type:"object",properties:{content:{type:"string",description:'The codicent message text, e.g. "remember to call Linda"'},parent_id:{type:"string",description:'The parent message id of a previous message that you will add a new revision to, by setting parent_id to the origin message being "edited"'}},required:["content"]}},(async({content:e,parent_id:t})=>await a.sendMessage(e,t))),d({name:"update_codicent_message",description:"Posts an edited message, which replaces the old message.",parameters:{type:"object",properties:{content:{type:"string",description:'The updated codicent message text, e.g. "remember to call Johan", usually including the tags from the original message.'},parent_id:{type:"string",description:"The message id of a previous/old message to be updated."}},required:["content","parent_id"]}},(async({content:e,parent_id:t})=>await a.sendMessage(e,t))),d({name:"get_messages_list",description:"Gets messages tagged with given tag name, optionally limited by num_messages. The messages are listed in chronological order, latest first.",parameters:{type:"object",properties:{num_messages:{type:"integer",description:"The number of matching messages to return"},search_text:{type:"string",description:"Search text to filter messages by"},tag_name:{type:"string",description:"Name of tag to find messages by"},after_timestamp:{type:"string",description:"Only return messages after this timestamp"},before_timestamp:{type:"string",description:"Only return messages before this timestamp"}},required:["num_messages"]}},(async({num_messages:e,search_text:t,tag_name:s,after_timestamp:r,before_timestamp:i})=>{let n=await a.getMessages(s?[s]:[],void 0,e);t&&(n=n.filter((e=>e.content.includes(t)))),r&&(n=n.filter((e=>e.createdAt>r))),i&&(n=n.filter((e=>e.createdAt<i)));const o=n.slice(0,e);return console.log("get_messages_list result:",o),o})),d({name:"chat_with_codicent_ai",description:"Sends a message to Codicent AI and gets a response message back. Remember that you can always ask codicent via this tool to find out more about what you can achieve from chatting with the codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"Text of message to the codicent"}},required:["message"]}},(async({message:e})=>await a.chat(e))),d({name:"create_todo",description:"Saves a todo message in Codicent.",parameters:{type:"object",properties:{text:{type:"string",description:"Text of todo"}},required:["text"]}},(async({text:e})=>await a.sendMessage("#todo "+e))),d({name:"place_order",description:"Places and order with the Codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"The text message of the order as spoken by the user."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#do Jag kommer ge dig en text (nedan) på vad jag ska beställa. Använd bara den text du får av mig, inga tools.\n Du ska skapa en tabell med detaljerad information inklusive lagerstatus \n för varje artikel i beställningen. Svara först när du har all info på plats. Ställ inga frågor på vägen utan använd det du får av mig.\n \n ----\n EXEMPEL PÅ SVAR:\n ----\n Här är en tabell med detaljerad information inklusive lagerstatus för varje artikel i beställningen:\n \n Artikel\tHyresobjekt\tNamn\tLagertyp\tGrupp\tNamn.1\tSRA/SBEF\tLeverantör\tLevnr\tDepå\tLager\tReserverat\tUthyrt nu\tTillgängligt nu\tMärke/typ\tTillverkarnr\tTillv.år\tInköpsdato\tInköpspris\tVikt\n Elverk\t12345\tElverk 1\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör A\t001\tDepå 1\t5\t0\t1\t4\tHonda EU22i\t123456\t2020\t2020-01-01\t10000 SEK\t21 kg\n Elverk\t12345\tElverk 2\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör B\t002\tDepå 2\t3\t0\t1\t2\tYamaha EF2000iS\t654321\t2019\t2019-05-01\t9000 SEK\t20 kg\n Observera att denna tabell är baserad på den information som finns tillgänglig i lagerstatusen. Om du behöver ytterligare detaljer eller om något specifikt saknas, vänligen meddela mig.\n ----\n \n --- här följer texten på vad jag vill beställa ---\n \n \n ${e}`))),d({name:"remember_info",description:"Saves info in your internal memory for future use. Use this tool to save insights on how to perform our conversations and tasks related to them.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve our future conversations."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#index #insights\n\n${e}`))),d({name:"save_to_crm",description:"Saves CRM related info to your memory (logbook).",parameters:{type:"object",properties:{message:{type:"string",description:"CRM log entry description/contents."},company:{type:"string",description:"Company name for CRM log entry."},contact:{type:"string",description:"Contact name for CRM log entry."}},required:["message","company"]}},(async({message:e,company:s,contact:r})=>{const i=a.codicent||t("APP_NAME")||"";return i||console.warn("[save_to_crm] No codicent target — message will be posted without @mention"),await a.sendMessage(`#crm\n\nFöretag: ${s}\n\nInfo: ${e}${r?"\n\nKontakt: "+r:""}`,void 0,i)})),d({name:"set_page_html",description:"Sets the innerHtml of the div covering the full app page. You can display any html/javascript/style/mermaid content here for the user to see ;-). This view is usually referred to as the canvas, or tavlan in Swedish.",parameters:{type:"object",properties:{html:{type:"string",description:"The inner html to set the page to."}},required:["html"]}},(async({html:e})=>(r(e),n.play(),{ok:!0}))),d({name:"hide_page_html",description:"Hides the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>(r(void 0),{ok:!0}))),d({name:"get_page_html",description:"Gets the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>({html:o}))),d({name:"get_tasks",description:"Gets the list of possible tasks that can be performed. This lists task names and their id:s. Use get_task to get the task instructions given a task id.",parameters:{type:"object",properties:{},required:[]}},(async e=>(await m()).map((e=>({id:e.id,name:e.title}))))),d({name:"get_task",description:"Gets the task instructions for a given task id. Use get_tasks to get a list of possible tasks (with their id:s).",parameters:{type:"object",properties:{task_id:{type:"string",description:"The task id to get instructions for."}},required:["task_id"]}},(async({task_id:e})=>{const t=(await m()).find((t=>t.id===e));return t?t.content:"Task not found"})),d({name:"get_chat_conversation",description:"Gets a chat conversation (message thread) by message ID. Returns all messages in the conversation thread in chronological order.",parameters:{type:"object",properties:{message_id:{type:"string",description:"The ID of any message in the conversation to retrieve the full thread."}},required:["message_id"]}},(async({message_id:e})=>{try{return{ok:!0,messages:await a.getChatHistory(e)}}catch(e){return{ok:!1,error:e.message||"Failed to get chat conversation"}}})),d({name:"get_gps_location",description:"Gets the current GPS location of the device. Returns latitude and longitude coordinates. Use this when the user asks about nearby places, directions, or anything location-dependent.",parameters:{type:"object",properties:{},required:[]}},(async e=>{try{const e=await s();return e?{ok:!0,latitude:e.coords.latitude,longitude:e.coords.longitude,accuracy:e.coords.accuracy}:{ok:!1,error:"GPS not available"}}catch{return{ok:!1,error:"Failed to get GPS location"}}}));const{additionalTools:l=[],mergeStrategy:g="merge"}=p||{};return"replace"===g?l:"builtin-only"===g?e:[...e,...l]}),[a,r,i,o,n,p]);export{a as default};
1
+ import{useMemo as e}from"react";import{getConfigValue as t}from"../config/index.js";import"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"react/jsx-runtime";import"../_virtual/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import{getGpsLocation as s}from"../utils/device.js";import"exceljs";const a=(a,r=e=>{console.log(e)},n=e=>{console.log(e)},i,o,c=()=>{console.log("stopVoiceSession")},p)=>e((()=>{const e=[],d=(t,s)=>{e.push({definition:t,handler:s})},m=async()=>{const e=t("APP_CONFIG"),s=t("APP_BUTTONS");return[...s&&e.apps[s]?e.apps[s].tasks:[],...await a.getAppTasks()]};d({name:"stop_voice_session",description:"Stops the voice session.",parameters:{type:"object",properties:{}}},(async()=>(console.log("TOOL: stop_voice_session"),r(void 0),c(),{ok:!0}))),d({name:"inject_script",description:"Injects a script into the html/canvas view.",parameters:{type:"object",properties:{script:{type:"string",description:"The script to inject"}},required:["script"]}},(async({script:e})=>(console.log("TOOL: inject_script",e),n(e),{ok:!0}))),d({name:"post_codicent_message",description:"Posts a message to Codicent. Include any relevant #tags at the start of the content, e.g. '#crm\\n\\nFöretag: ...' or '#todo Buy milk'.",parameters:{type:"object",properties:{content:{type:"string",description:"The full message text to post, including any leading #tags. Example: '#crm\\n\\nFöretag: Ahlsell Gävle\\nKontakt: Johan Lind, Säljchef'"},parent_id:{type:"string",description:'The parent message id of a previous message that you will add a new revision to, by setting parent_id to the origin message being "edited"'}},required:["content"]}},(async({content:e,parent_id:s})=>{const r=a.codicent||t("APP_NAME")||"";return await a.sendMessage(e,s,r)})),d({name:"update_codicent_message",description:"Posts an edited message, which replaces the old message.",parameters:{type:"object",properties:{content:{type:"string",description:'The updated codicent message text, e.g. "remember to call Johan", usually including the tags from the original message.'},parent_id:{type:"string",description:"The message id of a previous/old message to be updated."}},required:["content","parent_id"]}},(async({content:e,parent_id:s})=>{const r=a.codicent||t("APP_NAME")||"";return await a.sendMessage(e,s,r)})),d({name:"get_messages_list",description:"Gets messages tagged with given tag name, optionally limited by num_messages. The messages are listed in chronological order, latest first.",parameters:{type:"object",properties:{num_messages:{type:"integer",description:"The number of matching messages to return"},search_text:{type:"string",description:"Search text to filter messages by"},tag_name:{type:"string",description:"Name of tag to find messages by"},after_timestamp:{type:"string",description:"Only return messages after this timestamp"},before_timestamp:{type:"string",description:"Only return messages before this timestamp"}},required:["num_messages"]}},(async({num_messages:e,search_text:t,tag_name:s,after_timestamp:r,before_timestamp:n})=>{let i=await a.getMessages(s?[s]:[],void 0,e);t&&(i=i.filter((e=>e.content.includes(t)))),r&&(i=i.filter((e=>e.createdAt>r))),n&&(i=i.filter((e=>e.createdAt<n)));const o=i.slice(0,e);return console.log("get_messages_list result:",o),o})),d({name:"chat_with_codicent_ai",description:"Sends a message to Codicent AI and gets a response message back. Remember that you can always ask codicent via this tool to find out more about what you can achieve from chatting with the codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"Text of message to the codicent"}},required:["message"]}},(async({message:e})=>await a.chat(e))),d({name:"create_todo",description:"Saves a todo message in Codicent.",parameters:{type:"object",properties:{text:{type:"string",description:"Text of todo"}},required:["text"]}},(async({text:e})=>{const s=a.codicent||t("APP_NAME")||"";return await a.sendMessage("#todo "+e,void 0,s)})),d({name:"place_order",description:"Places and order with the Codicent AI.",parameters:{type:"object",properties:{message:{type:"string",description:"The text message of the order as spoken by the user."}},required:["message"]}},(async({message:e})=>await a.sendMessage(`#do Jag kommer ge dig en text (nedan) på vad jag ska beställa. Använd bara den text du får av mig, inga tools.\n Du ska skapa en tabell med detaljerad information inklusive lagerstatus \n för varje artikel i beställningen. Svara först när du har all info på plats. Ställ inga frågor på vägen utan använd det du får av mig.\n \n ----\n EXEMPEL PÅ SVAR:\n ----\n Här är en tabell med detaljerad information inklusive lagerstatus för varje artikel i beställningen:\n \n Artikel\tHyresobjekt\tNamn\tLagertyp\tGrupp\tNamn.1\tSRA/SBEF\tLeverantör\tLevnr\tDepå\tLager\tReserverat\tUthyrt nu\tTillgängligt nu\tMärke/typ\tTillverkarnr\tTillv.år\tInköpsdato\tInköpspris\tVikt\n Elverk\t12345\tElverk 1\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör A\t001\tDepå 1\t5\t0\t1\t4\tHonda EU22i\t123456\t2020\t2020-01-01\t10000 SEK\t21 kg\n Elverk\t12345\tElverk 2\tHyresobjekt\tMaskiner\tElverk\tSRA\tLeverantör B\t002\tDepå 2\t3\t0\t1\t2\tYamaha EF2000iS\t654321\t2019\t2019-05-01\t9000 SEK\t20 kg\n Observera att denna tabell är baserad på den information som finns tillgänglig i lagerstatusen. Om du behöver ytterligare detaljer eller om något specifikt saknas, vänligen meddela mig.\n ----\n \n --- här följer texten på vad jag vill beställa ---\n \n \n ${e}`))),d({name:"remember_info",description:"Saves info in your internal memory for future use. Use this tool to save insights on how to perform our conversations and tasks related to them.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve our future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),d({name:"add_codicent_memory",description:"Saves info in your internal memory for future use. Use this to save insights on how to perform conversations and tasks.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),d({name:"add_to_codicent_memory",description:"Saves info in your internal memory for future use. Use this to save insights on how to perform conversations and tasks.",parameters:{type:"object",properties:{message:{type:"string",description:"Your insights to improve future conversations."}},required:["message"]}},(async({message:e})=>{const s=a.codicent||t("APP_NAME")||"";return await a.sendMessage(`#index #insights\n\n${e}`,void 0,s)})),d({name:"save_to_crm",description:"Saves CRM related info to your memory (logbook).",parameters:{type:"object",properties:{message:{type:"string",description:"CRM log entry description/contents."},company:{type:"string",description:"Company name for CRM log entry."},contact:{type:"string",description:"Contact name for CRM log entry."}},required:["message","company"]}},(async({message:e,company:s,contact:r})=>{const n=a.codicent||t("APP_NAME")||"";return n||console.warn("[save_to_crm] No codicent target — message will be posted without @mention"),await a.sendMessage(`#crm\n\nFöretag: ${s}\n\nInfo: ${e}${r?"\n\nKontakt: "+r:""}`,void 0,n)})),d({name:"set_page_html",description:"Sets the innerHtml of the div covering the full app page. You can display any html/javascript/style/mermaid content here for the user to see ;-). This view is usually referred to as the canvas, or tavlan in Swedish.",parameters:{type:"object",properties:{html:{type:"string",description:"The inner html to set the page to."}},required:["html"]}},(async({html:e})=>(r(e),i.play(),{ok:!0}))),d({name:"hide_page_html",description:"Hides the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>(r(void 0),{ok:!0}))),d({name:"get_page_html",description:"Gets the inner html of the page.",parameters:{type:"object",properties:{}}},(async()=>({html:o}))),d({name:"get_tasks",description:"Gets the list of possible tasks that can be performed. This lists task names and their id:s. Use get_task to get the task instructions given a task id.",parameters:{type:"object",properties:{},required:[]}},(async e=>(await m()).map((e=>({id:e.id,name:e.title}))))),d({name:"get_task",description:"Gets the task instructions for a given task id. Use get_tasks to get a list of possible tasks (with their id:s).",parameters:{type:"object",properties:{task_id:{type:"string",description:"The task id to get instructions for."}},required:["task_id"]}},(async({task_id:e})=>{const t=(await m()).find((t=>t.id===e));return t?t.content:"Task not found"})),d({name:"get_chat_conversation",description:"Gets a chat conversation (message thread) by message ID. Returns all messages in the conversation thread in chronological order.",parameters:{type:"object",properties:{message_id:{type:"string",description:"The ID of any message in the conversation to retrieve the full thread."}},required:["message_id"]}},(async({message_id:e})=>{try{return{ok:!0,messages:await a.getChatHistory(e)}}catch(e){return{ok:!1,error:e.message||"Failed to get chat conversation"}}})),d({name:"get_gps_location",description:"Gets the current GPS location of the device. Returns latitude and longitude coordinates. Use this when the user asks about nearby places, directions, or anything location-dependent.",parameters:{type:"object",properties:{},required:[]}},(async e=>{try{const e=await s();return e?{ok:!0,latitude:e.coords.latitude,longitude:e.coords.longitude,accuracy:e.coords.accuracy}:{ok:!1,error:"GPS not available"}}catch{return{ok:!1,error:"Failed to get GPS location"}}}));const{additionalTools:g=[],mergeStrategy:l="merge"}=p||{};return"replace"===l?g:"builtin-only"===l?e:[...e,...g]}),[a,r,n,o,i,c,p]);export{a as default};
@@ -1 +1 @@
1
- {"version":3,"file":"CrmPage.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPage.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAWjF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAyExF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CAqWhF,CAAC;AAEF,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"CrmPage.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPage.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAWjF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAyExF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CA8YhF,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -1 +1 @@
1
- import{jsx as o,jsxs as t}from"react/jsx-runtime";import{useState as e,useRef as n,useCallback as r,useEffect as i,useMemo as s}from"react";import{makeStyles as a,shorthands as m,tokens as p,Button as c,MessageBar as l,MessageBarBody as d}from"@fluentui/react-components";import{ArrowLeft24Regular as u}from"@fluentui/react-icons";import{CodicentService as j}from"../services/codicent.js";import"../components/Markdown.js";import"../components/Textarea.js";import"../components/Button.js";import"../components/CompoundButton.js";import"../components/Spinner.js";import g from"../components/TextHeader.js";import"../components/TypingIndicator.js";import"../components/Dialog.js";import"../components/ChatInput.js";import"../components/CombinedPlaceholderDialog.js";import"../components/ChatMessage.js";import"../components/Header.js";import h from"../components/VoiceIcon.js";import f from"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"../_virtual/index.js";import{getConfigValue as v}from"../config/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import"exceljs";import"../components/FileThumbnail.js";import k from"../components/MessageInput.js";import S from"../components/UploadFile.js";import"../components/SnapFooter.js";import"../components/Profile.js";import"../components/MessageItem.js";import"../components/Content.js";import"../components/AiInput.js";import"../components/SearchBox.js";import"../components/DataMessagePicker.js";import"../components/HtmlView.js";import"../components/Footer.js";import{Page as x}from"../components/Page.js";import"../components/QrCodeDialog.js";import"../components/QrScanner.js";import A from"../hooks/useLocalization.js";import"../components/OfflineMessage.js";import"../components/LanguageSelector.js";import"../components/ListView.js";import"../components/RecordModal.js";import"../components/BulkUploadDialog.js";import"../components/CookieBanner.js";import"../components/audit/AuditCircularProgress.js";import"../components/audit/AuditHorizontalProgress.js";import"../components/audit/AuditRoleIndicator.js";import"../components/audit/AuditUnitSwitcher.js";import"../components/audit/AuditAnswerCell.js";import"../components/audit/AuditSearchBar.js";import"../components/audit/AuditFilterChips.js";import"../components/audit/AuditFilterBar.js";import"../components/audit/AuditGroupsProgress.js";import"../components/audit/AuditSummaryDashboard.js";import"../components/audit/AuditRequirementDialog.js";import"../components/audit/AuditUnitExportDialog.js";import"../components/audit/AuditUnitImportDialog.js";import"../components/audit/AuditBulkExportDialog.js";import"../components/audit/AuditBulkUploadDialog.js";import"../components/audit/AuditSortPresets.js";import"./AppFrame.js";import"./Canvas.js";import"./Chat.js";import"./Compose.js";import"./Snap.js";import"./Search.js";import"./Menu.js";import"./Log.js";import"./Login.js";import"./Home.js";import"./ListPage.js";import"./CrmPagePersistent.js";import"./ImageView.js";import"./FormInvite.js";import"./FormAccept.js";import"./Sales.js";import{useNavigate as w,useSearchParams as b}from"react-router-dom";import"./Purchase.js";import"./QrScan.js";import"react-dom/client";import"../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js";import"../hooks/useAppStyles.js";import C from"../hooks/useToaster.js";import{l as I}from"../node_modules/lodash/lodash.js";const U=a({main:{flexGrow:1,...m.padding("10px"),overflowY:"auto"},container:{maxWidth:"640px",margin:"0 auto",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",height:"100%",position:"relative"},root:{height:"100%"},item:{height:"auto",width:"auto",flexShrink:1,marginBottom:"1rem"},chat:{flexGrow:1,boxSizing:"border-box",overflow:"hidden",border:"none"},attachmentBar:{display:"none"},iconButton:{width:"24px",height:"24px"},imageCard:{alignContent:"center",borderRadius:p.borderRadiusLarge,height:"240px",textAlign:"left",position:"relative"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},backButton:{marginBottom:"10px"}}),y=({state:a,voice:m})=>{const p=U(),{t:y}=A(),P=w(),[B,_]=e(!1),M=n(null),F=n(null),E=n(null),[T]=e(null),[N,D]=e([]),{service:L}=a,[R,O]=b(),[$,V]=e(""),[H,z]=e(void 0),G=C(),[W,Q]=e(void 0),[J,q]=e("info"),K=r((o=>{for(const t of o)L.getFileInfo(t).then((o=>{D((t=>[...t,o]))}))}),[L,D]),X=r((o=>{const t=new FormData;t.append("file",o),L.uploadFile(o.name,t).then((o=>{K([o])})).catch((()=>{G.notify(y("Error"),y("Error uploading pasted image"),"")}))}),[L,K,G,y]),Y=o=>{_(o)};i((()=>{const o=R.get("text"),t=R.get("url");o&&(V(o),R.delete("text")),t&&(R.delete("url"),V(((o||"")+" "+t).trim())),O(R,{replace:!0})}),[R,O,V,L,K]);const Z=r((async o=>{if(!o||o.length<10)return;Q(y("Analyserar...")),q("info");const t=await L.chat((o=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${o}`)(o));if(t){const o=new f(t.content);try{const t=JSON.parse(o.content);console.log("json",t);let e=0;for(const o in t)"true"===t[o]&&e++;Q(y(4===e?"Allt är med":t.shortSummary||"Något saknas")),q(4===e?"success":"warning")}catch{Q("Error: "+o.content),q("error")}}}),[L]),oo=s((()=>I.debounce(Z,2e3)),[Z]);return i((()=>()=>{oo.cancel()}),[oo]),o(x,{hideHeader:!0,children:t("div",{className:p.container,style:{backgroundImage:`url(${v("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[m&&v("SHOW_VOICE_BUTTON")&&o("div",{className:p.voiceIcon,children:o(h,{voice:m})}),o("div",{className:p.backButton,children:o(c,{appearance:"subtle",icon:o(u,{}),onClick:()=>P(-1),children:y("Tillbaka")})}),o(g,{title:v("APP_SAVE_TITLE")?y(v("APP_SAVE_TITLE")):y("Spara")}),o(k,{defaultText:$,onSend:o=>{o=`${o=`#crm\n${o.replace("#log ","")}\nSALESPERSON: ${a.context.name||a.context.user?.email||a.context.nickname||"-"}`}${T?" "+T:""}${N.length>0?"\n":""}${N.map((o=>`#file:${o.id}`)).join(" ")}`,L.sendMessage(o).then((()=>{G.notify(y("Meddelande"),y("Meddelandet är sparat."),"")}))},files:N,onFilesChange:D,isUploading:B,onUploadImage:()=>{F.current?.triggerUpload()},onUploadCamera:()=>{E.current?.triggerUpload()},getImageUrl:j.getImageUrl,hasLocation:!!T,disableSend:!0,rows:9,placeholder:y("Beskriv CRM-aktivitet här..."),onChange:o=>{o!==H&&(z(o),o&&o.trim().length>0?oo(o):(Q(void 0),oo.cancel()))},onImagePasted:X}),W&&t("div",{className:p.review,children:[o("h3",{children:y("Analys")}),o(l,{intent:J,layout:"multiline",children:o(d,{children:y(W)})})]}),o(S,{ref:M,onFileUploaded:K,onUploading:Y,multiple:!0,codicentService:L}),o(S,{codicentService:L,ref:F,onFileUploaded:K,onUploading:Y,multiple:!0,accept:"image/*"}),o(S,{codicentService:L,ref:E,onFileUploaded:K,onUploading:Y,multiple:!0,accept:"image/*",capture:"environment"})]})})};export{y as CrmPage,y as default};
1
+ import{jsxs as o,jsx as e}from"react/jsx-runtime";import{useState as t,useRef as n,useCallback as r,useEffect as i,useMemo as a}from"react";import{makeStyles as s,shorthands as m,tokens as p,Button as l,MessageBar as c,MessageBarBody as d}from"@fluentui/react-components";import{ArrowLeft24Regular as u}from"@fluentui/react-icons";import{CodicentService as g}from"../services/codicent.js";import"../components/Markdown.js";import"../components/Textarea.js";import"../components/Button.js";import"../components/CompoundButton.js";import"../components/Spinner.js";import j from"../components/TextHeader.js";import"../components/TypingIndicator.js";import"../components/Dialog.js";import"../components/ChatInput.js";import"../components/CombinedPlaceholderDialog.js";import"../components/ChatMessage.js";import"../components/Header.js";import h from"../components/VoiceIcon.js";import f from"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"../_virtual/index.js";import{getConfigValue as v}from"../config/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import"exceljs";import"../components/FileThumbnail.js";import S from"../components/MessageInput.js";import k from"../components/UploadFile.js";import"../components/SnapFooter.js";import"../components/Profile.js";import"../components/MessageItem.js";import"../components/Content.js";import"../components/AiInput.js";import"../components/SearchBox.js";import"../components/DataMessagePicker.js";import"../components/HtmlView.js";import"../components/Footer.js";import{Page as x}from"../components/Page.js";import"../components/QrCodeDialog.js";import"../components/QrScanner.js";import b from"../hooks/useLocalization.js";import"../components/OfflineMessage.js";import"../components/LanguageSelector.js";import"../components/ListView.js";import"../components/RecordModal.js";import"../components/BulkUploadDialog.js";import"../components/CookieBanner.js";import"../components/audit/AuditCircularProgress.js";import"../components/audit/AuditHorizontalProgress.js";import"../components/audit/AuditRoleIndicator.js";import"../components/audit/AuditUnitSwitcher.js";import"../components/audit/AuditAnswerCell.js";import"../components/audit/AuditSearchBar.js";import"../components/audit/AuditFilterChips.js";import"../components/audit/AuditFilterBar.js";import"../components/audit/AuditGroupsProgress.js";import"../components/audit/AuditSummaryDashboard.js";import"../components/audit/AuditRequirementDialog.js";import"../components/audit/AuditUnitExportDialog.js";import"../components/audit/AuditUnitImportDialog.js";import"../components/audit/AuditBulkExportDialog.js";import"../components/audit/AuditBulkUploadDialog.js";import"../components/audit/AuditSortPresets.js";import"./AppFrame.js";import"./Canvas.js";import"./Chat.js";import"./Compose.js";import"./Snap.js";import"./Search.js";import"./Menu.js";import"./Log.js";import"./Login.js";import"./Home.js";import"./ListPage.js";import"./CrmPagePersistent.js";import"./ImageView.js";import"./FormInvite.js";import"./FormAccept.js";import"./Sales.js";import{useNavigate as w,useSearchParams as C}from"react-router-dom";import"./Purchase.js";import"./QrScan.js";import"react-dom/client";import"../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js";import"../hooks/useAppStyles.js";import y from"../hooks/useToaster.js";import{l as A}from"../node_modules/lodash/lodash.js";const I=s({main:{flexGrow:1,...m.padding("10px"),overflowY:"auto"},container:{maxWidth:"640px",margin:"0 auto",backgroundPosition:"calc(50% + 100px) center",backgroundRepeat:"no-repeat",backgroundSize:"contain",height:"100%",position:"relative"},root:{height:"100%"},item:{height:"auto",width:"auto",flexShrink:1,marginBottom:"1rem"},chat:{flexGrow:1,boxSizing:"border-box",overflow:"hidden",border:"none"},attachmentBar:{display:"none"},iconButton:{width:"24px",height:"24px"},imageCard:{alignContent:"center",borderRadius:p.borderRadiusLarge,height:"240px",textAlign:"left",position:"relative"},review:{padding:"10px",wordBreak:"break-word",whiteSpace:"pre-line",maxWidth:"100%",overflowWrap:"break-word"},voiceIcon:{position:"absolute",top:"20px",right:"12px",zIndex:1001,cursor:"pointer"},backButton:{marginBottom:"10px"}}),B=({state:s,voice:m})=>{const p=I(),{t:B}=b(),U=w(),[P,F]=t(!1),T=n(null),_=n(null),L=n(null),[M]=t(null),[E,N]=t([]),{service:D}=s,[R,O]=C(),[$,z]=t(""),[V,H]=t(void 0),G=y(),[W,J]=t(void 0),[Q,Y]=t("info"),q=r((o=>{for(const e of o)D.getFileInfo(e).then((o=>{N((e=>[...e,o]))}))}),[D,N]),K=r((o=>{const e=new FormData;e.append("file",o),D.uploadFile(o.name,e).then((o=>{q([o])})).catch((()=>{G.notify(B("Error"),B("Error uploading pasted image"),"")}))}),[D,q,G,B]),X=o=>{F(o)};i((()=>{const o=R.get("text"),e=R.get("url");o&&(z(o),R.delete("text")),e&&(R.delete("url"),z(((o||"")+" "+e).trim())),O(R,{replace:!0})}),[R,O,z,D,q]);const Z=r((async o=>{if(!o||o.length<10)return;J(B("Analyserar...")),Y("info");const e=await D.chat((o=>`Skapa kort ja/nej-status över hur CRM-aktiviteten som följer, givet dessa punkter: \n1. Namn på kunden finns med? ja/nej \n2. Syftet med mötet finns (varför träffar du kunden, kan täckas av 3 nedan) ja/nej\n3. Vad som framkommer under mötet (viktiga punkter och diskussioner) ja/nej\n4. Nästa steg finns med (hur ni kommer att träffas igen, och om något har sålts) ja/nej. \n\nSvara endast med ja/nej för varje punkt, ett svar per rad i följande exempel i JSON-format:\n{\n "customerName": "true",\n "purpose": "false",\n "meetingNotes": "true",\n "nextSteps": "false",\n "shortSummary": "Syfte med mötet och nästa steg saknas."\n}\n\n---\nTEXT:\n\n${o}`)(o));if(e){const o=new f(e.content);try{const e=JSON.parse(o.content);console.log("json",e);let t=0;for(const o in e)"true"===e[o]&&t++;J(B(4===t?"Allt är med":e.shortSummary||"Något saknas")),Y(4===t?"success":"warning")}catch{J("Error: "+o.content),Y("error")}}}),[D]),oo=a((()=>A.debounce(Z,2e3)),[Z]);return i((()=>()=>{oo.cancel()}),[oo]),o(x,{hideHeader:!0,children:[o("div",{className:p.container,style:{backgroundImage:`url(${v("COMPOSE_BACKGROUND_IMAGE_URL")})`},children:[m&&v("SHOW_VOICE_BUTTON")&&e("div",{className:p.voiceIcon,children:e(h,{voice:m})}),e("div",{className:p.backButton,children:e(l,{appearance:"subtle",icon:e(u,{}),onClick:()=>U(-1),children:B("Tillbaka")})}),e(j,{title:v("APP_SAVE_TITLE")?B(v("APP_SAVE_TITLE")):B("Spara")}),e(S,{defaultText:$,onSend:o=>{o=`${o=`#crm\n${o.replace("#log ","")}\nSALESPERSON: ${s.context.name||s.context.user?.email||s.context.nickname||"-"}`}${M?" "+M:""}${E.length>0?"\n":""}${E.map((o=>`#file:${o.id}`)).join(" ")}`,D.sendMessage(o).then((()=>{G.notify(B("Meddelande"),B("Meddelandet är sparat."),"")}))},files:E,onFilesChange:N,isUploading:P,onUploadImage:()=>{_.current?.triggerUpload()},onUploadCamera:()=>{L.current?.triggerUpload()},getImageUrl:g.getImageUrl,hasLocation:!!M,disableSend:!0,rows:9,placeholder:B("Beskriv CRM-aktivitet här..."),onChange:o=>{o!==V&&(H(o),o&&o.trim().length>0?oo(o):(J(void 0),oo.cancel()))},onImagePasted:K}),W&&o("div",{className:p.review,children:[e("h3",{children:B("Analys")}),e(c,{intent:Q,layout:"multiline",children:e(d,{children:B(W)})})]}),e(k,{ref:T,onFileUploaded:q,onUploading:X,multiple:!0,codicentService:D}),e(k,{codicentService:D,ref:_,onFileUploaded:q,onUploading:X,multiple:!0,accept:"image/*"}),e(k,{codicentService:D,ref:L,onFileUploaded:q,onUploading:X,multiple:!0,accept:"image/*",capture:"environment"})]}),m?.toolCallLog&&m.toolCallLog.length>0&&o("div",{style:{padding:"8px 16px",background:"#1e1e2e",borderTop:"1px solid #444",maxHeight:220,overflowY:"auto"},children:[o("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:4},children:[e("span",{style:{color:"#aaa",fontSize:11,fontFamily:"monospace"},children:"Voice tool calls"}),e(l,{size:"small",appearance:"subtle",style:{color:"#aaa",fontSize:11},onClick:()=>m.clearToolCallLog?.(),children:"Clear"})]}),m.toolCallLog.map(((t,n)=>o("div",{style:{fontFamily:"monospace",fontSize:11,color:t.error?"#f87171":"#86efac",marginBottom:6,whiteSpace:"pre-wrap",wordBreak:"break-all"},children:[o("span",{style:{color:"#94a3b8"},children:[new Date(t.timestamp).toLocaleTimeString()," "]}),e("strong",{children:t.name})," → ",t.error?`ERROR: ${t.error}`:JSON.stringify(t.args,null,0).substring(0,300)]},n)))]})]})};export{B as CrmPage,B as default};
@@ -1 +1 @@
1
- {"version":3,"file":"CrmPagePersistent.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPagePersistent.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAajF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAoHxF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CA2T1F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"CrmPagePersistent.d.ts","sourceRoot":"","sources":["../../../src/pages/CrmPagePersistent.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAajF,OAAO,EAAE,gBAAgB,EAA+B,aAAa,EAAE,MAAM,UAAU,CAAC;AAoHxF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,CAoW1F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}