glitchgrab 1.22.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -319,7 +319,7 @@ async function sendReport(payload, baseUrl) {
319
319
  return null;
320
320
  }
321
321
  }
322
- async function enhanceText(text, token, baseUrl) {
322
+ async function enhanceText(text, token, baseUrl, screenshot, context) {
323
323
  var _a;
324
324
  try {
325
325
  const trimmed = text.trim();
@@ -331,7 +331,11 @@ async function enhanceText(text, token, baseUrl) {
331
331
  "Content-Type": "application/json",
332
332
  Authorization: `Bearer ${token}`
333
333
  },
334
- body: JSON.stringify({ text: trimmed })
334
+ body: JSON.stringify({
335
+ text: trimmed,
336
+ ...screenshot ? { screenshot } : {},
337
+ ...context ? { context } : {}
338
+ })
335
339
  });
336
340
  if (!response.ok) return text;
337
341
  const envelope = await response.json();
@@ -343,6 +347,28 @@ async function enhanceText(text, token, baseUrl) {
343
347
  return text;
344
348
  }
345
349
  }
350
+ async function transcribeAudio(blob, token, baseUrl) {
351
+ var _a;
352
+ try {
353
+ if (blob.size === 0) return "";
354
+ const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/stt`;
355
+ const form = new FormData();
356
+ form.append("file", blob, "audio.webm");
357
+ const response = await fetch(url, {
358
+ method: "POST",
359
+ headers: { Authorization: `Bearer ${token}` },
360
+ body: form
361
+ });
362
+ if (!response.ok) return "";
363
+ const envelope = await response.json();
364
+ if ((envelope == null ? void 0 : envelope.success) && typeof ((_a = envelope.data) == null ? void 0 : _a.transcript) === "string") {
365
+ return envelope.data.transcript;
366
+ }
367
+ return "";
368
+ } catch (e) {
369
+ return "";
370
+ }
371
+ }
346
372
 
347
373
  // src/dedup.ts
348
374
  var DEFAULT_WINDOW_MS = 5 * 60 * 1e3;
@@ -481,11 +507,17 @@ function useIsDark() {
481
507
  try {
482
508
  for (const el of [document.body, document.documentElement]) {
483
509
  const bg = getComputedStyle(el).backgroundColor;
484
- const match = bg.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/);
510
+ const match = bg.match(
511
+ /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/
512
+ );
485
513
  if (match) {
486
514
  const alpha = match[4] !== void 0 ? parseFloat(match[4]) : 1;
487
515
  if (alpha < 0.05) continue;
488
- const [r, g, b] = [Number(match[1]), Number(match[2]), Number(match[3])];
516
+ const [r, g, b] = [
517
+ Number(match[1]),
518
+ Number(match[2]),
519
+ Number(match[3])
520
+ ];
489
521
  return (r * 299 + g * 587 + b * 114) / 1e3 < 128;
490
522
  }
491
523
  }
@@ -511,14 +543,26 @@ function parseColorToRgb(value) {
511
543
  const hexMatch = trimmed.match(/^#([0-9a-f]{3,8})$/i);
512
544
  if (hexMatch) {
513
545
  let hex = hexMatch[1];
514
- if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
515
- return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
546
+ if (hex.length === 3)
547
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
548
+ return [
549
+ parseInt(hex.slice(0, 2), 16),
550
+ parseInt(hex.slice(2, 4), 16),
551
+ parseInt(hex.slice(4, 6), 16)
552
+ ];
516
553
  }
517
554
  const rgbMatch = trimmed.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
518
- if (rgbMatch) return [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];
519
- const hslMatch = trimmed.match(/hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
555
+ if (rgbMatch)
556
+ return [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];
557
+ const hslMatch = trimmed.match(
558
+ /hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/
559
+ );
520
560
  if (hslMatch) {
521
- const hex = hslToHex(Number(hslMatch[1]), Number(hslMatch[2]), Number(hslMatch[3]));
561
+ const hex = hslToHex(
562
+ Number(hslMatch[1]),
563
+ Number(hslMatch[2]),
564
+ Number(hslMatch[3])
565
+ );
522
566
  return parseColorToRgb(hex);
523
567
  }
524
568
  return null;
@@ -548,9 +592,16 @@ function detectHostAccent() {
548
592
  if (!raw) continue;
549
593
  const hslSpaceMatch = raw.match(/^([\d.]+)\s+([\d.]+)%\s+([\d.]+)%$/);
550
594
  if (hslSpaceMatch) {
551
- const hex = hslToHex(Number(hslSpaceMatch[1]), Number(hslSpaceMatch[2]), Number(hslSpaceMatch[3]));
595
+ const hex = hslToHex(
596
+ Number(hslSpaceMatch[1]),
597
+ Number(hslSpaceMatch[2]),
598
+ Number(hslSpaceMatch[3])
599
+ );
552
600
  const rgb2 = parseColorToRgb(hex);
553
- return { accent: hex, accentText: rgb2 ? getContrastText(...rgb2) : "#ffffff" };
601
+ return {
602
+ accent: hex,
603
+ accentText: rgb2 ? getContrastText(...rgb2) : "#ffffff"
604
+ };
554
605
  }
555
606
  const rgb = parseColorToRgb(raw);
556
607
  if (rgb) {
@@ -565,7 +616,27 @@ function detectHostAccent() {
565
616
  }
566
617
  function getTheme(dark) {
567
618
  const hostAccent = detectHostAccent();
568
- const defaults = dark ? { bg: "#1c1c1e", bgSecondary: "#27272a", border: "#2c2c2e", text: "#fafafa", textMuted: "#a1a1aa", accent: "#22d3ee", accentText: "#09090b", inputBg: "#27272a", inputBorder: "#3f3f46" } : { bg: "#ffffff", bgSecondary: "#f4f4f5", border: "#e4e4e7", text: "#18181b", textMuted: "#71717a", accent: "#0891b2", accentText: "#ffffff", inputBg: "#fafafa", inputBorder: "#d4d4d8" };
619
+ const defaults = dark ? {
620
+ bg: "#1c1c1e",
621
+ bgSecondary: "#27272a",
622
+ border: "#2c2c2e",
623
+ text: "#fafafa",
624
+ textMuted: "#a1a1aa",
625
+ accent: "#22d3ee",
626
+ accentText: "#09090b",
627
+ inputBg: "#27272a",
628
+ inputBorder: "#3f3f46"
629
+ } : {
630
+ bg: "#ffffff",
631
+ bgSecondary: "#f4f4f5",
632
+ border: "#e4e4e7",
633
+ text: "#18181b",
634
+ textMuted: "#71717a",
635
+ accent: "#0891b2",
636
+ accentText: "#ffffff",
637
+ inputBg: "#fafafa",
638
+ inputBorder: "#d4d4d8"
639
+ };
569
640
  if (hostAccent) {
570
641
  defaults.accent = hostAccent.accent;
571
642
  defaults.accentText = hostAccent.accentText;
@@ -615,10 +686,18 @@ function isLowQualityText(text) {
615
686
  function ReportDialog({
616
687
  report,
617
688
  enhanceText: enhanceText2,
689
+ transcribeAudio: transcribeAudio2,
618
690
  types,
619
691
  showSeverity = true
620
692
  }) {
621
693
  const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
694
+ const [isEnhanced, setIsEnhanced] = (0, import_react.useState)(false);
695
+ const [originalDescription, setOriginalDescription] = (0, import_react.useState)(
696
+ null
697
+ );
698
+ const [isListening, setIsListening] = (0, import_react.useState)(false);
699
+ const [isTranscribing, setIsTranscribing] = (0, import_react.useState)(false);
700
+ const [voiceError, setVoiceError] = (0, import_react.useState)(null);
622
701
  const [screenshotDragOver, setScreenshotDragOver] = (0, import_react.useState)(false);
623
702
  const [mounted, setMounted] = (0, import_react.useState)(false);
624
703
  (0, import_react.useEffect)(() => {
@@ -632,10 +711,22 @@ function ReportDialog({
632
711
  const [previewOpen, setPreviewOpen] = (0, import_react.useState)(false);
633
712
  const fileInputRef = (0, import_react.useRef)(null);
634
713
  const modalRef = (0, import_react.useRef)(null);
714
+ const textareaRef = (0, import_react.useRef)(null);
715
+ const recognitionRef = (0, import_react.useRef)(null);
716
+ const voiceBaseRef = (0, import_react.useRef)("");
717
+ const usingWebSpeechRef = (0, import_react.useRef)(false);
718
+ const textBeforeVoiceRef = (0, import_react.useRef)("");
719
+ const mediaRecorderRef = (0, import_react.useRef)(null);
720
+ const streamRef = (0, import_react.useRef)(null);
721
+ const sarvamChunksRef = (0, import_react.useRef)([]);
722
+ const spaceTimerRef = (0, import_react.useRef)(null);
723
+ const isPushToTalkRef = (0, import_react.useRef)(false);
635
724
  (0, import_react.useEffect)(() => {
636
725
  if (!isOpen) return;
637
726
  const dialogs = Array.from(
638
- document.querySelectorAll('[role="dialog"][data-state="open"]')
727
+ document.querySelectorAll(
728
+ '[role="dialog"][data-state="open"]'
729
+ )
639
730
  );
640
731
  dialogs.forEach((d) => d.setAttribute("inert", ""));
641
732
  return () => dialogs.forEach((d) => d.removeAttribute("inert"));
@@ -644,7 +735,13 @@ function ReportDialog({
644
735
  const [reportType, setReportType] = (0, import_react.useState)("BUG");
645
736
  const [severity, setSeverity] = (0, import_react.useState)("medium");
646
737
  const [validationError, setValidationError] = (0, import_react.useState)(null);
647
- const availableTypes = types != null ? types : ["BUG", "FEATURE_REQUEST", "QUESTION", "OTHER"];
738
+ const [placeholderIdx, setPlaceholderIdx] = (0, import_react.useState)(0);
739
+ const availableTypes = types != null ? types : [
740
+ "BUG",
741
+ "FEATURE_REQUEST",
742
+ "QUESTION",
743
+ "OTHER"
744
+ ];
648
745
  const isDark = useIsDark();
649
746
  const t = getTheme(isDark);
650
747
  const handleFileUpload = (e) => {
@@ -744,12 +841,187 @@ function ReportDialog({
744
841
  window.addEventListener("paste", handlePaste);
745
842
  return () => window.removeEventListener("paste", handlePaste);
746
843
  }, [isOpen]);
844
+ (0, import_react.useEffect)(() => {
845
+ if (!isOpen || !transcribeAudio2) return;
846
+ const id = setInterval(() => {
847
+ setPlaceholderIdx((i) => (i + 1) % 2);
848
+ }, 3e3);
849
+ return () => clearInterval(id);
850
+ }, [isOpen, transcribeAudio2]);
851
+ const stopVoice = () => {
852
+ var _a, _b;
853
+ usingWebSpeechRef.current = false;
854
+ (_a = recognitionRef.current) == null ? void 0 : _a.stop();
855
+ recognitionRef.current = null;
856
+ sarvamChunksRef.current = [];
857
+ const stream = streamRef.current;
858
+ streamRef.current = null;
859
+ stream == null ? void 0 : stream.getTracks().forEach((t2) => t2.stop());
860
+ try {
861
+ (_b = mediaRecorderRef.current) == null ? void 0 : _b.stop();
862
+ } catch (e) {
863
+ }
864
+ mediaRecorderRef.current = null;
865
+ setIsListening(false);
866
+ setIsTranscribing(false);
867
+ };
868
+ const stopListeningAndTranscribe = () => {
869
+ var _a, _b;
870
+ usingWebSpeechRef.current = false;
871
+ (_a = recognitionRef.current) == null ? void 0 : _a.stop();
872
+ recognitionRef.current = null;
873
+ setIsListening(false);
874
+ try {
875
+ (_b = mediaRecorderRef.current) == null ? void 0 : _b.stop();
876
+ } catch (e) {
877
+ }
878
+ };
747
879
  const handleClose = () => {
880
+ stopVoice();
748
881
  setIsOpen(false);
749
882
  setStep(1);
750
883
  setReportType("BUG");
751
884
  setSeverity("medium");
752
885
  setValidationError(null);
886
+ setVoiceError(null);
887
+ setIsEnhanced(false);
888
+ setOriginalDescription(null);
889
+ };
890
+ const toggleVoice = async () => {
891
+ if (isListening) {
892
+ stopListeningAndTranscribe();
893
+ return;
894
+ }
895
+ const SpeechRec = typeof window !== "undefined" ? window.SpeechRecognition || window.webkitSpeechRecognition : void 0;
896
+ let stream = null;
897
+ try {
898
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
899
+ } catch (err) {
900
+ const isDenied = err instanceof DOMException && (err.name === "NotAllowedError" || err.name === "PermissionDeniedError");
901
+ if (!SpeechRec || isDenied) {
902
+ setVoiceError("Microphone access denied");
903
+ return;
904
+ }
905
+ }
906
+ if (stream && transcribeAudio2) {
907
+ streamRef.current = stream;
908
+ sarvamChunksRef.current = [];
909
+ textBeforeVoiceRef.current = description;
910
+ const rec = new MediaRecorder(stream);
911
+ mediaRecorderRef.current = rec;
912
+ rec.ondataavailable = (e) => {
913
+ if (e.data.size > 0) sarvamChunksRef.current.push(e.data);
914
+ };
915
+ rec.onstop = async () => {
916
+ stream.getTracks().forEach((t2) => t2.stop());
917
+ if (streamRef.current === stream) streamRef.current = null;
918
+ mediaRecorderRef.current = null;
919
+ const chunks = sarvamChunksRef.current;
920
+ sarvamChunksRef.current = [];
921
+ if (!chunks.length || !transcribeAudio2) return;
922
+ setIsTranscribing(true);
923
+ try {
924
+ const blob = new Blob(chunks, { type: rec.mimeType || "audio/webm" });
925
+ const text = await transcribeAudio2(blob);
926
+ if (text.trim()) {
927
+ const sep = textBeforeVoiceRef.current.trim() ? " " : "";
928
+ setDescription(textBeforeVoiceRef.current + sep + text.trim());
929
+ setValidationError(null);
930
+ setIsEnhanced(false);
931
+ setOriginalDescription(null);
932
+ }
933
+ } catch (e) {
934
+ }
935
+ setIsTranscribing(false);
936
+ };
937
+ rec.start();
938
+ }
939
+ if (SpeechRec) {
940
+ setVoiceError(null);
941
+ usingWebSpeechRef.current = true;
942
+ voiceBaseRef.current = description;
943
+ textBeforeVoiceRef.current = description;
944
+ const recognition = new SpeechRec();
945
+ recognition.lang = "en-IN";
946
+ recognition.interimResults = true;
947
+ recognition.continuous = true;
948
+ recognitionRef.current = recognition;
949
+ recognition.onstart = () => setIsListening(true);
950
+ recognition.onresult = (event) => {
951
+ let finalText = "";
952
+ let interimText = "";
953
+ for (let i = event.resultIndex; i < event.results.length; i++) {
954
+ const text = event.results[i][0].transcript;
955
+ if (event.results[i].isFinal) finalText += text;
956
+ else interimText += text;
957
+ }
958
+ if (finalText) {
959
+ const sep = voiceBaseRef.current.trim() ? " " : "";
960
+ voiceBaseRef.current += sep + finalText.trim();
961
+ }
962
+ const liveText = interimText ? voiceBaseRef.current + (voiceBaseRef.current.trim() ? " " : "") + interimText : voiceBaseRef.current;
963
+ setDescription(liveText);
964
+ setValidationError(null);
965
+ };
966
+ recognition.onend = () => {
967
+ if (usingWebSpeechRef.current && recognitionRef.current === recognition) {
968
+ try {
969
+ recognition.start();
970
+ } catch (e) {
971
+ }
972
+ } else {
973
+ setIsListening(false);
974
+ }
975
+ };
976
+ recognition.onerror = (event) => {
977
+ if (event.error === "aborted" || event.error === "no-speech") return;
978
+ const isDenied = event.error === "not-allowed" || event.error === "audio-capture";
979
+ setVoiceError(isDenied ? "Microphone access denied" : "Speech recognition not available \u2014 try Chrome");
980
+ usingWebSpeechRef.current = false;
981
+ recognitionRef.current = null;
982
+ setIsListening(false);
983
+ };
984
+ recognition.start();
985
+ } else if (stream) {
986
+ setVoiceError(null);
987
+ setIsListening(true);
988
+ }
989
+ };
990
+ const handleSpaceDown = (e) => {
991
+ if (e.code !== "Space") return;
992
+ if (spaceTimerRef.current || isListening || isPushToTalkRef.current) {
993
+ e.preventDefault();
994
+ return;
995
+ }
996
+ if (e.repeat || !transcribeAudio2 || isTranscribing) return;
997
+ e.preventDefault();
998
+ spaceTimerRef.current = setTimeout(() => {
999
+ isPushToTalkRef.current = true;
1000
+ void toggleVoice();
1001
+ }, 400);
1002
+ };
1003
+ const handleSpaceUp = (e) => {
1004
+ var _a;
1005
+ if (e.code !== "Space") return;
1006
+ if (spaceTimerRef.current) {
1007
+ clearTimeout(spaceTimerRef.current);
1008
+ spaceTimerRef.current = null;
1009
+ if (!isPushToTalkRef.current) {
1010
+ const ta = textareaRef.current;
1011
+ const pos = ta ? (_a = ta.selectionStart) != null ? _a : description.length : description.length;
1012
+ setDescription((prev) => prev.slice(0, pos) + " " + prev.slice(pos));
1013
+ requestAnimationFrame(() => {
1014
+ if (textareaRef.current) {
1015
+ textareaRef.current.selectionStart = pos + 1;
1016
+ textareaRef.current.selectionEnd = pos + 1;
1017
+ }
1018
+ });
1019
+ }
1020
+ }
1021
+ if (isPushToTalkRef.current) {
1022
+ isPushToTalkRef.current = false;
1023
+ stopListeningAndTranscribe();
1024
+ }
753
1025
  };
754
1026
  const handleSubmit = async () => {
755
1027
  try {
@@ -766,7 +1038,11 @@ function ReportDialog({
766
1038
  if (showSeverity && reportType === "BUG") {
767
1039
  metadata.severity = severity;
768
1040
  }
769
- const result = await report(reportType, description.trim(), Object.keys(metadata).length > 0 ? metadata : void 0);
1041
+ const result = await report(
1042
+ reportType,
1043
+ description.trim(),
1044
+ Object.keys(metadata).length > 0 ? metadata : void 0
1045
+ );
770
1046
  if (result) {
771
1047
  setSubmitted(true);
772
1048
  setDescription("");
@@ -783,6 +1059,16 @@ function ReportDialog({
783
1059
  };
784
1060
  if (!mounted) return null;
785
1061
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1062
+ isOpen && (0, import_react_dom.createPortal)(
1063
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1064
+ @keyframes gg-pulse{0%,100%{opacity:1}50%{opacity:0.35}}
1065
+ @keyframes gg-b1{0%,100%{height:3px}40%{height:13px}}
1066
+ @keyframes gg-b2{0%,100%{height:8px}50%{height:3px}}
1067
+ @keyframes gg-b3{0%,100%{height:4px}30%{height:14px}70%{height:5px}}
1068
+ @keyframes gg-b4{0%,100%{height:6px}60%{height:13px}}
1069
+ ` }),
1070
+ document.body
1071
+ ),
786
1072
  isOpen && (0, import_react_dom.createPortal)(
787
1073
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
788
1074
  "div",
@@ -839,36 +1125,64 @@ function ReportDialog({
839
1125
  alignItems: "center"
840
1126
  },
841
1127
  children: [
842
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
843
- step > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
844
- "button",
845
- {
846
- type: "button",
847
- onClick: () => setStep((s) => s - 1),
848
- style: {
849
- background: "none",
850
- border: "none",
851
- cursor: "pointer",
852
- padding: "2px",
853
- display: "flex",
854
- alignItems: "center"
855
- },
856
- "aria-label": "Back",
857
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 3L5 8L10 13", stroke: t.textMuted, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
858
- }
859
- ),
860
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
861
- "span",
862
- {
863
- style: {
864
- fontSize: "15px",
865
- fontWeight: 600,
866
- color: t.text
867
- },
868
- children: step === 1 ? "What's on your mind?" : step === 2 ? "Tell us more" : "Review & Send"
869
- }
870
- )
871
- ] }),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1129
+ "div",
1130
+ {
1131
+ style: {
1132
+ display: "flex",
1133
+ alignItems: "center",
1134
+ gap: "8px"
1135
+ },
1136
+ children: [
1137
+ step > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1138
+ "button",
1139
+ {
1140
+ type: "button",
1141
+ onClick: () => setStep((s) => s - 1),
1142
+ style: {
1143
+ background: "none",
1144
+ border: "none",
1145
+ cursor: "pointer",
1146
+ padding: "2px",
1147
+ display: "flex",
1148
+ alignItems: "center"
1149
+ },
1150
+ "aria-label": "Back",
1151
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1152
+ "svg",
1153
+ {
1154
+ width: "16",
1155
+ height: "16",
1156
+ viewBox: "0 0 16 16",
1157
+ fill: "none",
1158
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1159
+ "path",
1160
+ {
1161
+ d: "M10 3L5 8L10 13",
1162
+ stroke: t.textMuted,
1163
+ strokeWidth: "2",
1164
+ strokeLinecap: "round",
1165
+ strokeLinejoin: "round"
1166
+ }
1167
+ )
1168
+ }
1169
+ )
1170
+ }
1171
+ ),
1172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1173
+ "span",
1174
+ {
1175
+ style: {
1176
+ fontSize: "15px",
1177
+ fontWeight: 600,
1178
+ color: t.text
1179
+ },
1180
+ children: step === 1 ? "What's on your mind?" : "Tell us more"
1181
+ }
1182
+ )
1183
+ ]
1184
+ }
1185
+ ),
872
1186
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
873
1187
  "button",
