glitchgrab 1.22.0 → 1.23.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.
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,185 @@ 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 (e) {
900
+ if (!SpeechRec) {
901
+ setVoiceError("Microphone access denied");
902
+ return;
903
+ }
904
+ }
905
+ if (stream && transcribeAudio2) {
906
+ streamRef.current = stream;
907
+ sarvamChunksRef.current = [];
908
+ textBeforeVoiceRef.current = description;
909
+ const rec = new MediaRecorder(stream);
910
+ mediaRecorderRef.current = rec;
911
+ rec.ondataavailable = (e) => {
912
+ if (e.data.size > 0) sarvamChunksRef.current.push(e.data);
913
+ };
914
+ rec.onstop = async () => {
915
+ stream.getTracks().forEach((t2) => t2.stop());
916
+ if (streamRef.current === stream) streamRef.current = null;
917
+ mediaRecorderRef.current = null;
918
+ const chunks = sarvamChunksRef.current;
919
+ sarvamChunksRef.current = [];
920
+ if (!chunks.length || !transcribeAudio2) return;
921
+ setIsTranscribing(true);
922
+ try {
923
+ const blob = new Blob(chunks, { type: rec.mimeType || "audio/webm" });
924
+ const text = await transcribeAudio2(blob);
925
+ if (text.trim()) {
926
+ const sep = textBeforeVoiceRef.current.trim() ? " " : "";
927
+ setDescription(textBeforeVoiceRef.current + sep + text.trim());
928
+ setValidationError(null);
929
+ setIsEnhanced(false);
930
+ setOriginalDescription(null);
931
+ }
932
+ } catch (e) {
933
+ }
934
+ setIsTranscribing(false);
935
+ };
936
+ rec.start();
937
+ }
938
+ if (SpeechRec) {
939
+ setVoiceError(null);
940
+ usingWebSpeechRef.current = true;
941
+ voiceBaseRef.current = description;
942
+ textBeforeVoiceRef.current = description;
943
+ const recognition = new SpeechRec();
944
+ recognition.lang = "en-IN";
945
+ recognition.interimResults = true;
946
+ recognition.continuous = true;
947
+ recognitionRef.current = recognition;
948
+ recognition.onstart = () => setIsListening(true);
949
+ recognition.onresult = (event) => {
950
+ let finalText = "";
951
+ let interimText = "";
952
+ for (let i = event.resultIndex; i < event.results.length; i++) {
953
+ const text = event.results[i][0].transcript;
954
+ if (event.results[i].isFinal) finalText += text;
955
+ else interimText += text;
956
+ }
957
+ if (finalText) {
958
+ const sep = voiceBaseRef.current.trim() ? " " : "";
959
+ voiceBaseRef.current += sep + finalText.trim();
960
+ }
961
+ const liveText = interimText ? voiceBaseRef.current + (voiceBaseRef.current.trim() ? " " : "") + interimText : voiceBaseRef.current;
962
+ setDescription(liveText);
963
+ setValidationError(null);
964
+ };
965
+ recognition.onend = () => {
966
+ if (usingWebSpeechRef.current && recognitionRef.current === recognition) {
967
+ try {
968
+ recognition.start();
969
+ } catch (e) {
970
+ }
971
+ } else {
972
+ setIsListening(false);
973
+ }
974
+ };
975
+ recognition.onerror = (event) => {
976
+ if (event.error === "aborted" || event.error === "no-speech") return;
977
+ setVoiceError("Speech recognition not available \u2014 try Chrome");
978
+ usingWebSpeechRef.current = false;
979
+ recognitionRef.current = null;
980
+ setIsListening(false);
981
+ };
982
+ recognition.start();
983
+ } else if (stream) {
984
+ setVoiceError(null);
985
+ setIsListening(true);
986
+ }
987
+ };
988
+ const handleSpaceDown = (e) => {
989
+ if (e.code !== "Space") return;
990
+ if (spaceTimerRef.current || isListening || isPushToTalkRef.current) {
991
+ e.preventDefault();
992
+ return;
993
+ }
994
+ if (e.repeat || !transcribeAudio2 || isTranscribing) return;
995
+ e.preventDefault();
996
+ spaceTimerRef.current = setTimeout(() => {
997
+ isPushToTalkRef.current = true;
998
+ void toggleVoice();
999
+ }, 400);
1000
+ };
1001
+ const handleSpaceUp = (e) => {
1002
+ var _a;
1003
+ if (e.code !== "Space") return;
1004
+ if (spaceTimerRef.current) {
1005
+ clearTimeout(spaceTimerRef.current);
1006
+ spaceTimerRef.current = null;
1007
+ if (!isPushToTalkRef.current) {
1008
+ const ta = textareaRef.current;
1009
+ const pos = ta ? (_a = ta.selectionStart) != null ? _a : description.length : description.length;
1010
+ setDescription((prev) => prev.slice(0, pos) + " " + prev.slice(pos));
1011
+ requestAnimationFrame(() => {
1012
+ if (textareaRef.current) {
1013
+ textareaRef.current.selectionStart = pos + 1;
1014
+ textareaRef.current.selectionEnd = pos + 1;
1015
+ }
1016
+ });
1017
+ }
1018
+ }
1019
+ if (isPushToTalkRef.current) {
1020
+ isPushToTalkRef.current = false;
1021
+ stopListeningAndTranscribe();
1022
+ }
753
1023
  };
