@contentgrowth/llm-service 0.9.3 → 0.9.5

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.
@@ -149,7 +149,7 @@ function ChatHeader({
149
149
 
150
150
  // src/ui/react/components/ChatInputArea.tsx
151
151
  import { useState as useState3, useRef as useRef3, useImperativeHandle, forwardRef, useEffect as useEffect3, useCallback as useCallback3, useLayoutEffect } from "react";
152
- import { MicrophoneIcon, StopIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
152
+ import { MicrophoneIcon, StopIcon, PaperAirplaneIcon, XMarkIcon } from "@heroicons/react/24/outline";
153
153
 
154
154
  // src/ui/react/hooks/useSpeechRecognition.ts
155
155
  import { useState, useEffect, useCallback, useRef } from "react";
@@ -161,6 +161,12 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
161
161
  const recognitionRef = useRef(null);
162
162
  const isSimulatingRef = useRef(false);
163
163
  const simulationTimeoutRef = useRef(null);
164
+ const onResultRef = useRef(onResult);
165
+ const onEndRef = useRef(onEnd);
166
+ useEffect(() => {
167
+ onResultRef.current = onResult;
168
+ onEndRef.current = onEnd;
169
+ }, [onResult, onEnd]);
164
170
  useEffect(() => {
165
171
  if (typeof window !== "undefined") {
166
172
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
@@ -179,7 +185,7 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
179
185
  return;
180
186
  }
181
187
  setIsListening(false);
182
- if (onEnd) onEnd();
188
+ if (onEndRef.current) onEndRef.current();
183
189
  };
184
190
  recognition.onresult = (event) => {
185
191
  let interimTranscript = "";
@@ -188,10 +194,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
188
194
  const result = event.results[i];
189
195
  if (result.isFinal) {
190
196
  finalTranscript += result[0].transcript;
191
- if (onResult) onResult(finalTranscript, true);
197
+ if (onResultRef.current) onResultRef.current(finalTranscript, true);
192
198
  } else {
193
199
  interimTranscript += result[0].transcript;
194
- if (onResult) onResult(interimTranscript, false);
200
+ if (onResultRef.current) onResultRef.current(interimTranscript, false);
195
201
  }
196
202
  }
197
203
  setTranscript((prev) => prev + finalTranscript);
@@ -206,10 +212,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
206
212
  simulationTimeoutRef.current = setTimeout(() => {
207
213
  const mockText = "This is a simulated voice input for testing.";
208
214
  setTranscript((prev) => prev + (prev ? " " : "") + mockText);
209
- if (onResult) onResult(mockText, true);
215
+ if (onResultRef.current) onResultRef.current(mockText, true);
210
216
  isSimulatingRef.current = false;
211
217
  setIsListening(false);
212
- if (onEnd) onEnd();
218
+ if (onEndRef.current) onEndRef.current();
213
219
  simulationTimeoutRef.current = null;
214
220
  }, 3e3);
215
221
  return;
@@ -226,9 +232,11 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
226
232
  clearTimeout(simulationTimeoutRef.current);
227
233
  simulationTimeoutRef.current = null;
228
234
  }
229
- recognitionRef.current.stop();
235
+ if (recognitionRef.current) {
236
+ recognitionRef.current.stop();
237
+ }
230
238
  };
231
- }, [onResult, onEnd, language]);
239
+ }, [language]);
232
240
  const start = useCallback(() => {
233
241
  if (recognitionRef.current && !isListening) {
234
242
  try {
@@ -248,10 +256,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
248
256
  }
249
257
  const mockText = "This is a simulated voice input for testing.";
250
258
  setTranscript((prev) => prev + (prev ? " " : "") + mockText);
251
- if (onResult) onResult(mockText, true);
259
+ if (onResultRef.current) onResultRef.current(mockText, true);
252
260
  isSimulatingRef.current = false;
253
261
  setIsListening(false);
254
- if (onEnd) onEnd();
262
+ if (onEndRef.current) onEndRef.current();
255
263
  return;
256
264
  }
257
265
  if (recognitionRef.current && isListening) {
@@ -387,6 +395,42 @@ var ChatInputArea = forwardRef(({
387
395
  const [isTranscribing, setIsTranscribing] = useState3(false);
388
396
  const [voiceError, setVoiceError] = useState3(null);
389
397
  const [isFocused, setIsFocused] = useState3(false);
398
+ const [showDebug, setShowDebug] = useState3(false);
399
+ const [logs, setLogs] = useState3([]);
400
+ const tapCountRef = useRef3({ count: 0, lastTap: 0 });
401
+ useEffect3(() => {
402
+ const originalLog = console.log;
403
+ const originalWarn = console.warn;
404
+ const originalError = console.error;
405
+ const addLog = (type, args) => {
406
+ try {
407
+ const msg = args.map((arg) => {
408
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
409
+ if (typeof arg === "object") return JSON.stringify(arg);
410
+ return String(arg);
411
+ }).join(" ");
412
+ setLogs((prev) => [`[${type}] ${msg}`, ...prev].slice(0, 50));
413
+ } catch (e) {
414
+ }
415
+ };
416
+ console.log = (...args) => {
417
+ originalLog(...args);
418
+ addLog("LOG", args);
419
+ };
420
+ console.warn = (...args) => {
421
+ originalWarn(...args);
422
+ addLog("WRN", args);
423
+ };
424
+ console.error = (...args) => {
425
+ originalError(...args);
426
+ addLog("ERR", args);
427
+ };
428
+ return () => {
429
+ console.log = originalLog;
430
+ console.warn = originalWarn;
431
+ console.error = originalError;
432
+ };
433
+ }, []);
390
434
  const textareaRef = useRef3(null);
391
435
  const measurementRef = useRef3(null);
392
436
  const pendingSelectionRef = useRef3(null);
@@ -456,6 +500,12 @@ var ChatInputArea = forwardRef(({
456
500
  (_b2 = (_a2 = voiceConfigRef.current) == null ? void 0 : _a2.onVoiceEnd) == null ? void 0 : _b2.call(_a2);
457
501
  }, []);
458
502
  const nativeSpeech = useSpeechRecognition(handleVoiceResult, handleVoiceEnd, voiceConfig == null ? void 0 : voiceConfig.language);
503
+ useEffect3(() => {
504
+ if (nativeSpeech.error) {
505
+ setVoiceError(nativeSpeech.error);
506
+ console.error("[ChatInputArea] Native Speech Error:", nativeSpeech.error);
507
+ }
508
+ }, [nativeSpeech.error]);
459
509
  const customRecorder = useAudioRecorder(async (blob) => {
460
510
  var _a2, _b2, _c2;
461
511
  setVoiceTrigger(null);
@@ -569,20 +619,40 @@ var ChatInputArea = forwardRef(({
569
619
  if (!showInputForm) {
570
620
  return null;
571
621
  }
572
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col w-full", children: [
622
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col w-full relative", children: [
623
+ showDebug && /* @__PURE__ */ jsxs3("div", { className: "absolute bottom-full left-0 right-0 mb-2 p-2 bg-black/80 text-green-400 text-xs font-mono h-48 overflow-y-auto rounded z-50 pointer-events-auto", children: [
624
+ /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center bg-gray-800 p-1 mb-1", children: [
625
+ /* @__PURE__ */ jsx5("span", { children: "Debug Logs" }),
626
+ /* @__PURE__ */ jsx5("button", { onClick: () => setShowDebug(false), className: "text-white hover:text-red-400", children: /* @__PURE__ */ jsx5(XMarkIcon, { className: "w-4 h-4" }) })
627
+ ] }),
628
+ logs.map((log, i) => /* @__PURE__ */ jsx5("div", { className: "mb-0.5 border-b border-gray-700/50 pb-0.5 break-all", children: log }, i)),
629
+ logs.length === 0 && /* @__PURE__ */ jsx5("div", { children: "No logs yet..." })
630
+ ] }),
573
631
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
574
632
  voiceConfig && /* @__PURE__ */ jsx5(
575
633
  "button",
576
634
  {
577
635
  type: "button",
578
636
  onClick: () => {
637
+ const now = Date.now();
638
+ if (now - tapCountRef.current.lastTap < 500) {
639
+ tapCountRef.current.count++;
640
+ } else {
641
+ tapCountRef.current.count = 1;
642
+ }
643
+ tapCountRef.current.lastTap = now;
644
+ if (tapCountRef.current.count >= 5) {
645
+ setShowDebug((prev) => !prev);
646
+ tapCountRef.current.count = 0;
647
+ return;
648
+ }
579
649
  if (voiceTrigger) {
580
650
  stopRecording();
581
651
  } else if (!isTranscribing) {
582
652
  startRecording("click");
583
653
  }
584
654
  },
585
- className: `mb-1 p-2 rounded-full transition-all duration-300 flex-shrink-0 border ${voiceTrigger || isTranscribing ? "text-white border-orange-400 bg-orange-500 scale-110 shadow-lg" : "text-gray-500 border-gray-300 bg-white hover:text-gray-700 hover:bg-gray-100"} ${voiceTrigger ? "animate-pulse" : ""} ${isTranscribing ? "cursor-wait" : ""}`,
655
+ className: `mb-1 p-2 rounded-full transition-all duration-300 flex-shrink-0 border ${isTranscribing ? "text-white border-indigo-500 bg-indigo-600 scale-110 shadow-lg" : voiceTrigger ? "text-white border-orange-400 bg-orange-500 scale-110 shadow-lg" : "text-gray-500 border-gray-300 bg-white hover:text-gray-700 hover:bg-gray-100"} ${voiceTrigger ? "animate-pulse" : ""} ${isTranscribing ? "cursor-wait" : ""}`,
586
656
  disabled: isTranscribing,
587
657
  title: isTranscribing ? "Transcribing..." : voiceTrigger ? "Stop Recording" : "Start Voice Input",
588
658
  children: isTranscribing ? /* @__PURE__ */ jsx5("div", { className: "animate-spin w-5 h-5 flex items-center justify-center", children: /* @__PURE__ */ jsxs3("svg", { className: "w-5 h-5 text-white", viewBox: "0 0 24 24", children: [
@@ -685,17 +755,17 @@ var ChatInputArea = forwardRef(({
685
755
  )
686
756
  ] }),
687
757
  inputHint && /* @__PURE__ */ jsx5("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
688
- /* @__PURE__ */ jsx5("div", { className: "ml-[46px] mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ jsx5("p", { className: `text-[10px] leading-tight transition-all duration-200 ${voiceError ? "text-red-500" : voiceTrigger || isTranscribing ? "text-orange-600 font-medium" : "text-gray-400"}`, children: voiceError ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1 font-semibold italic", children: [
758
+ /* @__PURE__ */ jsx5("div", { className: "ml-[46px] mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ jsx5("p", { className: `text-[10px] leading-tight transition-all duration-200 ${voiceError ? "text-red-500" : isTranscribing ? "text-indigo-600 font-bold" : voiceTrigger ? "text-orange-600 font-medium" : "text-gray-400"}`, children: voiceError ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1 font-semibold italic", children: [
689
759
  "Error: ",
690
760
  voiceError
691
- ] }) : isTranscribing ? "Transcribing, please wait..." : voiceTrigger ? "Listening... tap mic icon again to stop" : hintText || (voiceConfig ? "Type in text or tap mic icon to talk" : "Type your message...") }) })
761
+ ] }) : isTranscribing ? "Transcribing... please wait" : voiceTrigger ? "Transcribing, please wait..." : voiceTrigger ? "Listening... tap mic icon again to stop" : hintText || (voiceConfig ? "Type in text or tap mic icon to talk" : "Type your message...") }) })
692
762
  ] });
693
763
  });
694
764
  ChatInputArea.displayName = "ChatInputArea";
695
765
 
696
766
  // src/ui/react/components/TapToTalk.tsx
697
- import { useState as useState4, useCallback as useCallback4 } from "react";
698
- import { MicrophoneIcon as MicrophoneIcon2 } from "@heroicons/react/24/outline";
767
+ import React3, { useState as useState4, useCallback as useCallback4, useRef as useRef4 } from "react";
768
+ import { MicrophoneIcon as MicrophoneIcon2, XMarkIcon as XMarkIcon2 } from "@heroicons/react/24/outline";
699
769
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
700
770
  var TapToTalk = ({
701
771
  onResult,
@@ -712,6 +782,42 @@ var TapToTalk = ({
712
782
  const [isTranscribing, setIsTranscribing] = useState4(false);
713
783
  const [voiceTrigger, setVoiceTrigger] = useState4(null);
714
784
  const [errorMsg, setErrorMsg] = useState4(null);
785
+ const [showDebug, setShowDebug] = useState4(false);
786
+ const [logs, setLogs] = useState4([]);
787
+ const tapCountRef = useRef4({ count: 0, lastTap: 0 });
788
+ React3.useEffect(() => {
789
+ const originalLog = console.log;
790
+ const originalWarn = console.warn;
791
+ const originalError = console.error;
792
+ const addLog = (type, args) => {
793
+ try {
794
+ const msg = args.map((arg) => {
795
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
796
+ if (typeof arg === "object") return JSON.stringify(arg);
797
+ return String(arg);
798
+ }).join(" ");
799
+ setLogs((prev) => [`[${type}] ${msg}`, ...prev].slice(0, 50));
800
+ } catch (e) {
801
+ }
802
+ };
803
+ console.log = (...args) => {
804
+ originalLog(...args);
805
+ addLog("LOG", args);
806
+ };
807
+ console.warn = (...args) => {
808
+ originalWarn(...args);
809
+ addLog("WRN", args);
810
+ };
811
+ console.error = (...args) => {
812
+ originalError(...args);
813
+ addLog("ERR", args);
814
+ };
815
+ return () => {
816
+ console.log = originalLog;
817
+ console.warn = originalWarn;
818
+ console.error = originalError;
819
+ };
820
+ }, []);
715
821
  const handleVoiceResult = useCallback4((text, isFinal) => {
716
822
  if (isFinal) {
717
823
  onResult(text);
@@ -723,6 +829,12 @@ var TapToTalk = ({
723
829
  setVoiceTrigger(null);
724
830
  }, []);
725
831
  const nativeSpeech = useSpeechRecognition(handleVoiceResult, handleVoiceEnd, voiceConfig == null ? void 0 : voiceConfig.language);
832
+ React3.useEffect(() => {
833
+ if (nativeSpeech.error) {
834
+ setErrorMsg(nativeSpeech.error);
835
+ console.error("[TapToTalk] Native Speech Error:", nativeSpeech.error);
836
+ }
837
+ }, [nativeSpeech.error]);
726
838
  const customRecorder = useAudioRecorder(async (blob) => {
727
839
  setVoiceTrigger(null);
728
840
  setIsTranscribing(true);
@@ -751,6 +863,18 @@ var TapToTalk = ({
751
863
  const isListening = !!voiceTrigger || nativeSpeech.isListening || customRecorder.isRecording;
752
864
  const isActive = isListening || isTranscribing;
753
865
  const toggleVoice = async () => {
866
+ const now = Date.now();
867
+ if (now - tapCountRef.current.lastTap < 500) {
868
+ tapCountRef.current.count++;
869
+ } else {
870
+ tapCountRef.current.count = 1;
871
+ }
872
+ tapCountRef.current.lastTap = now;
873
+ if (tapCountRef.current.count >= 5) {
874
+ setShowDebug((prev) => !prev);
875
+ tapCountRef.current.count = 0;
876
+ return;
877
+ }
754
878
  if (isActive) {
755
879
  if (isTranscribing && !isListening) return;
756
880
  if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
@@ -788,30 +912,43 @@ var TapToTalk = ({
788
912
  label = "Listening ... Tap to stop";
789
913
  Icon = /* @__PURE__ */ jsx6(MicrophoneIcon2, { className: "h-5 w-5 animate-pulse" });
790
914
  } else if (isTranscribing) {
791
- bgColor = "bg-orange-500";
915
+ bgColor = "bg-indigo-600";
792
916
  label = "Transcribing ...";
793
917
  Icon = /* @__PURE__ */ jsxs4("svg", { className: "animate-spin h-5 w-5 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
794
918
  /* @__PURE__ */ jsx6("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
795
919
  /* @__PURE__ */ jsx6("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
796
920
  ] });
797
921
  }
798
- return /* @__PURE__ */ jsxs4(
799
- "button",
800
- {
801
- onClick: toggleVoice,
802
- disabled: disabled || isTranscribing && !isListening,
803
- className: `flex items-center justify-center gap-3 px-6 py-3 rounded-xl transition-all duration-300 w-full font-medium shadow-md active:scale-[0.98]
922
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col w-full relative", children: [
923
+ showDebug && /* @__PURE__ */ jsxs4("div", { className: "absolute bottom-full left-0 right-0 mb-2 p-2 bg-black/80 text-green-400 text-xs font-mono h-48 overflow-y-auto rounded z-50 pointer-events-auto", children: [
924
+ /* @__PURE__ */ jsxs4("div", { className: "flex justify-between items-center bg-gray-800 p-1 mb-1", children: [
925
+ /* @__PURE__ */ jsx6("span", { children: "Debug Logs" }),
926
+ /* @__PURE__ */ jsx6("button", { onClick: (e) => {
927
+ e.stopPropagation();
928
+ setShowDebug(false);
929
+ }, className: "text-white hover:text-red-400", children: /* @__PURE__ */ jsx6(XMarkIcon2, { className: "w-4 h-4" }) })
930
+ ] }),
931
+ logs.map((log, i) => /* @__PURE__ */ jsx6("div", { className: "mb-0.5 border-b border-gray-700/50 pb-0.5 break-all", children: log }, i)),
932
+ logs.length === 0 && /* @__PURE__ */ jsx6("div", { children: "No logs yet..." })
933
+ ] }),
934
+ /* @__PURE__ */ jsxs4(
935
+ "button",
936
+ {
937
+ onClick: toggleVoice,
938
+ disabled: disabled || isTranscribing && !isListening,
939
+ className: `flex items-center justify-center gap-3 px-6 py-3 rounded-xl transition-all duration-300 w-full font-medium shadow-md active:scale-[0.98]
804
940
  ${bgColor} text-white
805
941
  ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
806
942
  ${className}`,
807
- title: label,
808
- children: [
809
- /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center shrink-0", children: Icon }),
810
- /* @__PURE__ */ jsx6("span", { className: "truncate", children: label }),
811
- errorMsg && /* @__PURE__ */ jsx6("span", { className: "text-[10px] bg-white/20 px-1.5 py-0.5 rounded text-red-100 animate-in fade-in slide-in-from-right-1", children: errorMsg })
812
- ]
813
- }
814
- );
943
+ title: label,
944
+ children: [
945
+ /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center shrink-0", children: Icon }),
946
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: label }),
947
+ errorMsg && /* @__PURE__ */ jsx6("span", { className: "text-[10px] bg-white/20 px-1.5 py-0.5 rounded text-red-100 animate-in fade-in slide-in-from-right-1", children: errorMsg })
948
+ ]
949
+ }
950
+ )
951
+ ] });
815
952
  };
816
953
 
817
954
  // src/ui/react/components/ChatMessageList.tsx