874
1188
  {
@@ -884,27 +1198,74 @@ function ReportDialog({
884
1198
  justifyContent: "center"
885
1199
  },
886
1200
  "aria-label": "Close",
887
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", style: { display: "block" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4L12 12M12 4L4 12", stroke: t.textMuted, strokeWidth: "2", strokeLinecap: "round" }) })
1201
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1202
+ "svg",
1203
+ {
1204
+ width: "16",
1205
+ height: "16",
1206
+ viewBox: "0 0 16 16",
1207
+ fill: "none",
1208
+ style: { display: "block" },
1209
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1210
+ "path",
1211
+ {
1212
+ d: "M4 4L12 12M12 4L4 12",
1213
+ stroke: t.textMuted,
1214
+ strokeWidth: "2",
1215
+ strokeLinecap: "round"
1216
+ }
1217
+ )
1218
+ }
1219
+ )
888
1220
  }
889
1221
  )
890
1222
  ]
891
1223
  }
892
1224
  ),
893
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0", marginTop: "12px" }, children: [1, 2, 3].map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center" }, children: [
894
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
895
- width: "8px",
896
- height: "8px",
897
- borderRadius: "50%",
898
- backgroundColor: s <= step ? t.accent : t.inputBorder,
899
- transition: "background-color 0.2s ease"
900
- } }),
901
- i < 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
902
- width: "40px",
903
- height: "2px",
904
- backgroundColor: s < step ? t.accent : t.inputBorder,
905
- transition: "background-color 0.2s ease"
906
- } })
907
- ] }, s)) })
1225
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1226
+ "div",
1227
+ {
1228
+ style: {
1229
+ display: "flex",
1230
+ alignItems: "center",
1231
+ justifyContent: "center",
1232
+ gap: "0",
1233
+ marginTop: "12px"
1234
+ },
1235
+ children: [1, 2].map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1236
+ "div",
1237
+ {
1238
+ style: { display: "flex", alignItems: "center" },
1239
+ children: [
1240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1241
+ "div",
1242
+ {
1243
+ style: {
1244
+ width: "8px",
1245
+ height: "8px",
1246
+ borderRadius: "50%",
1247
+ backgroundColor: s <= step ? t.accent : t.inputBorder,
1248
+ transition: "background-color 0.2s ease"
1249
+ }
1250
+ }
1251
+ ),
1252
+ i < 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1253
+ "div",
1254
+ {
1255
+ style: {
1256
+ width: "40px",
1257
+ height: "2px",
1258
+ backgroundColor: s < step ? t.accent : t.inputBorder,
1259
+ transition: "background-color 0.2s ease"
1260
+ }
1261
+ }
1262
+ )
1263
+ ]
1264
+ },
1265
+ s
1266
+ ))
1267
+ }
1268
+ )
908
1269
  ]
