bshepp commited on
Commit
06e876f
·
1 Parent(s): 22718c3

Live progress indicators + feedback widget

Browse files

- Backend: yield running state before each step so frontend sees real-time transitions
- AgentPipeline: live elapsed timer, spinning indicator, contextual activity messages
- FeedbackWidget: floating button -> message + optional contact form
- Backend: POST /api/feedback endpoint (JSONL storage)
- Pipeline running view: better messaging about expected duration

src/backend/app/agent/orchestrator.py CHANGED
@@ -137,6 +137,7 @@ class Orchestrator:
137
 
138
  try:
139
  # ── Step 1: Parse patient data ──
 
140
  step = await self._run_step("parse", self._step_parse, case.patient_text)
141
  yield step
142
 
@@ -148,6 +149,7 @@ class Orchestrator:
148
  return
149
 
150
  # ── Step 2: Clinical reasoning ──
 
151
  step = await self._run_step("reason", self._step_reason)
152
  yield step
153
 
@@ -160,8 +162,10 @@ class Orchestrator:
160
  # ── Step 3 & 4: Drug check + Guidelines (parallel) ──
161
  parallel_tasks = []
162
  if case.include_drug_check:
 
163
  parallel_tasks.append(("drugs", self._step_drug_check))
164
  if case.include_guidelines:
 
165
  parallel_tasks.append(("guidelines", self._step_guidelines))
166
 
167
  if parallel_tasks:
@@ -178,9 +182,11 @@ class Orchestrator:
178
 
179
  # ── Step 5: Conflict Detection ──
180
  if case.include_guidelines:
 
181
  yield await self._run_step("conflicts", self._step_conflict_detection)
182
 
183
  # ── Step 6: Synthesis ──
 
184
  yield await self._run_step("synthesize", self._step_synthesize)
185
 
186
  self._state.completed_at = datetime.utcnow()
@@ -207,6 +213,12 @@ class Orchestrator:
207
  skipped.append(step)
208
  return skipped
209
 
 
 
 
 
 
 
210
  async def _run_step(self, step_id: str, fn, *args) -> AgentStep:
211
  """Execute a single step, tracking status and timing."""
212
  step = self._get_step(step_id)
 
137
 
138
  try:
139
  # ── Step 1: Parse patient data ──
140
+ yield self._mark_running("parse")
141
  step = await self._run_step("parse", self._step_parse, case.patient_text)
142
  yield step
143
 
 
149
  return
150
 
151
  # ── Step 2: Clinical reasoning ──
152
+ yield self._mark_running("reason")
153
  step = await self._run_step("reason", self._step_reason)
154
  yield step
155
 
 
162
  # ── Step 3 & 4: Drug check + Guidelines (parallel) ──
163
  parallel_tasks = []
164
  if case.include_drug_check:
165
+ yield self._mark_running("drugs")
166
  parallel_tasks.append(("drugs", self._step_drug_check))
167
  if case.include_guidelines:
168
+ yield self._mark_running("guidelines")
169
  parallel_tasks.append(("guidelines", self._step_guidelines))
170
 
171
  if parallel_tasks:
 
182
 
183
  # ── Step 5: Conflict Detection ──
184
  if case.include_guidelines:
185
+ yield self._mark_running("conflicts")
186
  yield await self._run_step("conflicts", self._step_conflict_detection)
187
 
188
  # ── Step 6: Synthesis ──
189
+ yield self._mark_running("synthesize")
190
  yield await self._run_step("synthesize", self._step_synthesize)
191
 
192
  self._state.completed_at = datetime.utcnow()
 
213
  skipped.append(step)
214
  return skipped
215
 
216
+ def _mark_running(self, step_id: str) -> AgentStep:
217
+ """Mark a step as RUNNING and return it for immediate yielding."""
218
+ step = self._get_step(step_id)
219
+ step.status = AgentStepStatus.RUNNING
220
+ return step
221
+
222
  async def _run_step(self, step_id: str, fn, *args) -> AgentStep:
223
  """Execute a single step, tracking status and timing."""
224
  step = self._get_step(step_id)
src/backend/app/api/feedback.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Feedback endpoint — stores user feedback from the demo.
3
+ Simple JSON Lines file storage. No database needed.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import logging
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+
12
+ from fastapi import APIRouter, Request
13
+ from pydantic import BaseModel, Field
14
+
15
+ logger = logging.getLogger(__name__)
16
+ router = APIRouter()
17
+
18
+ FEEDBACK_FILE = Path("/tmp/cds_feedback.jsonl")
19
+
20
+
21
+ class FeedbackSubmission(BaseModel):
22
+ message: str = Field(..., max_length=1000)
23
+ contact: str | None = Field(None, max_length=200)
24
+ page_url: str | None = None
25
+ user_agent: str | None = None
26
+
27
+
28
+ @router.post("/api/feedback")
29
+ async def submit_feedback(feedback: FeedbackSubmission, request: Request):
30
+ """Save user feedback to a JSONL file."""
31
+ entry = {
32
+ "timestamp": datetime.now(timezone.utc).isoformat(),
33
+ "message": feedback.message,
34
+ "contact": feedback.contact,
35
+ "page_url": feedback.page_url,
36
+ "user_agent": feedback.user_agent,
37
+ "client_ip": request.client.host if request.client else None,
38
+ }
39
+
40
+ try:
41
+ with open(FEEDBACK_FILE, "a", encoding="utf-8") as f:
42
+ f.write(json.dumps(entry) + "\n")
43
+ logger.info(f"Feedback saved: {feedback.message[:50]}...")
44
+ except Exception as e:
45
+ logger.error(f"Failed to save feedback: {e}")
46
+ return {"status": "error", "message": "Failed to save feedback"}
47
+
48
+ return {"status": "ok", "message": "Thank you for your feedback!"}
49
+
50
+
51
+ @router.get("/api/feedback")
52
+ async def list_feedback():
53
+ """List all feedback (for the developer). No auth — it's a demo."""
54
+ if not FEEDBACK_FILE.exists():
55
+ return {"feedback": [], "count": 0}
56
+
57
+ entries = []
58
+ try:
59
+ with open(FEEDBACK_FILE, "r", encoding="utf-8") as f:
60
+ for line in f:
61
+ line = line.strip()
62
+ if line:
63
+ entries.append(json.loads(line))
64
+ except Exception as e:
65
+ logger.error(f"Failed to read feedback: {e}")
66
+ return {"feedback": [], "count": 0, "error": str(e)}
67
+
68
+ return {"feedback": entries, "count": len(entries)}
src/backend/app/main.py CHANGED
@@ -6,7 +6,7 @@ import logging
6
  from fastapi import FastAPI
7
  from fastapi.middleware.cors import CORSMiddleware
8
 
9
- from app.api import cases, health, ws
10
  from app.config import settings
11
 