754
1024
  const handleSubmit = async () => {
755
1025
  try {
@@ -766,7 +1036,11 @@ function ReportDialog({
766
1036
  if (showSeverity && reportType === "BUG") {
767
1037
  metadata.severity = severity;
768
1038
  }
769
- const result = await report(reportType, description.trim(), Object.keys(metadata).length > 0 ? metadata : void 0);
1039
+ const result = await report(
1040
+ reportType,
1041
+ description.trim(),
1042
+ Object.keys(metadata).length > 0 ? metadata : void 0
1043
+ );
770
1044
  if (result) {
771
1045
  setSubmitted(true);
772
1046
  setDescription("");
@@ -783,6 +1057,16 @@ function ReportDialog({
783
1057
  };
784
1058
  if (!mounted) return null;
785
1059
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1060
+ isOpen && (0, import_react_dom.createPortal)(
1061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1062
+ @keyframes gg-pulse{0%,100%{opacity:1}50%{opacity:0.35}}
1063
+ @keyframes gg-b1{0%,100%{height:3px}40%{height:13px}}
1064
+ @keyframes gg-b2{0%,100%{height:8px}50%{height:3px}}
1065
+ @keyframes gg-b3{0%,100%{height:4px}30%{height:14px}70%{height:5px}}
1066
+ @keyframes gg-b4{0%,100%{height:6px}60%{height:13px}}
1067
+ ` }),
1068
+ document.body
1069
+ ),
786
1070
  isOpen && (0, import_react_dom.createPortal)(
787
1071
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
788
1072
  "div",
@@ -839,36 +1123,64 @@ function ReportDialog({
839
1123
  alignItems: "center"
840
1124
  },
841
1125
  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
- ] }),
1126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1127
+ "div",
1128
+ {
1129
+ style: {
1130
+ display: "flex",
1131
+ alignItems: "center",
1132
+ gap: "8px"
1133
+ },
1134
+ children: [
1135
+ step > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1136
+ "button",
1137
+ {
1138
+ type: "button",
1139
+ onClick: () => setStep((s) => s - 1),
1140
+ style: {
1141
+ background: "none",
1142
+ border: "none",
1143
+ cursor: "pointer",
1144
+ padding: "2px",
1145
+ display: "flex",
1146
+ alignItems: "center"
1147
+ },
1148
+ "aria-label": "Back",
1149
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1150
+ "svg",
1151
+ {
1152
+ width: "16",
1153
+ height: "16",
1154
+ viewBox: "0 0 16 16",
1155
+ fill: "none",
1156
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1157
+ "path",
1158
+ {
1159
+ d: "M10 3L5 8L10 13",
1160
+ stroke: t.textMuted,
1161
+ strokeWidth: "2",
1162
+ strokeLinecap: "round",
1163
+ strokeLinejoin: "round"
1164
+ }
1165
+ )
1166
+ }
1167
+ )
1168
+ }
1169
+ ),
1170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1171
+ "span",
1172
+ {
1173
+ style: {
1174
+ fontSize: "15px",
1175
+ fontWeight: 600,
1176
+ color: t.text
1177
+ },
1178
+ children: step === 1 ? "What's on your mind?" : "Tell us more"
1179
+ }
1180
+ )
1181
+ ]
1182
+ }
1183
+ ),
872
1184
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
873
1185
  "button",
874
1186
  {
@@ -884,27 +1196,74 @@ function ReportDialog({
884
1196
  justifyContent: "center"
885
1197
  },
886
1198
  "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" }) })
1199
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1200
+ "svg",
1201
+ {
1202
+ width: "16",
1203
+ height: "16",
1204
+ viewBox: "0 0 16 16",
1205
+ fill: "none",
1206
+ style: { display: "block" },
1207
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1208
+ "path",
1209
+ {
1210
+ d: "M4 4L12 12M12 4L4 12",
1211
+ stroke: t.textMuted,
1212
+ strokeWidth: "2",
1213
+ strokeLinecap: "round"
1214
+ }
1215
+ )
1216
+ }
1217
+ )
888
1218
  }
889
1219
  )
890
1220
  ]
891
1221
  }
892
1222
  ),
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)) })
1223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1224
+ "div",
1225
+ {
1226
+ style: {
1227
+ display: "flex",
1228
+ alignItems: "center",
1229
+ justifyContent: "center",
1230
+ gap: "0",
1231
+ marginTop: "12px"
1232
+ },
1233
+ children: [1, 2].map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1234
+ "div",
1235
+ {
1236
+ style: { display: "flex", alignItems: "center" },
1237
+ children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1239
+ "div",
1240
+ {
1241
+ style: {
1242
+ width: "8px",
1243
+ height: "8px",
1244
+ borderRadius: "50%",
1245
+ backgroundColor: s <= step ? t.accent : t.inputBorder,
1246
+ transition: "background-color 0.2s ease"
1247
+ }
1248
+ }
1249
+ ),
1250
+ i < 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1251
+ "div",
1252
+ {
1253
+ style: {
1254
+ width: "40px",
1255
+ height: "2px",
1256
+ backgroundColor: s < step ? t.accent : t.inputBorder,
1257
+ transition: "background-color 0.2s ease"
1258
+ }
1259
+ }
1260
+ )
1261
+ ]
1262
+ },
1263
+ s
1264
+ ))
1265
+ }
1266
+ )
908
1267
  ]
909
1268
  }
910
1269
  ),
@@ -924,76 +1283,185 @@ function ReportDialog({
924
1283
  ]
925
1284
  }
926
1285
  ) : /* @__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",
1286
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1287
+ "div",
929
1288
  {
930
- type: "button",
931
- onClick: () => {
932
- setReportType(type);
933
- setStep(2);
934
- },
935
1289
  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;
1290
+ display: "grid",
1291
+ gridTemplateColumns: "1fr 1fr",
1292
+ gap: "8px"
954
1293
  },
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
- )) }),
1294
+ children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1295
+ "button",
1296
+ {
1297
+ type: "button",
1298
+ onClick: () => {
1299
+ setReportType(type);
1300
+ setStep(2);
1301
+ },
1302
+ style: {
1303
+ display: "flex",
1304
+ flexDirection: "column",
1305
+ alignItems: "center",
1306
+ gap: "8px",
1307
+ padding: "16px 8px",
1308
+ borderRadius: "8px",
1309
+ border: `1px solid ${t.inputBorder}`,
1310
+ background: t.inputBg,
1311
+ cursor: "pointer",
1312
+ color: t.text,
1313
+ fontFamily: "inherit",
1314
+ transition: "border-color 0.15s ease"
1315
+ },
1316
+ onMouseEnter: (e) => {
1317
+ e.currentTarget.style.borderColor = t.accent;
1318
+ },
1319
+ onMouseLeave: (e) => {
1320
+ e.currentTarget.style.borderColor = t.inputBorder;
1321
+ },
1322
+ children: [
1323
+ getTypeIcon(type, t.accent),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1326
+ "span",
1327
+ {
1328
+ style: {
1329
+ fontSize: "11px",
1330
+ color: t.textMuted,
1331
+ lineHeight: "1.3"
1332
+ },
1333
+ children: getTypeSubtitle(type)
1334
+ }
1335
+ )
1336
+ ]
1337
+ },
1338
+ type
1339
+ ))
1340
+ }
1341
+ ),
963
1342
  step === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
964
1343
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
965
1344
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
966
1345
  "textarea",
967
1346
  {
1347
+ ref: textareaRef,
968
1348
  value: description,
969
1349
  onChange: (e) => {
970
1350
  setDescription(e.target.value);
971
1351
  if (validationError) setValidationError(null);
1352
+ if (isEnhanced) {
1353
+ setIsEnhanced(false);
1354
+ setOriginalDescription(null);
1355
+ }
972
1356
  },
973
- placeholder: getPlaceholder(reportType),
1357
+ placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2),
974
1358
  style: {
975
1359
  width: "100%",
976
1360
  minHeight: "100px",
977
- padding: enhanceText2 ? "28px 12px 10px" : "10px 12px",
1361
+ padding: enhanceText2 ? "28px 12px 36px" : transcribeAudio2 ? "10px 12px 36px" : "10px 12px",
978
1362
  borderRadius: "8px",
979
- border: `1px solid ${t.inputBorder}`,
1363
+ border: `1px solid ${isTranscribing ? "#f59e0b" : isListening ? t.accent : t.inputBorder}`,
980
1364
  fontSize: "14px",
981
1365
  fontFamily: "inherit",
982
- resize: "vertical",
1366
+ resize: "none",
983
1367
  outline: "none",
984
1368
  boxSizing: "border-box",
985
1369
  color: t.text,
986
- backgroundColor: t.inputBg
1370
+ backgroundColor: t.inputBg,
1371
+ transition: "border-color 0.2s ease"
987
1372
  },
1373
+ onKeyDown: handleSpaceDown,
1374
+ onKeyUp: handleSpaceUp,
988
1375
  onFocus: (e) => {
989
1376
  e.target.style.borderColor = t.accent;
990
1377
  },
991
1378
  onBlur: (e) => {
992
- e.target.style.borderColor = t.inputBorder;
1379
+ if (!isListening && !isTranscribing)
1380
+ e.target.style.borderColor = t.inputBorder;
993
1381
  },
994
1382
  autoFocus: true
995
1383
  }
996
1384
  ),
1385
+ (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1386
+ "span",
1387
+ {
1388
+ style: {
1389
+ position: "absolute",
1390
+ bottom: "10px",
1391
+ left: "10px",
1392
+ display: "inline-flex",
1393
+ alignItems: "center",
1394
+ gap: "4px",
1395
+ fontSize: "10px",
1396
+ fontWeight: 700,
1397
+ letterSpacing: "0.04em",
1398
+ color: isTranscribing ? "#f59e0b" : "#ef4444",
1399
+ pointerEvents: "none",
1400
+ zIndex: 1
1401
+ },
1402
+ children: [
1403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1404
+ "span",
1405
+ {
1406
+ style: {
1407
+ width: "6px",
1408
+ height: "6px",
1409
+ borderRadius: "50%",
1410
+ backgroundColor: "currentColor",
1411
+ animation: "gg-pulse 1.2s ease-in-out infinite",
1412
+ display: "inline-block",
1413
+ flexShrink: 0
1414
+ }
1415
+ }
1416
+ ),
1417
+ isTranscribing ? "Transcribing\u2026" : "REC",
1418
+ isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1419
+ "span",
1420
+ {
1421
+ style: {
1422
+ display: "inline-flex",
1423
+ alignItems: "flex-end",
1424
+ gap: "2px",
1425
+ height: "14px",
1426
+ marginLeft: "3px"
1427
+ },
1428
+ children: [
1429
+ { anim: "gg-b1", dur: "0.6s", delay: "0s" },
1430
+ {
1431
+ anim: "gg-b2",
1432
+ dur: "0.5s",
1433
+ delay: "0.1s"
1434
+ },
1435
+ {
1436
+ anim: "gg-b3",
1437
+ dur: "0.7s",
1438
+ delay: "0.05s"
1439
+ },
1440
+ {
1441
+ anim: "gg-b4",
1442
+ dur: "0.55s",
1443
+ delay: "0.15s"
1444
+ }
1445
+ ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1446
+ "span",
1447
+ {
1448
+ style: {
1449
+ width: "2.5px",
1450
+ height: "4px",
1451
+ borderRadius: "1px",
1452
+ backgroundColor: "#ef4444",
1453
+ animation: `${b.anim} ${b.dur} ease-in-out ${b.delay} infinite`,
1454
+ display: "inline-block",
1455
+ alignSelf: "center"
1456
+ }
1457
+ },
1458
+ i
1459
+ ))
1460
+ }
1461
+ )
1462
+ ]
1463
+ }
1464
+ ),
997
1465
  enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
998
1466
  "button",
999
1467
  {
@@ -1002,16 +1470,22 @@ function ReportDialog({
1002
1470
  if (!description.trim() || isEnhancing) return;
1003
1471
  setIsEnhancing(true);
1004
1472
  try {
1005
- const polished = await enhanceText2(description);
1473
+ const polished = await enhanceText2(
1474
+ description,
1475
+ screenshot
1476
+ );
1006
1477
  if (polished && polished !== description) {
1478
+ setOriginalDescription(description);
1007
1479
  setDescription(polished);
1008
- if (validationError) setValidationError(null);
1480
+ setIsEnhanced(true);
1481
+ if (validationError)
1482
+ setValidationError(null);
1009
1483
  }
1010
1484
  } finally {
1011
1485
  setIsEnhancing(false);
1012
1486
  }
1013
1487
  },
1014
- disabled: !description.trim() || isEnhancing,
1488
+ disabled: !description.trim() || isEnhancing || isEnhanced,
1015
1489
  style: {
1016
1490
  position: "absolute",
1017
1491
  top: "6px",
@@ -1032,105 +1506,393 @@ function ReportDialog({
1032
1506
  },
1033
1507
  title: "Polish grammar \u2014 preserves your meaning",
1034
1508
  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" }) }),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1510
+ "svg",
1511
+ {
1512
+ width: "11",
1513
+ height: "11",
1514
+ viewBox: "0 0 24 24",
1515
+ fill: "none",
1516
+ "aria-hidden": "true",
1517
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1518
+ "path",
1519
+ {
1520
+ 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",
1521
+ stroke: t.textMuted,
1522
+ strokeWidth: "1.5",
1523
+ strokeLinejoin: "round"
1524
+ }
1525
+ )
1526
+ }
1527
+ ),
1036
1528
  isEnhancing ? "Enhancing..." : "AI enhance"
1037
1529
  ]
1038
1530
  }
1531
+ ),
1532
+ transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1533
+ "button",
1534
+ {
1535
+ type: "button",
1536
+ onClick: () => {
1537
+ void toggleVoice();
1538
+ },
1539
+ title: isListening ? "Stop recording" : "Speak your report",
1540
+ style: {
1541
+ position: "absolute",
1542
+ bottom: "14px",
1543
+ right: "7px",
1544
+ width: "26px",
1545
+ height: "26px",
1546
+ display: "flex",
1547
+ alignItems: "center",
1548
+ justifyContent: "center",
1549
+ background: "transparent",
1550
+ border: `1.5px solid ${isListening ? "#ef4444" : t.inputBorder}`,
1551
+ borderRadius: "6px",
1552
+ color: isListening ? "#ef4444" : t.textMuted,
1553
+ cursor: "pointer",
1554
+ zIndex: 1,
1555
+ padding: 0,
1556
+ transition: "border-color 0.2s ease, color 0.2s ease"
1557
+ },
1558
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1559
+ "svg",
1560
+ {
1561
+ width: "12",
1562
+ height: "12",
1563
+ viewBox: "0 0 24 24",
1564
+ fill: "currentColor",
1565
+ "aria-hidden": "true",
1566
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1567
+ "rect",
1568
+ {
1569
+ x: "6",
1570
+ y: "6",
1571
+ width: "12",
1572
+ height: "12",
1573
+ rx: "2"
1574
+ }
1575
+ )
1576
+ }
1577
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1578
+ "svg",
1579
+ {
1580
+ width: "13",
1581
+ height: "13",
1582
+ viewBox: "0 0 24 24",
1583
+ fill: "none",
1584
+ "aria-hidden": "true",
1585
+ children: [
1586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1587
+ "rect",
1588
+ {
1589
+ x: "9",
1590
+ y: "2",
1591
+ width: "6",
1592
+ height: "12",
1593
+ rx: "3",
1594
+ stroke: "currentColor",
1595
+ strokeWidth: "1.8"
1596
+ }
1597
+ ),
1598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1599
+ "path",
1600
+ {
1601
+ d: "M5 10a7 7 0 0014 0M12 19v3M9 22h6",
1602
+ stroke: "currentColor",
1603
+ strokeWidth: "1.8",
1604
+ strokeLinecap: "round"
1605
+ }
1606
+ )
1607
+ ]
1608
+ }
1609
+ )
1610
+ }
1039
1611
  )
1040
1612
  ] }),
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: [
1613
+ transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1614
+ "div",
1615
+ {
1616
+ style: {
1617
+ display: "flex",
1618
+ alignItems: "center",
1619
+ gap: "5px",
1620
+ marginTop: "5px",
1621
+ fontSize: "11px",
1622
+ color: t.text,
1623
+ opacity: 0.55,
1624
+ userSelect: "none"
1625
+ },
1626
+ children: [
1627
+ /* @__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: [
1628
+ /* @__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" }),
1629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1631
+ ] }),
1632
+ "Hold Space to speak \u2014 we'll transcribe it"
1633
+ ]
1634
+ }
1635
+ ),
1636
+ isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1637
+ "div",
1638
+ {
1639
+ style: {
1640
+ display: "flex",
1641
+ alignItems: "center",
1642
+ gap: "6px",
1643
+ marginTop: "6px",
1644
+ padding: "6px 8px",
1645
+ borderRadius: "6px",
1646
+ background: isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.05)",
1647
+ border: `1px solid ${t.accent}40`
1648
+ },
1649
+ children: [
1045
1650
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1046
- "img",
1651
+ "svg",
1652
+ {
1653
+ width: "12",
1654
+ height: "12",
1655
+ viewBox: "0 0 24 24",
1656
+ fill: "none",
1657
+ style: { flexShrink: 0 },
1658
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1659
+ "path",
1660
+ {
1661
+ d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z",
1662
+ stroke: t.accent,
1663
+ strokeWidth: "1.5",
1664
+ strokeLinejoin: "round"
1665
+ }
1666
+ )
1667
+ }
1668
+ ),
1669
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1670
+ "span",
1047
1671
  {
1048
- src: screenshot,
1049
- alt: "Page screenshot",
1050
- onClick: () => setPreviewOpen(true),
1051
1672
  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
- }
1673
+ fontSize: "11px",
1674
+ color: t.textMuted,
1675
+ flex: 1
1676
+ },
1677
+ children: "AI enhanced"
1061
1678
  }
1062
1679
  ),
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)(
1680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1077
1681
  "button",
1078
1682
  {
1079
1683
  type: "button",
1080
- onClick: () => setScreenshot(null),
1684
+ onClick: () => {
1685
+ setIsEnhanced(false);
1686
+ setOriginalDescription(null);
1687
+ },
1081
1688
  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,
1689
+ padding: "2px 8px",
1690
+ borderRadius: "4px",
1691
+ border: `1px solid ${t.accent}`,
1692
+ background: t.accent,
1693
+ color: t.accentText,
1694
+ fontSize: "11px",
1695
+ fontWeight: 600,
1090
1696
  cursor: "pointer",
1091
- fontFamily: "inherit",
1092
- display: "flex",
1093
- alignItems: "center",
1094
- justifyContent: "center",
1095
- gap: "4px"
1697
+ fontFamily: "inherit"
1096
1698
  },
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
- ]
1699
+ children: "Keep"
1101
1700
  }
1102
1701
  ),
1103
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1104
1703
  "button",
1105
1704
  {
1106
1705
  type: "button",
1107
1706
  onClick: () => {
1108
- var _a;
1109
- return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1707
+ setDescription(originalDescription);
1708
+ setIsEnhanced(false);
1709
+ setOriginalDescription(null);
1110
1710
  },
1111
1711
  style: {
1112
- flex: 1,
1113
- padding: "6px 0",
1114
- borderRadius: "6px",
1712
+ padding: "2px 8px",
1713
+ borderRadius: "4px",
1115
1714
  border: `1px solid ${t.inputBorder}`,
1116
1715
  background: "transparent",
1117
1716
  color: t.textMuted,
1118
- fontSize: "12px",
1119
- fontWeight: 500,
1717
+ fontSize: "11px",
1718
+ fontWeight: 600,
1120
1719
  cursor: "pointer",
1121
- fontFamily: "inherit",
1122
- display: "flex",
1123
- alignItems: "center",
1124
- justifyContent: "center",
1125
- gap: "4px"
1720
+ fontFamily: "inherit"
1126
1721
  },
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
- ]
1722
+ children: "Restore"
1723
+ }
1724
+ )
1725
+ ]
1726
+ }
1727
+ ),
1728
+ voiceError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1729
+ "p",
1730
+ {
1731
+ style: {
1732
+ color: "#ef4444",
1733
+ fontSize: "11px",
1734
+ marginTop: "4px",
1735
+ marginBottom: 0
1736
+ },
1737
+ children: voiceError
1738
+ }
1739
+ ),
1740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1742
+ "span",
1743
+ {
1744
+ style: {
1745
+ fontSize: "12px",
1746
+ color: t.textMuted,
1747
+ marginBottom: "6px",
1748
+ display: "block",
1749
+ fontWeight: 500
1750
+ },
1751
+ children: "Screenshot"
1752
+ }
1753
+ ),
1754
+ screenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
1756
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1757
+ "img",
1758
+ {
1759
+ src: screenshot,
1760
+ alt: "Page screenshot",
1761
+ onClick: () => setPreviewOpen(true),
1762
+ style: {
1763
+ width: "100%",
1764
+ borderRadius: "6px",
1765
+ border: `1px solid ${t.border}`,
1766
+ maxHeight: "64px",
1767
+ objectFit: "cover",
1768
+ objectPosition: "top",
1769
+ cursor: "zoom-in",
1770
+ display: "block"
1771
+ }
1772
+ }
1773
+ ),
1774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1775
+ "span",
1776
+ {
1777
+ style: {
1778
+ position: "absolute",
1779
+ bottom: "5px",
1780
+ left: "6px",
1781
+ background: "rgba(0,0,0,0.55)",
1782
+ color: "#fff",
1783
+ fontSize: "10px",
1784
+ padding: "1px 5px",
1785
+ borderRadius: "3px",
1786
+ pointerEvents: "none"
1787
+ },
1788
+ children: "click to preview"
1131
1789
  }
1132
1790
  )
1133
- ] })
1791
+ ] }),
1792
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1793
+ "div",
1794
+ {
1795
+ style: {
1796
+ display: "flex",
1797
+ gap: "6px",
1798
+ marginTop: "6px"
1799
+ },
1800
+ children: [
1801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1802
+ "button",
1803
+ {
1804
+ type: "button",
1805
+ onClick: () => setScreenshot(null),
1806
+ style: {
1807
+ flex: 1,
1808
+ padding: "6px 0",
1809
+ borderRadius: "6px",
1810
+ border: `1px solid ${t.inputBorder}`,
1811
+ background: "transparent",
1812
+ color: "#ef4444",
1813
+ fontSize: "12px",
1814
+ fontWeight: 500,
1815
+ cursor: "pointer",
1816
+ fontFamily: "inherit",
1817
+ display: "flex",
1818
+ alignItems: "center",
1819
+ justifyContent: "center",
1820
+ gap: "4px"
1821
+ },
1822
+ children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1824
+ "svg",
1825
+ {
1826
+ width: "11",
1827
+ height: "11",
1828
+ viewBox: "0 0 10 10",
1829
+ fill: "none",
1830
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1831
+ "path",
1832
+ {
1833
+ d: "M1 1L9 9M9 1L1 9",
1834
+ stroke: "currentColor",
1835
+ strokeWidth: "1.5",
1836
+ strokeLinecap: "round"
1837
+ }
1838
+ )
1839
+ }
1840
+ ),
1841
+ "Remove"
1842
+ ]
1843
+ }
1844
+ ),
1845
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1846
+ "button",
1847
+ {
1848
+ type: "button",
1849
+ onClick: () => {
1850
+ var _a;
1851
+ return (_a = fileInputRef.current) == null ? void 0 : _a.click();
1852
+ },
1853
+ style: {
1854
+ flex: 1,
1855
+ padding: "6px 0",
1856
+ borderRadius: "6px",
1857
+ border: `1px solid ${t.inputBorder}`,
1858
+ background: "transparent",
1859
+ color: t.textMuted,
1860
+ fontSize: "12px",
1861
+ fontWeight: 500,
1862
+ cursor: "pointer",
1863
+ fontFamily: "inherit",
1864
+ display: "flex",
1865
+ alignItems: "center",
1866
+ justifyContent: "center",
1867
+ gap: "4px"
1868
+ },
1869
+ children: [
1870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1871
+ "svg",
1872
+ {
1873
+ width: "11",
1874
+ height: "11",
1875
+ viewBox: "0 0 24 24",
1876
+ fill: "none",
1877
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1878
+ "path",
1879
+ {
1880
+ d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12",
1881
+ stroke: "currentColor",
1882
+ strokeWidth: "2",
1883
+ strokeLinecap: "round",
1884
+ strokeLinejoin: "round"
1885
+ }
1886
+ )
1887
+ }
1888
+ ),
1889
+ "Replace"
1890
+ ]
1891
+ }
1892
+ )
1893
+ ]
1894
+ }
1895
+ )
1134
1896
  ] }) : (
1135
1897
  /* Drop zone */
1136
1898
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1143,7 +1905,9 @@ function ReportDialog({
1143
1905
  onDrop: (e) => {
1144
1906
  e.preventDefault();
1145
1907
  setScreenshotDragOver(false);
1146
- const file = Array.from(e.dataTransfer.files).find((f) => f.type.startsWith("image/"));
1908
+ const file = Array.from(
1909
+ e.dataTransfer.files
1910
+ ).find((f) => f.type.startsWith("image/"));
1147
1911
  if (!file) return;
1148
1912
  const reader = new FileReader();
1149
1913
  reader.onload = () => setScreenshot(reader.result);
@@ -1164,20 +1928,95 @@ function ReportDialog({
1164
1928
  transition: "border-color 0.15s ease, background 0.15s ease"
1165
1929
  },
1166
1930
  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" })
1931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1932
+ "svg",
1933
+ {
1934
+ width: "22",
1935
+ height: "22",
1936
+ viewBox: "0 0 24 24",
1937
+ fill: "none",
1938
+ style: {
1939
+ margin: "0 auto 6px",
1940
+ display: "block",
1941
+ opacity: 0.45
1942
+ },
1943
+ children: [
1944
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1945
+ "rect",
1946
+ {
1947
+ x: "3",
1948
+ y: "3",
1949
+ width: "18",
1950
+ height: "18",
1951
+ rx: "2",
1952
+ stroke: t.textMuted,
1953
+ strokeWidth: "1.5"
1954
+ }
1955
+ ),
1956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1957
+ "circle",
1958
+ {
1959
+ cx: "8.5",
1960
+ cy: "8.5",
1961
+ r: "1.5",
1962
+ stroke: t.textMuted,
1963
+ strokeWidth: "1.5"
1964
+ }
1965
+ ),
1966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1967
+ "path",
1968
+ {
1969
+ d: "M21 15l-5-5L5 21",
1970
+ stroke: t.textMuted,
1971
+ strokeWidth: "1.5",
1972
+ strokeLinecap: "round",
1973
+ strokeLinejoin: "round"
1974
+ }
1975
+ )
1976
+ ]
1977
+ }
1978
+ ),
1979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1980
+ "p",
1981
+ {
1982
+ style: {
1983
+ margin: 0,
1984
+ fontSize: "12px",
1985
+ fontWeight: 600,
1986
+ color: t.text
1987
+ },
1988
+ children: screenshotDragOver ? "Drop to attach" : "Add a screenshot"
1989
+ }
1990
+ ),
1991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1992
+ "p",
1993
+ {
1994
+ style: {
1995
+ margin: "3px 0 0",
1996
+ fontSize: "11px",
1997
+ color: t.textMuted
1998
+ },
1999
+ children: "Drag & drop \xB7 Paste \u2318V / Ctrl+V \xB7 Click to browse"
2000
+ }
2001
+ )
1174
2002
  ]
1175
2003
  }
1176
2004
  )
1177
2005
  )
1178
2006
  ] }),
1179
2007
  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" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2009
+ "span",
2010
+ {
2011
+ style: {
2012
+ fontSize: "12px",
2013
+ color: t.textMuted,
2014
+ marginBottom: "6px",
2015
+ display: "block"
2016
+ },
2017
+ children: "Severity"
2018
+ }
2019
+ ),
1181
2020
  /* @__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
2021
  "button",
1183
2022
  {
@@ -1202,114 +2041,34 @@ function ReportDialog({
1202
2041
  s
1203
2042
  )) })
1204
2043
  ] }),
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",
2044
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2045
+ "p",
1208
2046
  {
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
2047
  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"
2048
+ color: "#ef4444",
2049
+ fontSize: "12px",
2050
+ marginTop: "6px"
1233
2051
  },
1234
- children: "Next"
2052
+ children: validationError
1235
2053
  }
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
- ) }),
2054
+ ),
1297
2055
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1298
2056
  "button",
1299
2057
  {
1300
2058
  type: "button",
1301
2059
  onClick: handleSubmit,
1302
- disabled: isSubmitting,
2060
+ disabled: !description.trim() || isSubmitting,
1303
2061
  style: {
2062
+ marginTop: "12px",
1304
2063
  width: "100%",
1305
2064
  padding: "10px",
1306
2065
  borderRadius: "8px",
1307
2066
  border: "none",
1308
- backgroundColor: isSubmitting ? t.bgSecondary : t.accent,
1309
- color: isSubmitting ? t.textMuted : t.accentText,
2067
+ backgroundColor: !description.trim() || isSubmitting ? t.bgSecondary : t.accent,
2068
+ color: !description.trim() || isSubmitting ? t.textMuted : t.accentText,
1310
2069
  fontSize: "14px",
1311
2070
  fontWeight: 600,
1312
- cursor: isSubmitting ? "not-allowed" : "pointer",
2071
+ cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
1313
2072
  fontFamily: "inherit",
1314
2073
  transition: "background-color 0.15s ease"
1315
2074
  },
@@ -1425,10 +2184,11 @@ function getTypeSubtitle(type) {
1425
2184
  return "General feedback";
1426
2185
  }
1427
2186
  }
1428
- function getPlaceholder(type) {
2187
+ function getPlaceholder(type, idx = 0, hasVoice = false) {
2188
+ if (hasVoice && idx === 1) return "Hold Space to speak \u2014 we'll transcribe it";
1429
2189
  switch (type) {
1430
2190
  case "BUG":
1431
- return "What went wrong?";
2191
+ return "What went wrong? Describe it or paste an error...";
1432
2192
  case "FEATURE_REQUEST":
1433
2193
  return "Describe the feature you'd like...";
1434
2194
  case "QUESTION":
@@ -1438,19 +2198,58 @@ function getPlaceholder(type) {
1438
2198
  }
1439
2199
  }
1440
2200
  function getTypeIcon(type, color, size = 24) {
1441
- const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 } };
2201
+ const props = {
2202
+ width: size,
2203
+ height: size,
2204
+ viewBox: "0 0 24 24",
2205
+ fill: "none",
2206
+ style: { flexShrink: 0 }
2207
+ };
1442
2208
  switch (type) {
1443
2209
  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" }) });
2210
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2211
+ "path",
2212
+ {
2213
+ 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",
2214
+ stroke: color,
2215
+ strokeWidth: "1.5",
2216
+ strokeLinecap: "round"
2217
+ }
2218
+ ) });
1445
2219
  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" }) });
2220
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2221
+ "path",
2222
+ {
2223
+ 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",
2224
+ stroke: color,
2225
+ strokeWidth: "1.5",
2226
+ strokeLinejoin: "round"
2227
+ }
2228
+ ) });
1447
2229
  case "QUESTION":
1448
2230
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...props, children: [
1449
2231
  /* @__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" })
2232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2233
+ "path",
2234
+ {
2235
+ d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01",
2236
+ stroke: color,
2237
+ strokeWidth: "1.5",
2238
+ strokeLinecap: "round",
2239
+ strokeLinejoin: "round"
2240
+ }
2241
+ )
1451
2242
  ] });
1452
2243
  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" }) });
2244
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2245
+ "path",
2246
+ {
2247
+ d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z",
2248
+ stroke: color,
2249
+ strokeWidth: "1.5",
2250
+ strokeLinejoin: "round"
2251
+ }
2252
+ ) });
1454
2253
  }
1455
2254
  }
1456
2255
 
@@ -1672,15 +2471,31 @@ function GlitchgrabProviderInner({
1672
2471
  []
1673
2472
  );
1674
2473
  const enhance = (0, import_react2.useCallback)(
1675
- async (text) => {
2474
+ async (text, screenshot) => {
1676
2475
  try {
1677
- return await enhanceText(text, token, baseUrl);
2476
+ const ctx = captureContext(visitedPagesRef.current);
2477
+ const context = {
2478
+ url: ctx.url,
2479
+ visitedPages: ctx.visitedPages.slice(-5),
2480
+ breadcrumbs: ctx.breadcrumbs.slice(-10).map((b) => ({ type: b.type, message: b.message }))
2481
+ };
2482
+ return await enhanceText(text, token, baseUrl, screenshot, context);
1678
2483
  } catch (e) {
1679
2484
  return text;
1680
2485
  }
1681
2486
  },
1682
2487
  [token, baseUrl]
1683
2488
  );
2489
+ const transcribe = (0, import_react2.useCallback)(
2490
+ async (blob) => {
2491
+ try {
2492
+ return await transcribeAudio(blob, token, baseUrl);
2493
+ } catch (e) {
2494
+ return "";
2495
+ }
2496
+ },
2497
+ [token, baseUrl]
2498
+ );
1684
2499
  const openReportDialog = (0, import_react2.useCallback)((options) => {
1685
2500
  if (typeof window !== "undefined") {
1686
2501
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
@@ -1725,7 +2540,7 @@ function GlitchgrabProviderInner({
1725
2540
  children
1726
2541
  }
1727
2542
  ),
1728
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, types, showSeverity })
2543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
1729
2544
  ]
1730
2545
  }
1731
2546
  );