@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.
@@ -203,6 +203,12 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
203
203
  const recognitionRef = (0, import_react2.useRef)(null);
204
204
  const isSimulatingRef = (0, import_react2.useRef)(false);
205
205
  const simulationTimeoutRef = (0, import_react2.useRef)(null);
206
+ const onResultRef = (0, import_react2.useRef)(onResult);
207
+ const onEndRef = (0, import_react2.useRef)(onEnd);
208
+ (0, import_react2.useEffect)(() => {
209
+ onResultRef.current = onResult;
210
+ onEndRef.current = onEnd;
211
+ }, [onResult, onEnd]);
206
212
  (0, import_react2.useEffect)(() => {
207
213
  if (typeof window !== "undefined") {
208
214
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
@@ -221,7 +227,7 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
221
227
  return;
222
228
  }
223
229
  setIsListening(false);
224
- if (onEnd) onEnd();
230
+ if (onEndRef.current) onEndRef.current();
225
231
  };
226
232
  recognition.onresult = (event) => {
227
233
  let interimTranscript = "";
@@ -230,10 +236,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
230
236
  const result = event.results[i];
231
237
  if (result.isFinal) {
232
238
  finalTranscript += result[0].transcript;
233
- if (onResult) onResult(finalTranscript, true);
239
+ if (onResultRef.current) onResultRef.current(finalTranscript, true);
234
240
  } else {
235
241
  interimTranscript += result[0].transcript;
236
- if (onResult) onResult(interimTranscript, false);
242
+ if (onResultRef.current) onResultRef.current(interimTranscript, false);
237
243
  }
238
244
  }
239
245
  setTranscript((prev) => prev + finalTranscript);
@@ -248,10 +254,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
248
254
  simulationTimeoutRef.current = setTimeout(() => {
249
255
  const mockText = "This is a simulated voice input for testing.";
250
256
  setTranscript((prev) => prev + (prev ? " " : "") + mockText);
251
- if (onResult) onResult(mockText, true);
257
+ if (onResultRef.current) onResultRef.current(mockText, true);
252
258
  isSimulatingRef.current = false;
253
259
  setIsListening(false);
254
- if (onEnd) onEnd();
260
+ if (onEndRef.current) onEndRef.current();
255
261
  simulationTimeoutRef.current = null;
256
262
  }, 3e3);
257
263
  return;
@@ -268,9 +274,11 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
268
274
  clearTimeout(simulationTimeoutRef.current);
269
275
  simulationTimeoutRef.current = null;
270
276
  }
271
- recognitionRef.current.stop();
277
+ if (recognitionRef.current) {
278
+ recognitionRef.current.stop();
279
+ }
272
280
  };
273
- }, [onResult, onEnd, language]);
281
+ }, [language]);
274
282
  const start = (0, import_react2.useCallback)(() => {
275
283
  if (recognitionRef.current && !isListening) {
276
284
  try {
@@ -290,10 +298,10 @@ var useSpeechRecognition = (onResult, onEnd, language = "en-US") => {
290
298
  }
291
299
  const mockText = "This is a simulated voice input for testing.";
292
300
  setTranscript((prev) => prev + (prev ? " " : "") + mockText);
293
- if (onResult) onResult(mockText, true);
301
+ if (onResultRef.current) onResultRef.current(mockText, true);
294
302
  isSimulatingRef.current = false;
295
303
  setIsListening(false);
296
- if (onEnd) onEnd();
304
+ if (onEndRef.current) onEndRef.current();
297
305
  return;
298
306
  }
299
307
  if (recognitionRef.current && isListening) {
@@ -429,6 +437,42 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
429
437
  const [isTranscribing, setIsTranscribing] = (0, import_react5.useState)(false);
430
438
  const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
431
439
  const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
440
+ const [showDebug, setShowDebug] = (0, import_react5.useState)(false);
441
+ const [logs, setLogs] = (0, import_react5.useState)([]);
442
+ const tapCountRef = (0, import_react5.useRef)({ count: 0, lastTap: 0 });
443
+ (0, import_react5.useEffect)(() => {
444
+ const originalLog = console.log;
445
+ const originalWarn = console.warn;
446
+ const originalError = console.error;
447
+ const addLog = (type, args) => {
448
+ try {
449
+ const msg = args.map((arg) => {
450
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
451
+ if (typeof arg === "object") return JSON.stringify(arg);
452
+ return String(arg);
453
+ }).join(" ");
454
+ setLogs((prev) => [`[${type}] ${msg}`, ...prev].slice(0, 50));
455
+ } catch (e) {
456
+ }
457
+ };
458
+ console.log = (...args) => {
459
+ originalLog(...args);
460
+ addLog("LOG", args);
461
+ };
462
+ console.warn = (...args) => {
463
+ originalWarn(...args);
464
+ addLog("WRN", args);
465
+ };
466
+ console.error = (...args) => {
467
+ originalError(...args);
468
+ addLog("ERR", args);
469
+ };
470
+ return () => {
471
+ console.log = originalLog;
472
+ console.warn = originalWarn;
473
+ console.error = originalError;
474
+ };
475
+ }, []);
432
476
  const textareaRef = (0, import_react5.useRef)(null);
433
477
  const measurementRef = (0, import_react5.useRef)(null);
434
478
  const pendingSelectionRef = (0, import_react5.useRef)(null);
@@ -498,6 +542,12 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
498
542
  (_b2 = (_a2 = voiceConfigRef.current) == null ? void 0 : _a2.onVoiceEnd) == null ? void 0 : _b2.call(_a2);
499
543
  }, []);
500
544
  const nativeSpeech = useSpeechRecognition(handleVoiceResult, handleVoiceEnd, voiceConfig == null ? void 0 : voiceConfig.language);
545
+ (0, import_react5.useEffect)(() => {
546
+ if (nativeSpeech.error) {
547
+ setVoiceError(nativeSpeech.error);
548
+ console.error("[ChatInputArea] Native Speech Error:", nativeSpeech.error);
549
+ }
550
+ }, [nativeSpeech.error]);
501
551
  const customRecorder = useAudioRecorder(async (blob) => {
502
552
  var _a2, _b2, _c2;
503
553
  setVoiceTrigger(null);
@@ -611,20 +661,40 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
611
661
  if (!showInputForm) {
612
662
  return null;
613
663
  }
614
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col w-full", children: [
664
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col w-full relative", children: [
665
+ showDebug && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("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: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-between items-center bg-gray-800 p-1 mb-1", children: [
667
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Debug Logs" }),
668
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: () => setShowDebug(false), className: "text-white hover:text-red-400", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_outline.XMarkIcon, { className: "w-4 h-4" }) })
669
+ ] }),
670
+ logs.map((log, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mb-0.5 border-b border-gray-700/50 pb-0.5 break-all", children: log }, i)),
671
+ logs.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: "No logs yet..." })
672
+ ] }),
615
673
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
616
674
  voiceConfig && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