12
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
@@ -30,6 +30,7 @@ app.add_middleware(
30
  # Routes
31
  app.include_router(health.router, tags=["health"])
32
  app.include_router(cases.router, prefix="/api/cases", tags=["cases"])
 
33
  app.include_router(ws.router, prefix="/ws", tags=["websocket"])
34
 
35
 
 
6
  from fastapi import FastAPI
7
  from fastapi.middleware.cors import CORSMiddleware
8
 
9
+ from app.api import cases, feedback, health, ws
10
  from app.config import settings
11
 
12
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
 
30
  # Routes
31
  app.include_router(health.router, tags=["health"])
32
  app.include_router(cases.router, prefix="/api/cases", tags=["cases"])
33
+ app.include_router(feedback.router, tags=["feedback"])
34
  app.include_router(ws.router, prefix="/ws", tags=["websocket"])
35
 
36
 
src/frontend/src/app/page.tsx CHANGED
@@ -4,6 +4,7 @@ import { useState, useCallback } from "react";
4
  import { PatientInput } from "@/components/PatientInput";
5
  import { AgentPipeline } from "@/components/AgentPipeline";
6
  import { CDSReport } from "@/components/CDSReport";
 
7
  import { useAgentWebSocket } from "@/hooks/useAgentWebSocket";
8
  import { reportToMarkdown } from "@/lib/reportToMarkdown";
9
 
@@ -173,7 +174,13 @@ export default function Home() {
173
  <div className="flex items-center justify-center h-64 text-gray-400">
174
  <div className="text-center">
175
  <div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full mx-auto mb-4" />
176
- <p>Agent pipeline running...</p>
 
 
 
 
 
 
177
  </div>
178
  </div>
179
  ) : error && steps.length === 0 ? (
@@ -207,6 +214,9 @@ export default function Home() {
207
  )}
208
  </div>
209
 
 
 
 
210
  {/* Disclaimer footer */}
211
  <footer className="fixed bottom-0 left-0 right-0 bg-amber-50 border-t border-amber-200 px-6 py-2">
212
  <p className="text-center text-xs text-amber-700">
 
4
  import { PatientInput } from "@/components/PatientInput";
5
  import { AgentPipeline } from "@/components/AgentPipeline";
6
  import { CDSReport } from "@/components/CDSReport";
7
+ import { FeedbackWidget } from "@/components/FeedbackWidget";
8
  import { useAgentWebSocket } from "@/hooks/useAgentWebSocket";
9
  import { reportToMarkdown } from "@/lib/reportToMarkdown";
10
 
 
174
  <div className="flex items-center justify-center h-64 text-gray-400">
175
  <div className="text-center">
176
  <div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full mx-auto mb-4" />
177
+ <p className="text-gray-600 font-medium">Agent pipeline running...</p>
178
+ <p className="text-sm text-gray-400 mt-1">
179
+ Watch the steps on the left — the report will appear here when done.
180
+ </p>
181
+ <p className="text-xs text-gray-400 mt-3">
182
+ Full analysis typically takes 2–4 minutes across 6 steps.
183
+ </p>
184
  </div>
185
  </div>
186
  ) : error && steps.length === 0 ? (
 
214
  )}
215
  </div>
216
 
217
+ {/* Feedback widget */}
218
+ <FeedbackWidget />
219
+
220
  {/* Disclaimer footer */}
221
  <footer className="fixed bottom-0 left-0 right-0 bg-amber-50 border-t border-amber-200 px-6 py-2">
222
  <p className="text-center text-xs text-amber-700">
src/frontend/src/components/AgentPipeline.tsx CHANGED
@@ -1,5 +1,7 @@
1
  "use client";
2
 
 
 