909
1270
  }
910
1271
  ),
@@ -924,76 +1285,185 @@ function ReportDialog({
924
1285
  ]
925
1286
  }
926
1287
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
927
- step === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
928
- "button",
1288
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1289
+ "div",
929
1290
  {
930
- type: "button",
931
- onClick: () => {
932
- setReportType(type);
933
- setStep(2);
934
- },
935
1291
  style: {
936
- display: "flex",
937
- flexDirection: "column",
938
- alignItems: "center",
939
- gap: "8px",
940
- padding: "16px 8px",
941
- borderRadius: "8px",
942
- border: `1px solid ${t.inputBorder}`,
943
- background: t.inputBg,
944
- cursor: "pointer",
945
- color: t.text,
946
- fontFamily: "inherit",
947
- transition: "border-color 0.15s ease"
948
- },
949
- onMouseEnter: (e) => {
950
- e.currentTarget.style.borderColor = t.accent;
951
- },
952
- onMouseLeave: (e) => {
953
- e.currentTarget.style.borderColor = t.inputBorder;
1292
+ display: "grid",
1293
+ gridTemplateColumns: "1fr 1fr",
1294
+ gap: "8px"
954
1295
  },
955
- children: [
956
- getTypeIcon(type, t.accent),
957
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
958
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "11px", color: t.textMuted, lineHeight: "1.3" }, children: getTypeSubtitle(type) })
959
- ]
960
- },
961
- type
962
- )) }),
1296
+ children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1297
+ "button",
1298
+ {
1299
+ type: "button",
1300
+ onClick: () => {
1301
+ setReportType(type);
1302
+ setStep(2);
1303
+ },
1304
+ style: {
1305
+ display: "flex",
1306
+ flexDirection: "column",
1307
+ alignItems: "center",
1308
+ gap: "8px",
1309
+ padding: "16px 8px",
1310
+ borderRadius: "8px",
1311
+ border: `1px solid ${t.inputBorder}`,
1312
+ background: t.inputBg,
1313
+ cursor: "pointer",
1314
+ color: t.text,
1315
+ fontFamily: "inherit",
1316
+ transition: "border-color 0.15s ease"
1317
+ },
1318
+ onMouseEnter: (e) => {
1319
+ e.currentTarget.style.borderColor = t.accent;
1320
+ },
1321
+ onMouseLeave: (e) => {
1322
+ e.currentTarget.style.borderColor = t.inputBorder;
1323
+ },
1324
+ children: [
1325
+ getTypeIcon(type, t.accent),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1328
+ "span",
1329
+ {
1330
+ style: {
1331
+ fontSize: "11px",
1332
+ color: t.textMuted,
1333
+ lineHeight: "1.3"
1334
+ },
1335
+ children: getTypeSubtitle(type)
1336
+ }
1337
+ )
1338
+ ]
1339
+ },
1340
+ type
1341
+ ))
1342
+ }
1343
+ ),
963
1344
  step === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