617
675
  "button",
618
676
  {
619
677
  type: "button",
620
678
  onClick: () => {
679
+ const now = Date.now();
680
+ if (now - tapCountRef.current.lastTap < 500) {
681
+ tapCountRef.current.count++;
682
+ } else {
683
+ tapCountRef.current.count = 1;
684
+ }
685
+ tapCountRef.current.lastTap = now;
686
+ if (tapCountRef.current.count >= 5) {
687
+ setShowDebug((prev) => !prev);
688
+ tapCountRef.current.count = 0;
689
+ return;
690
+ }
621
691
  if (voiceTrigger) {
622
692
  stopRecording();
623
693
  } else if (!isTranscribing) {
624
694
  startRecording("click");
625
695
  }
626
696
  },
627
- 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" : ""}`,
697
+ 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" : ""}`,
628
698
  disabled: isTranscribing,
629
699
  title: isTranscribing ? "Transcribing..." : voiceTrigger ? "Stop Recording" : "Start Voice Input",
630
700
  children: isTranscribing ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "animate-spin w-5 h-5 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: "w-5 h-5 text-white", viewBox: "0 0 24 24", children: [
@@ -727,16 +797,16 @@ var ChatInputArea = (0, import_react5.forwardRef)(({
727
797
  )
728
798
  ] }),
729
799
  inputHint && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-red-500 bg-red-50 py-1 px-4 rounded-lg mt-1", children: inputHint }),
730
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ml-[46px] mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-1 font-semibold italic", children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ml-[46px] mb-2 mt-0.5 min-h-[0.75rem]", style: { marginLeft: "48px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-1 font-semibold italic", children: [
731
801
  "Error: ",
732
802
  voiceError
733
- ] }) : 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...") }) })
803
+ ] }) : 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...") }) })
734
804
  ] });
735
805
  });
736
806
  ChatInputArea.displayName = "ChatInputArea";
737
807
 
738
808
  // src/ui/react/components/TapToTalk.tsx
739
- var import_react6 = require("react");
809
+ var import_react6 = __toESM(require("react"), 1);
740
810
  var import_outline2 = require("@heroicons/react/24/outline");
741
811
  var import_jsx_runtime6 = require("react/jsx-runtime");
