diff --git a/apps/web/src/app/api/copilot/route.ts b/apps/web/src/app/api/copilot/route.ts index 4ceea85..07f1dd2 100644 --- a/apps/web/src/app/api/copilot/route.ts +++ b/apps/web/src/app/api/copilot/route.ts @@ -136,6 +136,12 @@ ${signalsContext}`; }); const data = await res.json(); + if (!res.ok || data?.type === 'error') { + console.error('Anthropic API error:', JSON.stringify(data)); + return NextResponse.json({ + reply: `Iris error: ${data?.error?.message ?? data?.error?.type ?? 'Unknown error from Anthropic API'}`, + }); + } const reply = data?.content?.[0]?.text ?? 'No response.'; return NextResponse.json({ reply }); } diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 504c8f2..a29c3a8 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -29,9 +29,15 @@ export default async function HomePage({ searchParams }: PageProps) { const hospitalMap = Object.fromEntries(hospitals.map((h) => [h.id, h.name])); // Strip garbage — filtered_out signals should never appear in the feed - const cleanSignals = allSignals.filter( - (s) => s.tier !== 'filtered_out' && s.signal_type !== 'filtered_out' - ); + const seenUrls = new Set(); + const cleanSignals = allSignals.filter((s) => { + if (s.tier === 'filtered_out' || s.signal_type === 'filtered_out') return false; + if (s.source_url) { + if (seenUrls.has(s.source_url)) return false; + seenUrls.add(s.source_url); + } + return true; + }); // Filter by category let signals = category diff --git a/apps/web/src/app/review/page.tsx b/apps/web/src/app/review/page.tsx index 877eacb..9ba237a 100644 --- a/apps/web/src/app/review/page.tsx +++ b/apps/web/src/app/review/page.tsx @@ -20,7 +20,7 @@ export default async function ReviewPage() { return (
-

Review queue

+

Review Queue

Low-confidence signals (under 70%) awaiting Danielle's approval before digest. {signals.length > 0 && ( diff --git a/apps/web/src/components/CoPilot.tsx b/apps/web/src/components/CoPilot.tsx index 00a81ee..a502542 100644 --- a/apps/web/src/components/CoPilot.tsx +++ b/apps/web/src/components/CoPilot.tsx @@ -13,6 +13,7 @@ function IrisMessage({ text }: { text: string }) { const lines = text.split('\n'); const nodes: React.ReactNode[] = []; let i = 0; + let k = 0; while (i < lines.length) { const line = lines[i] ?? ''; @@ -31,7 +32,7 @@ function IrisMessage({ text }: { text: string }) { i++; } nodes.push( -

    +
      {items.map((item, j) => (
    • {renderInline(item)}
    • ))} @@ -48,7 +49,7 @@ function IrisMessage({ text }: { text: string }) { i++; } nodes.push( -
        +
          {items.map((item, j) => (
        1. {renderInline(item)}
        2. ))} @@ -59,7 +60,7 @@ function IrisMessage({ text }: { text: string }) { // Regular paragraph nodes.push( -

          +

          {renderInline(line)}

          ); @@ -131,7 +132,7 @@ function defaultPos() { const isMobile = window.innerWidth < 768; return { x: window.innerWidth - BUBBLE_SIZE - (isMobile ? 16 : 24), - y: window.innerHeight - BUBBLE_SIZE - (isMobile ? 88 : 24), + y: window.innerHeight - BUBBLE_SIZE - (isMobile ? 96 : 48), }; } @@ -146,7 +147,7 @@ export default function CoPilot() { const [pulsing, setPulsing] = useState(false); const [hasUnread, setHasUnread] = useState(false); // null = not yet mounted (SSR safe) - const { userId, isAdmin } = useUser(); + const { userId, isAdmin, userName } = useUser(); const [pos, setPos] = useState<{ x: number; y: number } | null>(null); const bottomRef = useRef(null); const pillDismissTimer = useRef | null>(null); @@ -170,11 +171,11 @@ export default function CoPilot() { if (Math.abs(dx) > 5 || Math.abs(dy) > 5) drag.current.moved = true; const newX = Math.max( 0, - Math.min(window.innerWidth - BUBBLE_SIZE, drag.current.startPosX + dx) + Math.min(window.innerWidth - BUBBLE_SIZE - 8, drag.current.startPosX + dx) ); const newY = Math.max( 0, - Math.min(window.innerHeight - BUBBLE_SIZE, drag.current.startPosY + dy) + Math.min(window.innerHeight - BUBBLE_SIZE - 16, drag.current.startPosY + dy) ); setPos({ x: newX, y: newY }); } @@ -185,8 +186,8 @@ export default function CoPilot() { setPos((prev) => { if (!prev) return defaultPos(); return { - x: Math.max(0, Math.min(window.innerWidth - BUBBLE_SIZE, prev.x)), - y: Math.max(0, Math.min(window.innerHeight - BUBBLE_SIZE, prev.y)), + x: Math.max(0, Math.min(window.innerWidth - BUBBLE_SIZE - 8, prev.x)), + y: Math.max(0, Math.min(window.innerHeight - BUBBLE_SIZE - 16, prev.y)), }; }); } @@ -338,12 +339,10 @@ export default function CoPilot() { }; } - const gap = 8; - let top = pos.y - PANEL_HEIGHT - gap; - if (top < 8) top = pos.y + BUBBLE_SIZE + gap; - let left = pos.x + BUBBLE_SIZE - PANEL_WIDTH; - left = Math.max(8, Math.min(window.innerWidth - PANEL_WIDTH - 8, left)); - return { position: 'fixed', top, left, zIndex: 50, width: PANEL_WIDTH }; + const gap = 12; + const bottom = window.innerHeight - pos.y + gap; + const right = Math.max(8, window.innerWidth - pos.x - BUBBLE_SIZE); + return { position: 'fixed', bottom, right, zIndex: 50, width: PANEL_WIDTH }; } if (!pos) return null; @@ -534,6 +533,11 @@ export default function CoPilot() { > {messages.length === 0 && (
          + {userName && ( +

          + Hi {userName.split(' ')[0]}, what do you need? +

          + )} - {[ - 'Who should I call this week?', - 'Draft an outreach email for Ascension', - "What's urgent across my accounts?", - ].map((prompt) => ( + {(isAdmin + ? [ + 'Draft my weekly digest', + "What's urgent across all accounts?", + 'Which AEs need a follow-up this week?', + ] + : [ + 'Who should I call this week?', + 'Summarize my most important account', + "What's urgent across my accounts?", + ] + ).map((prompt) => (