964
1345
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
965
1346
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
966
1347
  "textarea",
967
1348
  {
1349
+ ref: textareaRef,
968
1350
  value: description,
969
1351
  onChange: (e) => {
970
1352
  setDescription(e.target.value);
971
1353
  if (validationError) setValidationError(null);
1354
+ if (isEnhanced) {
1355
+ setIsEnhanced(false);
1356
+ setOriginalDescription(null);
1357
+ }
972
1358
  },
973
- placeholder: getPlaceholder(reportType),
1359
+ placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2),
974
1360
  style: {
975
1361
  width: "100%",
976
1362
  minHeight: "100px",
977
- padding: enhanceText2 ? "28px 12px 10px" : "10px 12px",
1363
+ padding: enhanceText2 ? "28px 12px 36px" : transcribeAudio2 ? "10px 12px 36px" : "10px 12px",
978
1364
  borderRadius: "8px",
979
- border: `1px solid ${t.inputBorder}`,
1365
+ border: `1px solid ${isTranscribing ? "#f59e0b" : isListening ? t.accent : t.inputBorder}`,
980
1366
  fontSize: "14px",
981
1367
  fontFamily: "inherit",
982
- resize: "vertical",
1368
+ resize: "none",
983
1369
  outline: "none",
984
1370
  boxSizing: "border-box",
985
1371
  color: t.text,
986
- backgroundColor: t.inputBg
1372
+ backgroundColor: t.inputBg,
1373
+ transition: "border-color 0.2s ease"
987
1374
  },
1375
+ onKeyDown: handleSpaceDown,
1376
+ onKeyUp: handleSpaceUp,
988
1377
  onFocus: (e) => {
989
1378
  e.target.style.borderColor = t.accent;
990
1379
  },
991
1380
  onBlur: (e) => {
992
- e.target.style.borderColor = t.inputBorder;
1381
+ if (!isListening && !isTranscribing)
1382
+ e.target.style.borderColor = t.inputBorder;
993
1383
  },
994
1384
  autoFocus: true
995
1385
  }
996
1386
  ),
