@emblemvault/hustle-react 1.4.11 → 1.5.0

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.
@@ -379,6 +379,134 @@ function useHustle() {
379
379
  }
380
380
  return context;
381
381
  }
382
+ function useSpeechRecognition({
383
+ onTranscript,
384
+ onInterimTranscript,
385
+ onError,
386
+ continuous = true,
387
+ language = "en-US"
388
+ } = {}) {
389
+ const [isListening, setIsListening] = react.useState(false);
390
+ const [isSupported, setIsSupported] = react.useState(false);
391
+ const recognitionRef = react.useRef(null);
392
+ const finalTranscriptRef = react.useRef("");
393
+ const onTranscriptRef = react.useRef(onTranscript);
394
+ const onInterimTranscriptRef = react.useRef(onInterimTranscript);
395
+ const onErrorRef = react.useRef(onError);
396
+ react.useEffect(() => {
397
+ onTranscriptRef.current = onTranscript;
398
+ onInterimTranscriptRef.current = onInterimTranscript;
399
+ onErrorRef.current = onError;
400
+ }, [onTranscript, onInterimTranscript, onError]);
401
+ react.useEffect(() => {
402
+ if (typeof window === "undefined") {
403
+ return;
404
+ }
405
+ const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
406
+ if (!SpeechRecognitionAPI) {
407
+ console.warn("Speech recognition not supported in this browser");
408
+ return;
409
+ }
410
+ setIsSupported(true);
411
+ const recognition = new SpeechRecognitionAPI();
412
+ recognition.continuous = continuous;
413
+ recognition.interimResults = true;
414
+ recognition.lang = language;
415
+ recognition.onstart = () => {
416
+ console.log("Speech recognition started");
417
+ setIsListening(true);
418
+ finalTranscriptRef.current = "";
419
+ };
420
+ recognition.onresult = (event) => {
421
+ let interimTranscript = "";
422
+ let finalTranscript = "";
423
+ const resultIndex = event.resultIndex || 0;
424
+ for (let i = resultIndex; i < event.results.length; i++) {
425
+ const transcript = event.results[i][0].transcript;
426
+ if (event.results[i].isFinal) {
427
+ finalTranscript += transcript;
428
+ } else {
429
+ interimTranscript += transcript;
430
+ }
431
+ }
432
+ if (finalTranscript) {
433
+ finalTranscriptRef.current += finalTranscript;
434
+ onTranscriptRef.current?.(finalTranscript);
435
+ }
436
+ if (interimTranscript) {
437
+ onInterimTranscriptRef.current?.(interimTranscript);
438
+ }
439
+ };
440
+ recognition.onerror = (event) => {
441
+ console.error("Speech recognition error:", event.error);
442
+ if (event.error !== "aborted" && event.error !== "no-speech") {
443
+ onErrorRef.current?.(event.error);
444
+ }
445
+ setIsListening(false);
446
+ };
447
+ recognition.onend = () => {
448
+ console.log("Speech recognition ended");
449
+ setIsListening(false);
450
+ };
451
+ recognitionRef.current = recognition;
452
+ return () => {
453
+ if (recognitionRef.current) {
454
+ try {
455
+ recognitionRef.current.abort();
456
+ } catch (e) {
457
+ }
458
+ }
459
+ };
460
+ }, [continuous, language]);
461
+ const startListening = react.useCallback(() => {
462
+ const recognition = recognitionRef.current;
463
+ if (!recognition) {
464
+ console.error("Speech recognition not available");
465
+ return;
466
+ }
467
+ if (isListening) {
468
+ return;
469
+ }
470
+ console.log("Starting speech recognition...");
471
+ try {
472
+ finalTranscriptRef.current = "";
473
+ recognition.start();
474
+ } catch (error2) {
475
+ console.error("Error starting:", error2);
476
+ onErrorRef.current?.("Failed to start speech recognition");
477
+ }
478
+ }, [isListening]);
479
+ const stopListening = react.useCallback(() => {
480
+ const recognition = recognitionRef.current;
481
+ if (!recognition) {
482
+ return;
483
+ }
484
+ if (!isListening) {
485
+ return;
486
+ }
487
+ console.log("Stopping speech recognition...");
488
+ try {
489
+ recognition.stop();
490
+ } catch (error2) {
491
+ console.warn("Error stopping:", error2);
492
+ setIsListening(false);
493
+ }
494
+ }, [isListening]);
495
+ const toggleListening = react.useCallback(() => {
496
+ if (isListening) {
497
+ stopListening();
498
+ } else {
499
+ startListening();
500
+ }
501
+ }, [isListening, startListening, stopListening]);
502
+ return {
503
+ isSupported,
504
+ isListening,
505
+ startListening,
506
+ stopListening,
507
+ toggleListening
508
+ };
509
+ }
382
510
 
383
511
  // src/plugins/predictionMarket.ts
384
512
  var DOME_API_BASE = "https://api.domeapi.io/v1";
@@ -3366,6 +3494,28 @@ var styles = {
3366
3494
  borderRadius: tokens.radius.full,
3367
3495
  animation: "hustle-spin 0.8s linear infinite"
3368
3496
  },
