@wealthx/shadcn 1.5.40 → 1.5.42
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/.turbo/turbo-build.log +103 -103
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-MGIDYXOP.mjs → chunk-DWNLBUDC.mjs} +459 -67
- package/dist/{chunk-EFHPSKVF.mjs → chunk-EGM4DARZ.mjs} +110 -1
- package/dist/{chunk-B5PSUONN.mjs → chunk-TF5TOVIM.mjs} +1 -1
- package/dist/{chunk-STN5QIWN.mjs → chunk-THOHFAW2.mjs} +119 -46
- package/dist/{chunk-RRROLESJ.mjs → chunk-XHZONBL4.mjs} +1 -1
- package/dist/components/ui/ai-assistant-drawer.js +101 -0
- package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
- package/dist/components/ui/ai-conversations/index.js +101 -0
- package/dist/components/ui/ai-conversations/index.mjs +2 -2
- package/dist/components/ui/chat-input-area.js +101 -0
- package/dist/components/ui/chat-input-area.mjs +1 -1
- package/dist/components/ui/policy-ai/index.js +818 -261
- package/dist/components/ui/policy-ai/index.mjs +11 -2
- package/dist/components/ui/support-agent/index.js +218 -44
- package/dist/components/ui/support-agent/index.mjs +2 -2
- package/dist/index.js +3506 -3329
- package/dist/index.mjs +5 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/chat-input-area.tsx +181 -2
- package/src/components/ui/policy-ai/index.tsx +12 -0
- package/src/components/ui/policy-ai/policy-ai-context-sidebar.tsx +231 -0
- package/src/components/ui/policy-ai/policy-ai-history-panel.tsx +175 -0
- package/src/components/ui/policy-ai/policy-ai-page.tsx +243 -0
- package/src/components/ui/policy-ai/policy-ai-panel.tsx +64 -57
- package/src/components/ui/policy-ai/policy-ai-responses.tsx +8 -12
- package/src/components/ui/support-agent/support-agent-panel.tsx +153 -46
- package/src/styles/styles-css.ts +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { PanelRight } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { PageTopBar } from "@/components/ui/page-top-bar";
|
|
6
|
+
import { PolicyAIPanel } from "./policy-ai-panel";
|
|
7
|
+
import { PolicyAIHistoryPanel } from "./policy-ai-history-panel";
|
|
8
|
+
import { PolicyAIContextSidebar } from "./policy-ai-context-sidebar";
|
|
9
|
+
import type {
|
|
10
|
+
PolicyAIMessage,
|
|
11
|
+
PolicyConversationItem,
|
|
12
|
+
PolicyQueryContext,
|
|
13
|
+
} from "./policy-ai-primitives";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Props
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export interface PolicyAIPageProps {
|
|
20
|
+
// ── Chat area (pass-through to PolicyAIPanel inline) ──
|
|
21
|
+
messages?: PolicyAIMessage[];
|
|
22
|
+
suggestedQuestions?: Record<string, string[]>;
|
|
23
|
+
isStreaming?: boolean;
|
|
24
|
+
isLoading?: boolean;
|
|
25
|
+
thinkingSteps?: string[];
|
|
26
|
+
onSendMessage?: (text: string) => void;
|
|
27
|
+
onAttachFile?: (files: FileList) => void;
|
|
28
|
+
onAttachImage?: (files: FileList) => void;
|
|
29
|
+
onReset?: () => void;
|
|
30
|
+
|
|
31
|
+
// ── History panel (left) ──
|
|
32
|
+
conversations?: PolicyConversationItem[];
|
|
33
|
+
activeConversationId?: string;
|
|
34
|
+
onSelectConversation?: (id: string) => void;
|
|
35
|
+
onNewChat?: () => void;
|
|
36
|
+
|
|
37
|
+
// ── Context sidebar (right) ──
|
|
38
|
+
/**
|
|
39
|
+
* Whether the context panel starts visible.
|
|
40
|
+
* Renamed from `showContextPanel` to make uncontrolled semantics explicit —
|
|
41
|
+
* the toggle button owns the state after mount.
|
|
42
|
+
*/
|
|
43
|
+
defaultShowContextPanel?: boolean;
|
|
44
|
+
bankCount?: number;
|
|
45
|
+
categoryCount?: number;
|
|
46
|
+
lastUpdated?: string;
|
|
47
|
+
/** Opens the Support Agent panel — wired to PageTopBar "Ask Support" button. */
|
|
48
|
+
onAskSupport?: () => void;
|
|
49
|
+
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Internal helpers
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/** Derives suggested follow-up questions from the last assistant message's query context. */
|
|
58
|
+
function deriveFollowUps(context: PolicyQueryContext | undefined): string[] {
|
|
59
|
+
if (!context) return [];
|
|
60
|
+
|
|
61
|
+
const { queryType, policyType, bankName } = context;
|
|
62
|
+
|
|
63
|
+
if (queryType === "cross_bank_comparison") {
|
|
64
|
+
return [
|
|
65
|
+
`Which of these banks is best overall for ${context.categories[0] ?? policyType}?`,
|
|
66
|
+
bankName
|
|
67
|
+
? `Does ${bankName} have any exceptions?`
|
|
68
|
+
: "Which bank has the most flexible policy?",
|
|
69
|
+
"Show me the ranked list of lenders.",
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
if (queryType === "ranking") {
|
|
73
|
+
return [
|
|
74
|
+
`What are the full details for the #1 ranked bank?`,
|
|
75
|
+
`Which banks in the list accept cases under 80% LVR?`,
|
|
76
|
+
`Compare the top 3 banks side by side.`,
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
if (queryType === "single_bank") {
|
|
80
|
+
return [
|
|
81
|
+
`How does ${bankName ?? "this bank"} compare to other lenders?`,
|
|
82
|
+
`What documentation does ${bankName ?? "this bank"} require?`,
|
|
83
|
+
`Which banks have a better policy than ${bankName ?? "this bank"}?`,
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
if (queryType === "threshold_filter") {
|
|
87
|
+
return [
|
|
88
|
+
`What is the maximum LVR across all filtered banks?`,
|
|
89
|
+
`Are any of these banks on a specialist product?`,
|
|
90
|
+
`Rank the filtered banks best to worst.`,
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// PolicyAIPage (Template)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Dedicated full-page layout for the Policy AI feature.
|
|
102
|
+
*
|
|
103
|
+
* 3-panel layout modelled on the AI Conversations page:
|
|
104
|
+
* - **Left** (260px): `PolicyAIHistoryPanel` — recent conversations + search
|
|
105
|
+
* - **Center** (flex-1): `PolicyAIPanel` in inline mode — chat + response cards
|
|
106
|
+
* - **Right** (280px, toggle-able): `PolicyAIContextSidebar` — coverage stats +
|
|
107
|
+
* last query context + suggested follow-ups
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* <PolicyAIPage
|
|
111
|
+
* messages={messages}
|
|
112
|
+
* conversations={conversations}
|
|
113
|
+
* activeConversationId={activeId}
|
|
114
|
+
* onSendMessage={handleSend}
|
|
115
|
+
* onSelectConversation={setActiveId}
|
|
116
|
+
* onNewChat={handleNewChat}
|
|
117
|
+
* />
|
|
118
|
+
*/
|
|
119
|
+
export function PolicyAIPage({
|
|
120
|
+
// Chat
|
|
121
|
+
messages = [],
|
|
122
|
+
suggestedQuestions,
|
|
123
|
+
isStreaming = false,
|
|
124
|
+
isLoading = false,
|
|
125
|
+
thinkingSteps,
|
|
126
|
+
onSendMessage,
|
|
127
|
+
onAttachFile,
|
|
128
|
+
onAttachImage,
|
|
129
|
+
onReset,
|
|
130
|
+
// History
|
|
131
|
+
conversations = [],
|
|
132
|
+
activeConversationId,
|
|
133
|
+
onSelectConversation,
|
|
134
|
+
onNewChat,
|
|
135
|
+
// Context
|
|
136
|
+
defaultShowContextPanel = true,
|
|
137
|
+
bankCount = 42,
|
|
138
|
+
categoryCount = 86,
|
|
139
|
+
lastUpdated = "June 2026",
|
|
140
|
+
onAskSupport,
|
|
141
|
+
className,
|
|
142
|
+
}: PolicyAIPageProps) {
|
|
143
|
+
const [showContextPanel, setShowContextPanel] = React.useState(
|
|
144
|
+
defaultShowContextPanel,
|
|
145
|
+
);
|
|
146
|
+
const [historySearch, setHistorySearch] = React.useState("");
|
|
147
|
+
|
|
148
|
+
// Walk backwards to find the most recent assistant message that carries a
|
|
149
|
+
// queryContext. This keeps the sidebar populated while a new (streaming)
|
|
150
|
+
// reply is pending — the in-flight message won't carry a queryContext yet.
|
|
151
|
+
// Single-pass: no intermediate array allocation.
|
|
152
|
+
const lastQueryContext = React.useMemo(() => {
|
|
153
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
154
|
+
const m = messages[i];
|
|
155
|
+
if (m.role === "assistant" && m.queryContext) return m.queryContext;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}, [messages]);
|
|
159
|
+
|
|
160
|
+
// deriveFollowUps is a cheap pure function; no useMemo needed.
|
|
161
|
+
const followUps = deriveFollowUps(lastQueryContext);
|
|
162
|
+
|
|
163
|
+
const handleNewChat = () => {
|
|
164
|
+
onReset?.();
|
|
165
|
+
onNewChat?.();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Derive once — used for both aria-label and title on the toggle button.
|
|
169
|
+
const panelToggleLabel = showContextPanel
|
|
170
|
+
? "Hide context panel"
|
|
171
|
+
: "Show context panel";
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
data-slot="policy-ai-page"
|
|
176
|
+
className={cn("flex flex-col h-full bg-background", className)}
|
|
177
|
+
>
|
|
178
|
+
{/* ── Page header — uses shared PageTopBar (same as Loan CRM, Contact, etc.) ── */}
|
|
179
|
+
<PageTopBar
|
|
180
|
+
title="Policy AI"
|
|
181
|
+
actions={
|
|
182
|
+
<Button
|
|
183
|
+
variant="ghost"
|
|
184
|
+
size="icon-sm"
|
|
185
|
+
onClick={() => setShowContextPanel((v) => !v)}
|
|
186
|
+
aria-label={panelToggleLabel}
|
|
187
|
+
aria-pressed={showContextPanel}
|
|
188
|
+
title={panelToggleLabel}
|
|
189
|
+
>
|
|
190
|
+
<PanelRight className="size-4" aria-hidden="true" />
|
|
191
|
+
</Button>
|
|
192
|
+
}
|
|
193
|
+
onAskSupport={onAskSupport}
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
{/* ── 3-panel body ── */}
|
|
197
|
+
<div className="flex flex-1 min-h-0 overflow-hidden">
|
|
198
|
+
{/* Left — conversation history */}
|
|
199
|
+
<PolicyAIHistoryPanel
|
|
200
|
+
conversations={conversations}
|
|
201
|
+
activeId={activeConversationId}
|
|
202
|
+
onSelect={onSelectConversation}
|
|
203
|
+
onNewChat={handleNewChat}
|
|
204
|
+
searchQuery={historySearch}
|
|
205
|
+
onSearchChange={setHistorySearch}
|
|
206
|
+
className="w-[260px]"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
{/* Center — chat area (PolicyAIPanel inline).
|
|
210
|
+
min-h-0 lets it shrink inside the flex row; the panel owns its
|
|
211
|
+
own overflow-y-auto so the footer stays pinned at the bottom. */}
|
|
212
|
+
<div className="flex-1 flex flex-col min-w-0 min-h-0">
|
|
213
|
+
<PolicyAIPanel
|
|
214
|
+
inline
|
|
215
|
+
messages={messages}
|
|
216
|
+
suggestedQuestions={suggestedQuestions}
|
|
217
|
+
isStreaming={isStreaming}
|
|
218
|
+
isLoading={isLoading}
|
|
219
|
+
thinkingSteps={thinkingSteps}
|
|
220
|
+
onSendMessage={onSendMessage}
|
|
221
|
+
onAttachFile={onAttachFile}
|
|
222
|
+
onAttachImage={onAttachImage}
|
|
223
|
+
onReset={onReset}
|
|
224
|
+
className="flex-1 min-h-0"
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{/* Right — context sidebar: only shown once a conversation is active */}
|
|
229
|
+
{showContextPanel && messages.length > 0 && (
|
|
230
|
+
<PolicyAIContextSidebar
|
|
231
|
+
lastQueryContext={lastQueryContext}
|
|
232
|
+
suggestedFollowUps={followUps}
|
|
233
|
+
onFollowUpClick={onSendMessage}
|
|
234
|
+
bankCount={bankCount}
|
|
235
|
+
categoryCount={categoryCount}
|
|
236
|
+
lastUpdated={lastUpdated}
|
|
237
|
+
className="w-[280px]"
|
|
238
|
+
/>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "lucide-react";
|
|
10
10
|
import { cn } from "@/lib/utils";
|
|
11
11
|
import { Button } from "@/components/ui/button";
|
|
12
|
+
import { Card } from "@/components/ui/card";
|
|
12
13
|
import { ChatInputArea } from "@/components/ui/chat-input-area";
|
|
13
14
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
14
15
|
import { ChatWidgetMessage } from "@/components/ui/chat-widget-primitives";
|
|
@@ -38,8 +39,10 @@ export interface PolicyAIFABProps {
|
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
|
|
40
41
|
export interface PolicyAIPanelProps {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/** Required in float mode; ignored in `inline` mode. */
|
|
43
|
+
open?: boolean;
|
|
44
|
+
/** Required in float mode; ignored in `inline` mode. */
|
|
45
|
+
onClose?: () => void;
|
|
43
46
|
messages?: PolicyAIMessage[];
|
|
44
47
|
suggestedQuestions?: Record<string, string[]>;
|
|
45
48
|
isStreaming?: boolean;
|
|
@@ -286,7 +289,7 @@ export function PolicyAIFAB({
|
|
|
286
289
|
* />
|
|
287
290
|
*/
|
|
288
291
|
export function PolicyAIPanel({
|
|
289
|
-
open,
|
|
292
|
+
open = true,
|
|
290
293
|
onClose,
|
|
291
294
|
messages = [],
|
|
292
295
|
suggestedQuestions,
|
|
@@ -405,60 +408,69 @@ export function PolicyAIPanel({
|
|
|
405
408
|
{/* ── Body ── */}
|
|
406
409
|
{(!minimised || inline) && (
|
|
407
410
|
<>
|
|
408
|
-
{/* Scroll container
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
<div className={cn(!inline && "flex-1 overflow-y-auto min-h-0")}>
|
|
411
|
+
{/* Scroll container — always flex-1 + overflow-y-auto so the footer
|
|
412
|
+
stays pinned at the bottom in both float and inline modes. */}
|
|
413
|
+
<div className="flex-1 overflow-y-auto min-h-0">
|
|
412
414
|
{isLoading ? (
|
|
413
415
|
<div className="flex flex-col justify-center h-full py-8">
|
|
414
416
|
<PolicyAIThinkingSteps steps={resolvedThinkingSteps} />
|
|
415
417
|
</div>
|
|
416
418
|
) : !isChatMode ? (
|
|
417
419
|
/* Home state — suggested questions by policy type */
|
|
418
|
-
<div className="flex flex-col">
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
<div className="flex flex-col gap-3 px-3 py-3">
|
|
421
|
+
{/* Description */}
|
|
422
|
+
<p className="text-sm text-muted-foreground">
|
|
423
|
+
Ask me about lending policies across 40+ Australian banks —
|
|
424
|
+
income, LVR, security types, serviceability, and more.
|
|
425
|
+
</p>
|
|
426
|
+
|
|
427
|
+
{/* Suggested questions — wrapped in a Card for visual separation */}
|
|
428
|
+
<div className="flex flex-col gap-1.5">
|
|
429
|
+
<p className="text-overline text-muted-foreground">
|
|
430
|
+
Suggested
|
|
423
431
|
</p>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
432
|
+
<Card className="gap-0 py-0 shadow-none overflow-hidden">
|
|
433
|
+
{/* Policy type tabs */}
|
|
434
|
+
<div className="border-b border-border px-3 pt-2">
|
|
435
|
+
<Tabs
|
|
436
|
+
value={activeTab}
|
|
437
|
+
onValueChange={(v) => setActiveTab(v as PolicyTypeTab)}
|
|
438
|
+
>
|
|
439
|
+
<TabsList
|
|
440
|
+
variant="line"
|
|
441
|
+
className="justify-start h-8 gap-0 -mb-px overflow-x-auto"
|
|
442
|
+
>
|
|
443
|
+
{(
|
|
444
|
+
Object.keys(DEFAULT_SUGGESTED) as PolicyTypeTab[]
|
|
445
|
+
).map((type) => (
|
|
446
|
+
<TabsTrigger
|
|
447
|
+
key={type}
|
|
448
|
+
value={type}
|
|
449
|
+
className="px-2 h-7 shrink-0"
|
|
450
|
+
>
|
|
451
|
+
{type}
|
|
452
|
+
</TabsTrigger>
|
|
453
|
+
))}
|
|
454
|
+
</TabsList>
|
|
455
|
+
</Tabs>
|
|
456
|
+
</div>
|
|
449
457
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
458
|
+
{/* Question list */}
|
|
459
|
+
<div className="divide-y divide-border">
|
|
460
|
+
{(
|
|
461
|
+
(suggestedQuestions ?? DEFAULT_SUGGESTED)[activeTab] ??
|
|
462
|
+
[]
|
|
463
|
+
).map((q) => (
|
|
464
|
+
<button
|
|
465
|
+
key={q}
|
|
466
|
+
onClick={() => setInputValue(q)}
|
|
467
|
+
className="w-full text-left text-sm text-foreground px-3 py-2.5 hover:bg-muted/60 transition-colors"
|
|
468
|
+
>
|
|
469
|
+
{q}
|
|
470
|
+
</button>
|
|
471
|
+
))}
|
|
472
|
+
</div>
|
|
473
|
+
</Card>
|
|
462
474
|
</div>
|
|
463
475
|
</div>
|
|
464
476
|
) : (
|
|
@@ -498,15 +510,9 @@ export function PolicyAIPanel({
|
|
|
498
510
|
</div>
|
|
499
511
|
|
|
500
512
|
{/* Footer input — hidden during loading.
|
|
501
|
-
|
|
502
|
-
Inline: sticky bottom-0 keeps it visible as the parent tab scrolls. */}
|
|
513
|
+
shrink-0 pins it to the flex column bottom in both modes. */}
|
|
503
514
|
{!isLoading && (
|
|
504
|
-
<div
|
|
505
|
-
className={cn(
|
|
506
|
-
"border-t border-border px-3 py-3 bg-card",
|
|
507
|
-
inline ? "sticky bottom-0 z-10" : "shrink-0",
|
|
508
|
-
)}
|
|
509
|
-
>
|
|
515
|
+
<div className="shrink-0 border-t border-border px-3 py-3 bg-card">
|
|
510
516
|
<ChatInputArea
|
|
511
517
|
value={inputValue}
|
|
512
518
|
onChange={setInputValue}
|
|
@@ -516,6 +522,7 @@ export function PolicyAIPanel({
|
|
|
516
522
|
disabled={isStreaming}
|
|
517
523
|
placeholder="Ask about lending policies…"
|
|
518
524
|
autoFocus={!minimised}
|
|
525
|
+
showMarkdownToolbar
|
|
519
526
|
/>
|
|
520
527
|
</div>
|
|
521
528
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Building2, Info } from "lucide-react";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
4
5
|
import { Badge } from "@/components/ui/badge";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
import {
|
|
@@ -86,12 +87,9 @@ export interface PolicyRankedListProps {
|
|
|
86
87
|
/** Bank initial avatar — consistent across all response formats. */
|
|
87
88
|
function BankAvatar({ name }: { name: string }) {
|
|
88
89
|
return (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>
|
|
93
|
-
{name.charAt(0).toUpperCase()}
|
|
94
|
-
</span>
|
|
90
|
+
<Avatar aria-hidden="true" className="size-7 shrink-0">
|
|
91
|
+
<AvatarFallback>{name.charAt(0).toUpperCase()}</AvatarFallback>
|
|
92
|
+
</Avatar>
|
|
95
93
|
);
|
|
96
94
|
}
|
|
97
95
|
|
|
@@ -140,16 +138,14 @@ function CitationBadge({ citation }: { citation: PolicyCitationItem }) {
|
|
|
140
138
|
<PopoverContent className="w-72 p-3" sideOffset={6}>
|
|
141
139
|
<div className="flex flex-col gap-2">
|
|
142
140
|
<div className="flex items-center gap-1.5 flex-wrap">
|
|
143
|
-
<span className="text-
|
|
141
|
+
<span className="text-sm font-semibold text-foreground">
|
|
144
142
|
{citation.bankName}
|
|
145
143
|
</span>
|
|
146
144
|
<Badge variant="secondary" className="text-xs px-1.5 py-0">
|
|
147
145
|
{citation.category}
|
|
148
146
|
</Badge>
|
|
149
147
|
</div>
|
|
150
|
-
<p className="text-
|
|
151
|
-
{citation.excerpt}
|
|
152
|
-
</p>
|
|
148
|
+
<p className="text-sm text-muted-foreground">{citation.excerpt}</p>
|
|
153
149
|
</div>
|
|
154
150
|
</PopoverContent>
|
|
155
151
|
</Popover>
|
|
@@ -175,7 +171,7 @@ function AnswerWithCitations({
|
|
|
175
171
|
const citation = citationMap.get(parseInt(match[1], 10));
|
|
176
172
|
if (citation) return <CitationBadge key={i} citation={citation} />;
|
|
177
173
|
return (
|
|
178
|
-
<span key={i} className="text-
|
|
174
|
+
<span key={i} className="text-caption text-muted-foreground">
|
|
179
175
|
{part}
|
|
180
176
|
</span>
|
|
181
177
|
);
|
|
@@ -385,7 +381,7 @@ export function PolicyComparisonTable({
|
|
|
385
381
|
{bank.bankName}
|
|
386
382
|
</span>
|
|
387
383
|
{bank.details && (
|
|
388
|
-
<span className="text-
|
|
384
|
+
<span className="text-caption text-muted-foreground">
|
|
389
385
|
{bank.details}
|
|
390
386
|
</span>
|
|
391
387
|
)}
|