1387
+ (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1388
+ "span",
1389
+ {
1390
+ style: {
1391
+ position: "absolute",
1392
+ bottom: "10px",
1393
+ left: "10px",
1394
+ display: "inline-flex",
1395
+ alignItems: "center",
1396
+ gap: "4px",
1397
+ fontSize: "10px",
1398
+ fontWeight: 700,
1399
+ letterSpacing: "0.04em",
1400
+ color: isTranscribing ? "#f59e0b" : "#ef4444",
1401
+ pointerEvents: "none",
1402
+ zIndex: 1
1403
+ },
1404
+ children: [
1405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1406
+ "span",
1407
+ {
1408
+ style: {
1409
+ width: "6px",
1410
+ height: "6px",
1411
+ borderRadius: "50%",
1412
+ backgroundColor: "currentColor",
1413
+ animation: "gg-pulse 1.2s ease-in-out infinite",
1414
+ display: "inline-block",
1415
+ flexShrink: 0
1416
+ }
1417
+ }
1418
+ ),
1419
+ isTranscribing ? "Transcribing\u2026" : "REC",
1420
+ isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1421
+ "span",
1422
+ {
1423
+ style: {
1424
+ display: "inline-flex",
1425
+ alignItems: "flex-end",
1426
+ gap: "2px",
1427
+ height: "14px",
1428
+ marginLeft: "3px"
1429
+ },
1430
+ children: [
1431
+ { anim: "gg-b1", dur: "0.6s", delay: "0s" },
1432
+ {
1433
+ anim: "gg-b2",
1434
+ dur: "0.5s",
1435
+ delay: "0.1s"
1436
+ },
1437
+ {
1438
+ anim: "gg-b3",
1439
+ dur: "0.7s",
1440
+ delay: "0.05s"
1441
+ },
1442
+ {
1443
+ anim: "gg-b4",
1444
+ dur: "0.55s",
1445
+ delay: "0.15s"
1446
+ }
1447
+ ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1448
+ "span",
1449
+ {
1450
+ style: {
1451
+ width: "2.5px",
1452
+ height: "4px",
1453
+ borderRadius: "1px",
1454
+ backgroundColor: "#ef4444",
1455
+ animation: `${b.anim} ${b.dur} ease-in-out ${b.delay} infinite`,
1456
+ display: "inline-block",
1457
+ alignSelf: "center"
1458
+ }
1459
+ },
1460
+ i
1461
+ ))
1462
+ }
1463
+ )
1464
+ ]
1465
+ }
1466
+ ),
997
1467
  enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
998
1468
  "button",
999
1469
  {
@@ -1002,16 +1472,22 @@ function ReportDialog({
1002
1472
  if (!description.trim() || isEnhancing) return;
1003
1473
  setIsEnhancing(true);
1004
1474
  try {
1005
- const polished = await enhanceText2(description);
1475
+ const polished = await enhanceText2(
1476
+ description,
1477
+ screenshot
1478
+ );
1006
1479
  if (polished && polished !== description) {
1480
+ setOriginalDescription(description);
1007
1481
  setDescription(polished);
1008
- if (validationError) setValidationError(null);
1482
+ setIsEnhanced(true);
1483
+ if (validationError)
1484
+ setValidationError(null);
1009
1485
  }
1010
1486
  } finally {
1011
1487
  setIsEnhancing(false);
1012
1488
  }
1013
1489
  },
1014
- disabled: !description.trim() || isEnhancing,
1490
+ disabled: !description.trim() || isEnhancing || isEnhanced,
1015
1491
  style: {
1016
1492
  position: "absolute",
1017
1493
  top: "6px",
@@ -1032,105 +1508,393 @@ function ReportDialog({
1032
1508
  },
1033
1509
  title: "Polish grammar \u2014 preserves your meaning",
1034
1510
  children: [
1035
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3zM19 15l.75 2.25L22 18l-2.25.75L19 21l-.75-2.25L16 18l2.25-.75L19 15z", stroke: t.textMuted, strokeWidth: "1.5", strokeLinejoin: "round" }) }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1512
+ "svg",
1513
+ {
1514
+ width: "11",
1515
+ height: "11",
1516
+ viewBox: "0 0 24 24",
1517
+ fill: "none",
1518
+ "aria-hidden": "true",
1519
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1520
+ "path",
1521
+ {
1522
+ d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3zM19 15l.75 2.25L22 18l-2.25.75L19 21l-.75-2.25L16 18l2.25-.75L19 15z",
1523
+ stroke: t.textMuted,
1524
+ strokeWidth: "1.5",
1525
+ strokeLinejoin: "round"
1526
+ }
1527
+ )
1528
+ }
1529
+ ),
1036
1530
  isEnhancing ? "Enhancing..." : "AI enhance"
1037
1531
  ]
1038
1532
  }
1533
+ ),
1534
+ transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1535
+ "button",
1536
+ {
1537
+ type: "button",
1538
+ onClick: () => {
1539
+ void toggleVoice();
1540
+ },
1541
+ title: isListening ? "Stop recording" : "Speak your report",
1542
+ style: {
1543
+ position: "absolute",
1544
+ bottom: "14px",
1545
+ right: "7px",
1546
+ width: "26px",
1547
+ height: "26px",
1548
+ display: "flex",
1549
+ alignItems: "center",
1550
+ justifyContent: "center",
1551
+ background: "transparent",
1552
+ border: `1.5px solid ${isListening ? "#ef4444" : t.inputBorder}`,
1553
+ borderRadius: "6px",
1554
+ color: isListening ? "#ef4444" : t.textMuted,
1555
+ cursor: "pointer",
1556
+ zIndex: 1,
1557
+ padding: 0,
1558
+ transition: "border-color 0.2s ease, color 0.2s ease"
1559
+ },
1560
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1561
+ "svg",
1562
+ {
1563
+ width: "12",
1564
+ height: "12",
1565
+ viewBox: "0 0 24 24",
1566
+ fill: "currentColor",
1567
+ "aria-hidden": "true",
1568
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1569
+ "rect",
1570
+ {
1571
+ x: "6",
1572
+ y: "6",
1573
+ width: "12",
1574
+ height: "12",
1575
+ rx: "2"
1576
+ }
1577
+ )
1578
+ }
1579
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1580
+ "svg",
1581
+ {
1582
+ width: "13",
1583
+ height: "13",
1584
+ viewBox: "0 0 24 24",
1585
+ fill: "none",
1586
+ "aria-hidden": "true",
1587
+ children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1589
+ "rect",
1590
+ {
1591
+ x: "9",
1592
+ y: "2",
1593
+ width: "6",
1594
+ height: "12",
1595
+ rx: "3",
1596
+ stroke: "currentColor",
1597
+ strokeWidth: "1.8"
1598
+ }
1599
+ ),
1600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1601
+ "path",
1602
+ {
1603
+ d: "M5 10a7 7 0 0014 0M12 19v3M9 22h6",
1604
+ stroke: "currentColor",
1605
+ strokeWidth: "1.8",
1606
+ strokeLinecap: "round"
1607
+ }
1608
+ )
1609
+ ]
1610
+ }
1611
+ )
1612
+ }
1039
1613
  )
1040
1614
  ] }),