3
  interface Step {
4
  step_id: string;
5
  step_name: string;
@@ -48,7 +50,86 @@ const STATUS_CONFIG = {
48
  },
49
  };
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  export function AgentPipeline({ steps, isRunning }: AgentPipelineProps) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  return (
53
  <div className="bg-white rounded-xl border border-gray-200 p-5">
54
  <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">
@@ -58,6 +139,7 @@ export function AgentPipeline({ steps, isRunning }: AgentPipelineProps) {
58
  <div className="space-y-1">
59
  {steps.map((step, index) => {
60
  const config = STATUS_CONFIG[step.status];
 
61
  return (
62
  <div key={step.step_id}>
63
  {/* Connector line */}
@@ -70,13 +152,18 @@ export function AgentPipeline({ steps, isRunning }: AgentPipelineProps) {
70
  className={`flex items-start gap-3 p-3 rounded-lg border ${config.bg} ${config.border} transition-all duration-300`}
71
  >
72
  {/* Status icon */}
73
- <span
74
- className={`text-lg font-bold ${config.color} ${
75
- step.status === "running" ? "animate-pulse-dot" : ""
76
- }`}
77
- >
78
- {config.icon}
79
- </span>
 
 
 
 
 
80
 
81
  {/* Step info */}
82
  <div className="flex-1 min-w-0">
@@ -84,14 +171,25 @@ export function AgentPipeline({ steps, isRunning }: AgentPipelineProps) {
84
  <span className="text-sm font-medium text-gray-800">
85
  {step.step_name}
86
  </span>
87
- {step.duration_ms != null && (
 
 
 
 
 
88
  <span className="text-xs text-gray-400">
89
- {step.duration_ms}ms
90
  </span>
91
  )}
92
  </div>
93
 
94
- {step.tool_name && (
 
 
 
 
 
 
95
  <span className="text-xs text-gray-400 font-mono">
96
  {step.tool_name}
97
  </span>
 
1
  "use client";
2
 
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
  interface Step {
6
  step_id: string;
7
  step_name: string;
 
50
  },
51
  };
52
 
53
+ const STEP_MESSAGES: Record<string, string[]> = {
54
+ parse: [
55
+ "Extracting demographics, medications, labs...",
56
+ "Identifying chief complaint and history...",
57
+ "Structuring patient data...",
58
+ ],
59
+ reason: [
60
+ "Analyzing symptoms and history...",
61
+ "Generating differential diagnosis...",
62
+ "Evaluating clinical evidence...",
63
+ ],
64
+ drugs: [
65
+ "Querying OpenFDA drug database...",
66
+ "Checking RxNorm interactions...",
67
+ "Evaluating medication safety...",
68
+ ],
69
+ guidelines: [
70
+ "Searching clinical guideline database...",
71
+ "Retrieving relevant excerpts...",
72
+ "Matching guidelines to diagnosis...",
73
+ ],
74
+ conflicts: [
75
+ "Cross-referencing care plan with guidelines...",
76
+ "Detecting care gaps...",
77
+ "Checking for contraindications...",
78
+ ],
79
+ synthesize: [
80
+ "Compiling analysis results...",
81
+ "Generating clinical report...",
82
+ "Finalizing recommendations...",
83
+ ],
84
+ };
85
+
86
+ function formatElapsed(seconds: number): string {
87
+ if (seconds < 60) return `${seconds}s`;
88
+ const m = Math.floor(seconds / 60);
89
+ const s = seconds % 60;
90
+ return `${m}m ${s}s`;
91
+ }
92
+
93
  export function AgentPipeline({ steps, isRunning }: AgentPipelineProps) {
94
+ const [tick, setTick] = useState(0);
95
+ const startTimes = useRef<Record<string, number>>({});
96
+
97
+ // Track when steps start running
98
+ useEffect(() => {
99
+ const now = Date.now();
100
+ steps.forEach((step) => {
101
+ if (step.status === "running" && !startTimes.current[step.step_id]) {
102
+ startTimes.current[step.step_id] = now;
103
+ }
104
+ if (step.status !== "running" && startTimes.current[step.step_id]) {
105
+ delete startTimes.current[step.step_id];
106
+ }
107
+ });
108
+ }, [steps]);
109
+
110
+ // Tick every second when any step is running
111
+ useEffect(() => {
112
+ const hasRunning = steps.some((s) => s.status === "running");
113
+ if (!hasRunning) return;
114
+
115
+ const interval = setInterval(() => setTick((t) => t + 1), 1000);
116
+ return () => clearInterval(interval);
117
+ }, [steps.map((s) => s.status).join(",")]);
118
+
119
+ const getElapsed = (stepId: string): number => {
120
+ const start = startTimes.current[stepId];
121
+ if (!start) return 0;
122
+ return Math.floor((Date.now() - start) / 1000);
123
+ };
124
+
125
+ const getActivityMessage = (stepId: string, elapsed: number): string => {
126
+ const messages = STEP_MESSAGES[stepId];
127
+ if (!messages) return "Processing...";
128
+ // Cycle through messages every 8 seconds
129
+ const idx = Math.floor(elapsed / 8) % messages.length;
130
+ return messages[idx];
131
+ };
132
+
133
  return (
134
  <div className="bg-white rounded-xl border border-gray-200 p-5">
135
  <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-4">
 
139
  <div className="space-y-1">
140
  {steps.map((step, index) => {
141
  const config = STATUS_CONFIG[step.status];
142
+ const elapsed = step.status === "running" ? getElapsed(step.step_id) : 0;
143
  return (
144
  <div key={step.step_id}>
145
  {/* Connector line */}
 
152
  className={`flex items-start gap-3 p-3 rounded-lg border ${config.bg} ${config.border} transition-all duration-300`}
153
  >
154
  {/* Status icon */}
155
+ <div className="flex-shrink-0 mt-0.5">
156
+ {step.status === "running" ? (
157
+ <div className="w-5 h-5 relative">
158
+ <div className="absolute inset-0 rounded-full border-2 border-blue-200" />
159
+ <div className="absolute inset-0 rounded-full border-2 border-blue-500 border-t-transparent animate-spin" />
160
+ </div>
161
+ ) : (
162
+ <span className={`text-lg font-bold ${config.color}`}>
163
+ {config.icon}
164
+ </span>
165
+ )}
166
+ </div>
167
 
168
  {/* Step info */}
169
  <div className="flex-1 min-w-0">
 
171
  <span className="text-sm font-medium text-gray-800">
172
  {step.step_name}
173
  </span>
174
+ {step.status === "running" && elapsed > 0 && (
175
+ <span className="text-xs font-mono text-blue-500 tabular-nums">
176
+ {formatElapsed(elapsed)}
177
+ </span>
178
+ )}
179
+ {step.duration_ms != null && step.status !== "running" && (
180
  <span className="text-xs text-gray-400">
181
+ {(step.duration_ms / 1000).toFixed(1)}s
182
  </span>
183
  )}
184
  </div>
185
 
186
+ {step.status === "running" && (
187
+ <p className="text-xs text-blue-500 mt-1 animate-pulse">
188
+ {getActivityMessage(step.step_id, elapsed)}
189
+ </p>
190
+ )}
191
+
192
+ {step.tool_name && step.status !== "running" && (
193
  <span className="text-xs text-gray-400 font-mono">
194
  {step.tool_name}
195
  </span>
src/frontend/src/components/FeedbackWidget.tsx ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+
5
+ export function FeedbackWidget() {
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ const [message, setMessage] = useState("");
8
+ const [contact, setContact] = useState("");
9
+ const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
10
+ const panelRef = useRef<HTMLDivElement>(null);
11
+
12
+ // Close on outside click
13
+ useEffect(() => {
14
+ if (!isOpen) return;
15
+ const handleClick = (e: MouseEvent) => {
16
+ if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
17
+ setIsOpen(false);
18
+ }
19
+ };
20
+ document.addEventListener("mousedown", handleClick);
21
+ return () => document.removeEventListener("mousedown", handleClick);
22
+ }, [isOpen]);
23
+
24
+ const handleSubmit = async () => {
25
+ if (!message.trim()) return;
26
+ setStatus("sending");
27
+
28
+ try {
29
+ const res = await fetch("/api/feedback", {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify({
33
+ message: message.trim(),
34
+ contact: contact.trim() || undefined,
35
+ page_url: window.location.href,
36
+ user_agent: navigator.userAgent,
37
+ }),
38
+ });
39
+
40
+ if (res.ok) {
41
+ setStatus("sent");
42
+ setMessage("");
43
+ setContact("");
44
+ setTimeout(() => {
45
+ setIsOpen(false);
46
+ setStatus("idle");
47
+ }, 2000);
48
+ } else {
49
+ setStatus("error");
50
+ }
51
+ } catch {
52
+ setStatus("error");
53
+ }
54
+ };
55
+
56
+ return (
57
+ <div className="fixed bottom-12 right-6 z-50" ref={panelRef}>
58
+ {/* Feedback panel */}
59
+ {isOpen && (
60
+ <div className="mb-3 w-80 bg-white rounded-xl shadow-2xl border border-gray-200 overflow-hidden animate-in">
61
+ <div className="bg-blue-600 px-4 py-3">
62
+ <h3 className="text-white font-semibold text-sm">Send Feedback</h3>
63
+ <p className="text-blue-100 text-xs mt-0.5">
64
+ Bugs, impressions, ideas — anything helps!
65
+ </p>
66
+ </div>
67
+
68
+ <div className="p-4 space-y-3">
69
+ {status === "sent" ? (
70
+ <div className="text-center py-4">
71
+ <div className="text-3xl mb-2">✓</div>
72
+ <p className="text-green-700 font-medium text-sm">Thanks for the feedback!</p>
73
+ </div>
74
+ ) : (
75
+ <>
76
+ <div>
77
+ <textarea
78
+ value={message}
79
+ onChange={(e) => setMessage(e.target.value)}
80
+ placeholder="What's on your mind?"
81
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
82
+ rows={3}
83
+ maxLength={1000}
84
+ autoFocus
85
+ />
86
+ </div>
87
+
88
+ <div>
89
+ <input
90
+ type="text"
91
+ value={contact}
92
+ onChange={(e) => setContact(e.target.value)}
93
+ placeholder="Email or Twitter (optional)"
94
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
95
+ maxLength={200}
96
+ />
97
+ </div>
98
+
99
+ {status === "error" && (
100
+ <p className="text-xs text-red-600">
101
+ Failed to send — try again in a moment.
102
+ </p>
103
+ )}
104
+
105
+ <button
106
+ onClick={handleSubmit}
107
+ disabled={!message.trim() || status === "sending"}
108
+ className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition-colors"
109
+ >
110
+ {status === "sending" ? "Sending..." : "Send Feedback"}
111
+ </button>
112
+ </>
113
+ )}
114
+ </div>
115
+ </div>
116
+ )}
117
+
118
+ {/* Floating button */}
119
+ <button
120
+ onClick={() => {
121
+ setIsOpen(!isOpen);
122
+ setStatus("idle");
123
+ }}
124
+ className="w-12 h-12 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-105 active:scale-95"
125
+ title="Send feedback"
126
+ >
127
+ <svg
128
+ xmlns="http://www.w3.org/2000/svg"
129
+ viewBox="0 0 24 24"
130
+ fill="currentColor"
131
+ className="w-5 h-5"
132
+ >
133
+ {isOpen ? (
134
+ <path
135
+ fillRule="evenodd"
136
+ d="M5.47 5.47a.75.75 0 011.06 0L12 10.94l5.47-5.47a.75.75 0 111.06 1.06L13.06 12l5.47 5.47a.75.75 0 11-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 01-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 010-1.06z"
137
+ clipRule="evenodd"
138
+ />
139
+ ) : (
140
+ <path
141
+ fillRule="evenodd"
142
+ d="M4.848 2.771A49.144 49.144 0 0112 2.25c2.43 0 4.817.178 7.152.52 1.978.29 3.348 2.024 3.348 3.97v6.02c0 1.946-1.37 3.68-3.348 3.97a48.901 48.901 0 01-3.476.383.39.39 0 00-.297.17l-2.755 4.133a.75.75 0 01-1.248 0l-2.755-4.133a.39.39 0 00-.297-.17 48.9 48.9 0 01-3.476-.384c-1.978-.29-3.348-2.024-3.348-3.97V6.741c0-1.946 1.37-3.68 3.348-3.97zM6.75 8.25a.75.75 0 01.75-.75h9a.75.75 0 010 1.5h-9a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H7.5z"
143
+ clipRule="evenodd"
144
+ />
145
+ )}
146
+ </svg>
147
+ </button>
148
+ </div>
149
+ );
150
+ }