3497
+ // Mic button
3498
+ micBtn: {
3499
+ width: "40px",
3500
+ height: "40px",
3501
+ padding: 0,
3502
+ background: tokens.colors.bgTertiary,
3503
+ border: `1px solid ${tokens.colors.borderSecondary}`,
3504
+ borderRadius: tokens.radius.lg,
3505
+ color: tokens.colors.textSecondary,
3506
+ flexShrink: 0,
3507
+ display: "flex",
3508
+ alignItems: "center",
3509
+ justifyContent: "center",
3510
+ cursor: "pointer",
3511
+ transition: `all ${tokens.transitions.fast}`
3512
+ },
3513
+ micBtnListening: {
3514
+ background: "rgba(239, 68, 68, 0.1)",
3515
+ borderColor: "rgba(239, 68, 68, 0.3)",
3516
+ color: "#ef4444",
3517
+ animation: "hustle-pulse 1s ease-in-out infinite"
3518
+ },
3369
3519
  // Error display
3370
3520
  errorBox: {
3371
3521
  marginTop: tokens.spacing.sm,
@@ -3539,6 +3689,7 @@ function HustleChat({
3539
3689
  showDebug = false,
3540
3690
  hideHeader = false,
3541
3691
  initialSystemPrompt = "",
3692
+ enableSpeechToText = false,
3542
3693
  onMessage,
3543
3694
  onToolCall,
3544
3695
  onResponse
@@ -3575,6 +3726,30 @@ function HustleChat({
3575
3726
  const [attachments, setAttachments] = react.useState([]);
3576
3727
  const [currentToolCalls, setCurrentToolCalls] = react.useState([]);
3577
3728
  const [showSettingsPanel, setShowSettingsPanel] = react.useState(false);
3729
+ const [speechToTextEnabled, setSpeechToTextEnabled] = react.useState(() => {
3730
+ if (typeof window !== "undefined") {
3731
+ const stored = localStorage.getItem(`hustle-stt-enabled-${instanceId}`);
3732
+ return stored === "true";
3733
+ }
3734
+ return false;
3735
+ });
3736
+ react.useEffect(() => {
3737
+ if (typeof window !== "undefined") {
3738
+ localStorage.setItem(`hustle-stt-enabled-${instanceId}`, String(speechToTextEnabled));
3739
+ }
3740
+ }, [speechToTextEnabled, instanceId]);
3741
+ const {
3742
+ isSupported: isSpeechSupported,
3743
+ isListening,
3744
+ toggleListening
3745
+ } = useSpeechRecognition({
3746
+ onTranscript: react.useCallback((transcript) => {
3747
+ setInputValue((prev) => prev + (prev ? " " : "") + transcript);
3748
+ }, []),
3749
+ onError: react.useCallback((error3) => {
3750
+ console.error("Speech recognition error:", error3);
3751
+ }, [])
3752
+ });
3578
3753
  const messagesEndRef = react.useRef(null);
3579
3754
  const fileInputRef = react.useRef(null);
3580
3755
  const messagesRef = react.useRef(messages);
@@ -4043,6 +4218,27 @@ function HustleChat({
4043
4218
  }
4044
4219
  )
4045
4220
  ] }),
4221
+ isSpeechSupported && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.settingGroup, children: [
4222
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: styles.settingLabel, children: "Voice Input" }),
4223
+ /* @__PURE__ */ jsxRuntime.jsxs(
4224
+ "div",
4225
+ {
4226
+ style: styles.toggleRow,
4227
+ onClick: () => setSpeechToTextEnabled(!speechToTextEnabled),
4228
+ children: [
4229
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.toggleLabel, children: "Enable speech-to-text" }),
4230
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4231
+ ...styles.toggleSwitch,
4232
+ ...speechToTextEnabled ? styles.toggleSwitchActive : {}
4233
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4234
+ ...styles.toggleKnob,
4235
+ ...speechToTextEnabled ? styles.toggleKnobActive : {}
4236
+ } }) })
4237
+ ]
4238
+ }
4239
+ ),
4240
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.settingDescription, children: "Use your microphone to dictate messages" })
4241
+ ] }),
4046
4242
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.settingDivider }),
4047
4243
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...styles.settingGroup, marginBottom: 0 }, children: [
4048
4244
  /* @__PURE__ */ jsxRuntime.jsx("label", { style: styles.settingLabel, children: "Plugins" }),
@@ -4179,6 +4375,21 @@ function HustleChat({
4179
4375
  }
4180
4376
  ) })
4181
4377
  ] }),
4378
+ (enableSpeechToText || speechToTextEnabled) && isSpeechSupported && /* @__PURE__ */ jsxRuntime.jsx(
4379
+ "button",
4380
+ {
4381
+ type: "button",
4382
+ onClick: toggleListening,
4383
+ disabled: !canChat || isStreaming || isLoading,
4384
+ style: {
4385
+ ...styles.micBtn,
4386
+ ...isListening ? styles.micBtnListening : {},
4387
+ ...!canChat || isStreaming || isLoading ? { opacity: 0.5, cursor: "not-allowed" } : {}
4388
+ },
4389
+ title: isListening ? "Stop listening" : "Start voice input",
4390
+ children: isListening ? /* @__PURE__ */ jsxRuntime.jsx(MicOffIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(MicIcon, {})
4391
+ }
4392
+ ),
4182
4393
  /* @__PURE__ */ jsxRuntime.jsx(
4183
4394
  "button",
4184
4395
  {
@@ -4238,6 +4449,23 @@ function SettingsIcon() {
4238
4449
  function AttachIcon() {
4239
4450
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
4240
4451
  }
4452
+ function MicIcon() {
4453
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
4454
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
4455
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
4456
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
4457
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
4458
+ ] });
4459
+ }
4460
+ function MicOffIcon() {
4461
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
4462
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
4463
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
4464
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" }),
4465
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
4466
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
4467
+ ] });
4468
+ }
4241
4469
 
4242
4470
  // src/constants/index.ts
4243
4471
  var AGENT_HUSTLE_ICON = "data:image/jpeg;base64,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";