1041
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1042
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: t.textMuted, marginBottom: "6px", display: "block", fontWeight: 500 }, children: "Screenshot" }),
1043
- screenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1044
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
1615
+ transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1616
+ "div",
1617
+ {
1618
+ style: {
1619
+ display: "flex",
1620
+ alignItems: "center",
1621
+ gap: "5px",
1622
+ marginTop: "5px",
1623
+ fontSize: "11px",
1624
+ color: t.text,
1625
+ opacity: 0.55,
1626
+ userSelect: "none"
1627
+ },
1628
+ children: [
1629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1633
+ ] }),
1634
+ "Hold Space to speak \u2014 we'll transcribe it"
1635
+ ]
1636
+ }
1637
+ ),
1638
+ isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1639
+ "div",
1640
+ {
1641
+ style: {
1642
+ display: "flex",
1643
+ alignItems: "center",
1644
+ gap: "6px",
1645
+ marginTop: "6px",
1646
+ padding: "6px 8px",
1647
+ borderRadius: "6px",
1648
+ background: isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.05)",
1649
+ border: `1px solid ${t.accent}40`
1650
+ },
1651
+ children: [
1045
1652
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1046
- "img",
1653
+ "svg",
1654
+ {
1655
+ width: "12",
1656
+ height: "12",
1657
+ viewBox: "0 0 24 24",
1658
+ fill: "none",
1659
+ style: { flexShrink: 0 },
1660
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1661
+ "path",
1662
+ {
1663
+ d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z",
1664
+ stroke: t.accent,
1665
+ strokeWidth: "1.5",
1666
+ strokeLinejoin: "round"
1667
+ }
1668
+ )
1669
+ }
1670
+ ),
1671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1672
+ "span",
1047
1673
  {
1048
- src: screenshot,
1049
- alt: "Page screenshot",
1050
- onClick: () => setPreviewOpen(true),
1051
1674
  style: {
1052
- width: "100%",
1053
- borderRadius: "6px",
1054
- border: `1px solid ${t.border}`,
1055
- maxHeight: "64px",
1056
- objectFit: "cover",
1057
- objectPosition: "top",
1058
- cursor: "zoom-in",
1059
- display: "block"
1060
- }
1675
+ fontSize: "11px",
1676
+ color: t.textMuted,
1677
+ flex: 1
1678
+ },
1679
+ children: "AI enhanced"
1061
1680
  }
1062
1681
  ),
1063
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
1064
- position: "absolute",
1065
- bottom: "5px",
1066
- left: "6px",
1067
- background: "rgba(0,0,0,0.55)",
1068
- color: "#fff",
1069
- fontSize: "10px",
1070
- padding: "1px 5px",
1071
- borderRadius: "3px",
1072
- pointerEvents: "none"
1073
- }, children: "click to preview" })
1074
- ] }),
1075
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "6px", marginTop: "6px" }, children: [
1076
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1077
1683
  "button",
1078
1684
  {
1079
1685
  type: "button",
1080
- onClick: () => setScreenshot(null),
1686
+ onClick: () => {
1687
+ setIsEnhanced(false);
1688
+ setOriginalDescription(null);
1689
+ },
1081
1690
  style: {
1082
- flex: 1,
1083
- padding: "6px 0",
1084
- borderRadius: "6px",
1085
- border: `1px solid ${t.inputBorder}`,
1086
- background: "transparent",
1087
- color: "#ef4444",
1088
- fontSize: "12px",
1089
- fontWeight: 500,
1691
+ padding: "2px 8px",
1692
+ borderRadius: "4px",
1693
+ border: `1px solid ${t.accent}`,
1694
+ background: t.accent,
1695
+ color: t.accentText,
1696
+ fontSize: "11px",
1697
+ fontWeight: 600,
1090
1698
  cursor: "pointer",
1091
- fontFamily: "inherit",
1092
- display: "flex",
1093
- alignItems: "center",
1094
- justifyContent: "center",
1095
- gap: "4px"
1699
+ fontFamily: "inherit"
1096
1700
  },
1097
- children: [
1098
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 1L9 9M9 1L1 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }),
1099
- "Remove"
1100
- ]
1701
+ children: "Keep"
1101
1702
  }
1102
1703
  ),
1103
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1104
1705
  "button",
1105
1706
  {
1106
1707
  type: "button",
1107
1708
  onClick: () => {
1108
- var _a;
1109
- return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1709
+ setDescription(originalDescription);
1710
+ setIsEnhanced(false);
1711
+ setOriginalDescription(null);
1110
1712
  },
1111
1713
  style: {
1112
- flex: 1,
1113
- padding: "6px 0",
1114
- borderRadius: "6px",
1714
+ padding: "2px 8px",
1715
+ borderRadius: "4px",
1115
1716
  border: `1px solid ${t.inputBorder}`,
1116
1717
  background: "transparent",
1117
1718
  color: t.textMuted,
1118
- fontSize: "12px",
1119
- fontWeight: 500,
1719
+ fontSize: "11px",
1720
+ fontWeight: 600,
1120
1721
  cursor: "pointer",
1121
- fontFamily: "inherit",
1122
- display: "flex",
1123
- alignItems: "center",
1124
- justifyContent: "center",
1125
- gap: "4px"
1722
+ fontFamily: "inherit"
1126
1723
  },
1127
- children: [
1128
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
1129
- "Replace"
1130
- ]
1724
+ children: "Restore"
1725
+ }
1726
+ )
1727
+ ]
1728
+ }
1729
+ ),
1730
+ voiceError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1731
+ "p",
1732
+ {
1733
+ style: {
1734
+ color: "#ef4444",
1735
+ fontSize: "11px",
1736
+ marginTop: "4px",
1737
+ marginBottom: 0
1738
+ },
1739
+ children: voiceError
1740
+ }
1741
+ ),
1742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1744
+ "span",
1745
+ {
1746
+ style: {
1747
+ fontSize: "12px",
1748
+ color: t.textMuted,
1749
+ marginBottom: "6px",
1750
+ display: "block",
1751
+ fontWeight: 500
1752
+ },
1753
+ children: "Screenshot"
1754
+ }
1755
+ ),
1756
+ screenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
1758
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1759
+ "img",
1760
+ {
1761
+ src: screenshot,
1762
+ alt: "Page screenshot",
1763
+ onClick: () => setPreviewOpen(true),
1764
+ style: {
1765
+ width: "100%",
1766
+ borderRadius: "6px",
1767
+ border: `1px solid ${t.border}`,
1768
+ maxHeight: "64px",
1769
+ objectFit: "cover",
1770
+ objectPosition: "top",
1771
+ cursor: "zoom-in",
1772
+ display: "block"
1773
+ }
1774
+ }
1775
+ ),
1776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1777
+ "span",
1778
+ {
1779
+ style: {
1780
+ position: "absolute",
1781
+ bottom: "5px",
1782
+ left: "6px",
1783
+ background: "rgba(0,0,0,0.55)",
1784
+ color: "#fff",
1785
+ fontSize: "10px",
1786
+ padding: "1px 5px",
1787
+ borderRadius: "3px",
1788
+ pointerEvents: "none"
1789
+ },
1790
+ children: "click to preview"
1131
1791
  }
1132
1792
  )