742
812
  var TapToTalk = ({
@@ -754,6 +824,42 @@ var TapToTalk = ({
754
824
  const [isTranscribing, setIsTranscribing] = (0, import_react6.useState)(false);
755
825
  const [voiceTrigger, setVoiceTrigger] = (0, import_react6.useState)(null);
756
826
  const [errorMsg, setErrorMsg] = (0, import_react6.useState)(null);
827
+ const [showDebug, setShowDebug] = (0, import_react6.useState)(false);
828
+ const [logs, setLogs] = (0, import_react6.useState)([]);
829
+ const tapCountRef = (0, import_react6.useRef)({ count: 0, lastTap: 0 });
830
+ import_react6.default.useEffect(() => {
831
+ const originalLog = console.log;
832
+ const originalWarn = console.warn;
833
+ const originalError = console.error;
834
+ const addLog = (type, args) => {
835
+ try {
836
+ const msg = args.map((arg) => {
837
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
838
+ if (typeof arg === "object") return JSON.stringify(arg);
839
+ return String(arg);
840
+ }).join(" ");
841
+ setLogs((prev) => [`[${type}] ${msg}`, ...prev].slice(0, 50));
842
+ } catch (e) {
843
+ }
844
+ };
845
+ console.log = (...args) => {
846
+ originalLog(...args);
847
+ addLog("LOG", args);
848
+ };
849
+ console.warn = (...args) => {
850
+ originalWarn(...args);
851
+ addLog("WRN", args);
852
+ };
853
+ console.error = (...args) => {
854
+ originalError(...args);
855
+ addLog("ERR", args);
856
+ };
857
+ return () => {
858
+ console.log = originalLog;
859
+ console.warn = originalWarn;
860
+ console.error = originalError;
861
+ };
862
+ }, []);
757
863
  const handleVoiceResult = (0, import_react6.useCallback)((text, isFinal) => {
758
864
  if (isFinal) {
759
865
  onResult(text);
@@ -765,6 +871,12 @@ var TapToTalk = ({
765
871
  setVoiceTrigger(null);
766
872
  }, []);
767
873
  const nativeSpeech = useSpeechRecognition(handleVoiceResult, handleVoiceEnd, voiceConfig == null ? void 0 : voiceConfig.language);
874
+ import_react6.default.useEffect(() => {
875
+ if (nativeSpeech.error) {
876
+ setErrorMsg(nativeSpeech.error);
877
+ console.error("[TapToTalk] Native Speech Error:", nativeSpeech.error);
878
+ }
879
+ }, [nativeSpeech.error]);
768
880
  const customRecorder = useAudioRecorder(async (blob) => {
769
881
  setVoiceTrigger(null);
770
882
  setIsTranscribing(true);
@@ -793,6 +905,18 @@ var TapToTalk = ({
793
905
  const isListening = !!voiceTrigger || nativeSpeech.isListening || customRecorder.isRecording;
794
906
  const isActive = isListening || isTranscribing;
795
907
  const toggleVoice = async () => {
908
+ const now = Date.now();
909
+ if (now - tapCountRef.current.lastTap < 500) {
910
+ tapCountRef.current.count++;
911
+ } else {
912
+ tapCountRef.current.count = 1;
913
+ }
914
+ tapCountRef.current.lastTap = now;
915
+ if (tapCountRef.current.count >= 5) {
916
+ setShowDebug((prev) => !prev);
917
+ tapCountRef.current.count = 0;
918
+ return;
919
+ }
796
920
  if (isActive) {
797
921
  if (isTranscribing && !isListening) return;
798
922
  if ((voiceConfig == null ? void 0 : voiceConfig.mode) === "native") {
@@ -830,30 +954,43 @@ var TapToTalk = ({
830
954
  label = "Listening ... Tap to stop";
831
955
  Icon = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_outline2.MicrophoneIcon, { className: "h-5 w-5 animate-pulse" });
832
956
  } else if (isTranscribing) {
833
- bgColor = "bg-orange-500";
957
+ bgColor = "bg-indigo-600";
834
958
  label = "Transcribing ...";
835
959
  Icon = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("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: [
836
960
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
837
961
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" })
838
962
  ] });
839
963
  }
840
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
841
- "button",
842
- {
843
- onClick: toggleVoice,
844
- disabled: disabled || isTranscribing && !isListening,
845
- 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]
964
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col w-full relative", children: [
965
+ showDebug && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("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: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex justify-between items-center bg-gray-800 p-1 mb-1", children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Debug Logs" }),
968
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: (e) => {
969
+ e.stopPropagation();
970
+ setShowDebug(false);
971
+ }, className: "text-white hover:text-red-400", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_outline2.XMarkIcon, { className: "w-4 h-4" }) })
972
+ ] }),
973
+ logs.map((log, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mb-0.5 border-b border-gray-700/50 pb-0.5 break-all", children: log }, i)),
974
+ logs.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: "No logs yet..." })
975
+ ] }),
976
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
977
+ "button",
978
+ {
979
+ onClick: toggleVoice,
980
+ disabled: disabled || isTranscribing && !isListening,
981
+ 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]
846
982
  ${bgColor} text-white
847
983
  ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
848
984
  ${className}`,
849
- title: label,
850
- children: [
851
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center justify-center shrink-0", children: Icon }),
852
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate", children: label }),
853
- errorMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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 })
854
- ]
855
- }
856
- );
985
+ title: label,
986
+ children: [
987
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center justify-center shrink-0", children: Icon }),
988
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate", children: label }),
989
+ errorMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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 })
990
+ ]
991
+ }
992
+ )
993
+ ] });
857
994
  };
858
995
 
859
996
  // src/ui/react/components/ChatMessageList.tsx