1133
- ] })
1793
+ ] }),
1794
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1795
+ "div",
1796
+ {
1797
+ style: {
1798
+ display: "flex",
1799
+ gap: "6px",
1800
+ marginTop: "6px"
1801
+ },
1802
+ children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1804
+ "button",
1805
+ {
1806
+ type: "button",
1807
+ onClick: () => setScreenshot(null),
1808
+ style: {
1809
+ flex: 1,
1810
+ padding: "6px 0",
1811
+ borderRadius: "6px",
1812
+ border: `1px solid ${t.inputBorder}`,
1813
+ background: "transparent",
1814
+ color: "#ef4444",
1815
+ fontSize: "12px",
1816
+ fontWeight: 500,
1817
+ cursor: "pointer",
1818
+ fontFamily: "inherit",
1819
+ display: "flex",
1820
+ alignItems: "center",
1821
+ justifyContent: "center",
1822
+ gap: "4px"
1823
+ },
1824
+ children: [
1825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1826
+ "svg",
1827
+ {
1828
+ width: "11",
1829
+ height: "11",
1830
+ viewBox: "0 0 10 10",
1831
+ fill: "none",
1832
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1833
+ "path",
1834
+ {
1835
+ d: "M1 1L9 9M9 1L1 9",
1836
+ stroke: "currentColor",
1837
+ strokeWidth: "1.5",
1838
+ strokeLinecap: "round"
1839
+ }
1840
+ )
1841
+ }
1842
+ ),
1843
+ "Remove"
1844
+ ]
1845
+ }
1846
+ ),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1848
+ "button",
1849
+ {
1850
+ type: "button",
1851
+ onClick: () => {
1852
+ var _a;
1853
+ return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1854
+ },
1855
+ style: {
1856
+ flex: 1,
1857
+ padding: "6px 0",
1858
+ borderRadius: "6px",
1859
+ border: `1px solid ${t.inputBorder}`,
1860
+ background: "transparent",
1861
+ color: t.textMuted,
1862
+ fontSize: "12px",
1863
+ fontWeight: 500,
1864
+ cursor: "pointer",
1865
+ fontFamily: "inherit",
1866
+ display: "flex",
1867
+ alignItems: "center",
1868
+ justifyContent: "center",
1869
+ gap: "4px"
1870
+ },
1871
+ children: [
1872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1873
+ "svg",
1874
+ {
1875
+ width: "11",
1876
+ height: "11",
1877
+ viewBox: "0 0 24 24",
1878
+ fill: "none",
1879
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1880
+ "path",
1881
+ {
1882
+ d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12",
1883
+ stroke: "currentColor",
1884
+ strokeWidth: "2",
1885
+ strokeLinecap: "round",
1886
+ strokeLinejoin: "round"
1887
+ }
1888
+ )
1889
+ }
1890
+ ),
1891
+ "Replace"
1892
+ ]
1893
+ }
1894
+ )
1895
+ ]
1896
+ }
1897
+ )
1134
1898
  ] }) : (
1135
1899
  /* Drop zone */
1136
1900
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1143,7 +1907,9 @@ function ReportDialog({
1143
1907
  onDrop: (e) => {
1144
1908
  e.preventDefault();
1145
1909
  setScreenshotDragOver(false);
1146
- const file = Array.from(e.dataTransfer.files).find((f) => f.type.startsWith("image/"));
1910
+ const file = Array.from(
1911
+ e.dataTransfer.files
1912
+ ).find((f) => f.type.startsWith("image/"));
1147
1913
  if (!file) return;
1148
1914
  const reader = new FileReader();
1149
1915
  reader.onload = () => setScreenshot(reader.result);
@@ -1164,20 +1930,95 @@ function ReportDialog({
1164
1930
  transition: "border-color 0.15s ease, background 0.15s ease"
1165
1931
  },
1166
1932
  children: [
1167
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", style: { margin: "0 auto 6px", display: "block", opacity: 0.45 }, children: [
1168
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", stroke: t.textMuted, strokeWidth: "1.5" }),
1169
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5", stroke: t.textMuted, strokeWidth: "1.5" }),
1170
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15l-5-5L5 21", stroke: t.textMuted, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
1171
- ] }),
1172
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: "12px", fontWeight: 600, color: t.text }, children: screenshotDragOver ? "Drop to attach" : "Add a screenshot" }),
1173
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "3px 0 0", fontSize: "11px", color: t.textMuted }, children: "Drag & drop \xB7 Paste \u2318V / Ctrl+V \xB7 Click to browse" })
1933
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1934
+ "svg",
1935
+ {
1936
+ width: "22",
1937
+ height: "22",
1938
+ viewBox: "0 0 24 24",
1939
+ fill: "none",
1940
+ style: {
1941
+ margin: "0 auto 6px",
1942
+ display: "block",
1943
+ opacity: 0.45
1944
+ },
1945
+ children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1947
+ "rect",
1948
+ {
1949
+ x: "3",
1950
+ y: "3",
1951
+ width: "18",
1952
+ height: "18",
1953
+ rx: "2",
1954
+ stroke: t.textMuted,
1955
+ strokeWidth: "1.5"
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1959
+ "circle",
1960
+ {
1961
+ cx: "8.5",
1962
+ cy: "8.5",
1963
+ r: "1.5",
1964
+ stroke: t.textMuted,
1965
+ strokeWidth: "1.5"
1966
+ }
1967
+ ),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1969
+ "path",
1970
+ {
1971
+ d: "M21 15l-5-5L5 21",
1972
+ stroke: t.textMuted,
1973
+ strokeWidth: "1.5",
1974
+ strokeLinecap: "round",
1975
+ strokeLinejoin: "round"
1976
+ }
1977
+ )
1978
+ ]
1979
+ }
1980
+ ),
1981
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1982
+ "p",
1983
+ {
1984
+ style: {
1985
+ margin: 0,
1986
+ fontSize: "12px",
1987
+ fontWeight: 600,
1988
+ color: t.text
1989
+ },
1990
+ children: screenshotDragOver ? "Drop to attach" : "Add a screenshot"
1991
+ }
1992
+ ),
1993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1994
+ "p",
1995
+ {
1996
+ style: {
1997
+ margin: "3px 0 0",
1998
+ fontSize: "11px",
1999
+ color: t.textMuted
2000
+ },
2001
+ children: "Drag & drop \xB7 Paste \u2318V / Ctrl+V \xB7 Click to browse"
2002
+ }
2003
+ )
1174
2004
  ]
1175
2005
  }
1176
2006
  )
1177
2007
  )
1178
2008
  ] }),
1179
2009
  showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1180
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: t.textMuted, marginBottom: "6px", display: "block" }, children: "Severity" }),
2010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2011
+ "span",
2012
+ {
2013
+ style: {
2014
+ fontSize: "12px",
2015
+ color: t.textMuted,
2016
+ marginBottom: "6px",
2017
+ display: "block"
2018
+ },
2019
+ children: "Severity"
2020
+ }
2021
+ ),
1181
2022
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1182
2023
  "button",
1183
2024
  {
@@ -1202,114 +2043,34 @@ function ReportDialog({
1202
2043
  s
1203
2044
  )) })
1204
2045
  ] }),
1205
- validationError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "#ef4444", fontSize: "12px", marginTop: "6px" }, children: validationError }),
1206
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1207
- "button",
2046
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2047
+ "p",
1208
2048
  {
1209
- type: "button",
1210
- onClick: () => {
1211
- const err = isLowQualityText(description);
1212
- if (err) {
1213
- setValidationError(err);
1214
- return;
1215
- }
1216
- setValidationError(null);
1217
- setStep(3);
1218
- },
1219
- disabled: !description.trim(),
1220
2049
  style: {
1221
- marginTop: "12px",
1222
- width: "100%",
1223
- padding: "10px",
1224
- borderRadius: "8px",
1225
- border: "none",
1226
- backgroundColor: !description.trim() ? t.bgSecondary : t.accent,
1227
- color: !description.trim() ? t.textMuted : t.accentText,
1228
- fontSize: "14px",
1229
- fontWeight: 600,
1230
- cursor: !description.trim() ? "not-allowed" : "pointer",
1231
- fontFamily: "inherit",
1232
- transition: "background-color 0.15s ease"
2050
+ color: "#ef4444",
2051
+ fontSize: "12px",
2052
+ marginTop: "6px"
1233
2053
  },
1234
- children: "Next"
2054
+ children: validationError
1235
2055
  }
1236
- )
1237
- ] }),
1238
- step === 3 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1239
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap", marginBottom: "12px" }, children: [
1240
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: {
1241
- display: "inline-flex",
1242
- alignItems: "center",
1243
- gap: "4px",
1244
- padding: "4px 10px",
1245
- borderRadius: "12px",
1246
- fontSize: "12px",
1247
- fontWeight: 600,
1248
- backgroundColor: t.bgSecondary,
1249
- color: t.text,
1250
- border: `1px solid ${t.border}`
1251
- }, children: [
1252
- getTypeIcon(reportType, t.accent, 12),
1253
- getTypeLabel(reportType)
1254
- ] }),
1255
- showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
1256
- display: "inline-flex",
1257
- alignItems: "center",
1258
- padding: "4px 10px",
1259
- borderRadius: "12px",
1260
- fontSize: "12px",
1261
- fontWeight: 600,
1262
- backgroundColor: severity === "high" ? "#fef2f2" : severity === "medium" ? "#fffbeb" : "#f0fdf4",
1263
- color: severity === "high" ? "#dc2626" : severity === "medium" ? "#d97706" : "#16a34a",
1264
- border: `1px solid ${severity === "high" ? "#fecaca" : severity === "medium" ? "#fde68a" : "#bbf7d0"}`,
1265
- textTransform: "capitalize"
1266
- }, children: severity })
1267
- ] }),
1268
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1269
- padding: "10px 12px",
1270
- borderRadius: "8px",
1271
- border: `1px solid ${t.inputBorder}`,
1272
- backgroundColor: t.inputBg,
1273
- fontSize: "13px",
1274
- color: t.text,
1275
- marginBottom: "10px",
1276
- maxHeight: "80px",
1277
- overflow: "hidden",
1278
- lineHeight: "1.4"
1279
- }, children: description.length > 200 ? description.slice(0, 200) + "..." : description }),
1280
- screenshot && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: "10px", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1281
- "img",
1282
- {
1283
- src: screenshot,
1284
- alt: "Page screenshot",
1285
- onClick: () => setPreviewOpen(true),
1286
- style: {
1287
- width: "100%",
1288
- borderRadius: "6px",
1289
- border: `1px solid ${t.border}`,
1290
- maxHeight: "80px",
1291
- objectFit: "cover",
1292
- objectPosition: "top",
1293
- cursor: "pointer"
1294
- }
1295
- }
1296
- ) }),
2056
+ ),
1297
2057
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1298
2058
  "button",
1299
2059
  {
1300
2060
  type: "button",
1301
2061
  onClick: handleSubmit,
1302
- disabled: isSubmitting,
2062
+ disabled: !description.trim() || isSubmitting,
1303
2063
  style: {
2064
+ marginTop: "12px",
1304
2065
  width: "100%",
1305
2066
  padding: "10px",
1306
2067
  borderRadius: "8px",
1307
2068
  border: "none",
1308
- backgroundColor: isSubmitting ? t.bgSecondary : t.accent,
1309
- color: isSubmitting ? t.textMuted : t.accentText,
2069
+ backgroundColor: !description.trim() || isSubmitting ? t.bgSecondary : t.accent,
2070
+ color: !description.trim() || isSubmitting ? t.textMuted : t.accentText,
1310
2071
  fontSize: "14px",
1311
2072
  fontWeight: 600,
1312
- cursor: isSubmitting ? "not-allowed" : "pointer",
2073
+ cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
1313
2074
  fontFamily: "inherit",
1314
2075
  transition: "background-color 0.15s ease"
1315
2076
  },
@@ -1425,10 +2186,11 @@ function getTypeSubtitle(type) {
1425
2186
  return "General feedback";
1426
2187
  }
1427
2188
  }
1428
- function getPlaceholder(type) {
2189
+ function getPlaceholder(type, idx = 0, hasVoice = false) {
2190
+ if (hasVoice && idx === 1) return "Hold Space to speak \u2014 we'll transcribe it";
1429
2191
  switch (type) {
1430
2192
  case "BUG":
1431
- return "What went wrong?";
2193
+ return "What went wrong? Describe it or paste an error...";
1432
2194
  case "FEATURE_REQUEST":
1433
2195
  return "Describe the feature you'd like...";
1434
2196
  case "QUESTION":
@@ -1438,19 +2200,58 @@ function getPlaceholder(type) {
1438
2200
  }
1439
2201
  }
1440
2202
  function getTypeIcon(type, color, size = 24) {
1441
- const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 } };
2203
+ const props = {
2204
+ width: size,
2205
+ height: size,
2206
+ viewBox: "0 0 24 24",
2207
+ fill: "none",
2208
+ style: { flexShrink: 0 }
2209
+ };
1442
2210
  switch (type) {
1443
2211
  case "BUG":
1444
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2L6.5 3.5M16 2L17.5 3.5M3 9H7M17 9H21M12 2a5 5 0 015 5v4a5 5 0 01-10 0V7a5 5 0 015-5zM7 16l-2 3M17 16l2 3", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }) });
2212
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2213
+ "path",
2214
+ {
2215
+ d: "M8 2L6.5 3.5M16 2L17.5 3.5M3 9H7M17 9H21M12 2a5 5 0 015 5v4a5 5 0 01-10 0V7a5 5 0 015-5zM7 16l-2 3M17 16l2 3",
2216
+ stroke: color,
2217
+ strokeWidth: "1.5",
2218
+ strokeLinecap: "round"
2219
+ }
2220
+ ) });
1445
2221
  case "FEATURE_REQUEST":
1446
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }) });
2222
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2223
+ "path",
2224
+ {
2225
+ d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z",
2226
+ stroke: color,
2227
+ strokeWidth: "1.5",
2228
+ strokeLinejoin: "round"
2229
+ }
2230
+ ) });
1447
2231
  case "QUESTION":
1448
2232
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...props, children: [
1449
2233
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "1.5" }),
1450
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
2234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2235
+ "path",
2236
+ {
2237
+ d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01",
2238
+ stroke: color,
2239
+ strokeWidth: "1.5",
2240
+ strokeLinecap: "round",
2241
+ strokeLinejoin: "round"
2242
+ }
2243
+ )
1451
2244
  ] });
1452
2245
  case "OTHER":
1453
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }) });
2246
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2247
+ "path",
2248
+ {
2249
+ d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z",
2250
+ stroke: color,
2251
+ strokeWidth: "1.5",
2252
+ strokeLinejoin: "round"
2253
+ }
2254
+ ) });
1454
2255
  }
1455
2256
  }
1456
2257
 
@@ -1672,15 +2473,31 @@ function GlitchgrabProviderInner({
1672
2473
  []
1673
2474
  );
1674
2475
  const enhance = (0, import_react2.useCallback)(
1675
- async (text) => {
2476
+ async (text, screenshot) => {
1676
2477
  try {
1677
- return await enhanceText(text, token, baseUrl);
2478
+ const ctx = captureContext(visitedPagesRef.current);
2479
+ const context = {
2480
+ url: ctx.url,
2481
+ visitedPages: ctx.visitedPages.slice(-5),
2482
+ breadcrumbs: ctx.breadcrumbs.slice(-10).map((b) => ({ type: b.type, message: b.message }))
2483
+ };
2484
+ return await enhanceText(text, token, baseUrl, screenshot, context);
1678
2485
  } catch (e) {
1679
2486
  return text;
1680
2487
  }
1681
2488
  },
1682
2489
  [token, baseUrl]
1683
2490
  );
2491
+ const transcribe = (0, import_react2.useCallback)(
2492
+ async (blob) => {
2493
+ try {
2494
+ return await transcribeAudio(blob, token, baseUrl);
2495
+ } catch (e) {
2496
+ return "";
2497
+ }
2498
+ },
2499
+ [token, baseUrl]
2500
+ );
1684
2501
  const openReportDialog = (0, import_react2.useCallback)((options) => {
1685
2502
  if (typeof window !== "undefined") {
1686
2503
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
@@ -1725,7 +2542,7 @@ function GlitchgrabProviderInner({
1725
2542
  children
1726
2543
  }
1727
2544
  ),
1728
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, types, showSeverity })
2545
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
1729
2546
  ]
1730
2547
  }
1731
2548
  );