xertica-ui 1.3.3 → 1.3.4

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.
@@ -2,15 +2,24 @@ import React from 'react';
2
2
  import { useLocation, useNavigate } from 'react-router';
3
3
  import { Sidebar } from './Sidebar';
4
4
  import { HomeContent } from './HomeContent';
5
- import { AssistenteXertica } from './AssistenteXertica';
5
+ import { XerticaAssistant } from './ui/xertica-assistant';
6
+ import { generateDemoResponse } from '../utils/demo-responses';
6
7
  import { routes } from '../routes';
8
+ import { useLayout } from '../contexts/LayoutContext';
7
9
 
8
- interface HomePageProps {
9
- user: { email: string } | null;
10
- onLogout: () => void;
11
- }
10
+ const richSuggestions = [
11
+ { id: 'chart-1', texto: 'Ver análise de performance' },
12
+ { id: 'table-1', texto: 'Gerar relatório de projetos' },
13
+ { id: 'doc-1', texto: 'Criar documento de requisitos' },
14
+ { id: 'pod-1', texto: 'Gerar podcast do resumo diário' }
15
+ ];
16
+
17
+ const feedbackOptions = [
18
+ 'Não era o que eu procurava',
19
+ 'Informação incorreta',
20
+ 'Resposta incompleta'
21
+ ];
12
22
 
13
- import { useLayout } from '../contexts/LayoutContext';
14
23
 
15
24
  interface HomePageProps {
16
25
  user: { email: string } | null;
@@ -34,10 +43,14 @@ export function HomePage({ user, onLogout }: HomePageProps) {
34
43
  routes={routes}
35
44
  />
36
45
  <HomeContent />
37
- <AssistenteXertica
46
+ <XerticaAssistant
38
47
  isExpanded={assistenteExpanded}
39
48
  onToggle={toggleAssistente}
40
- onToggleWithTab={toggleAssistenteWithTab}
49
+ defaultTab="chat"
50
+ demoMode={true}
51
+ responseGenerator={generateDemoResponse}
52
+ richSuggestions={richSuggestions}
53
+ feedbackOptions={feedbackOptions}
41
54
  />
42
55
  </div>
43
56
  );
@@ -31,8 +31,52 @@ import {
31
31
  BarChart3,
32
32
  Bookmark,
33
33
  Maximize2,
34
- AlertCircle
34
+ AlertCircle,
35
+ ThumbsUp,
36
+ ThumbsDown,
37
+ Table as TableIcon,
38
+ ArrowLeft
35
39
  } from 'lucide-react';
40
+ import {
41
+ Bar,
42
+ BarChart,
43
+ CartesianGrid,
44
+ XAxis,
45
+ Tooltip as RechartsTooltip,
46
+ ResponsiveContainer
47
+ } from "recharts";
48
+ import {
49
+ ChartConfig,
50
+ ChartContainer,
51
+ ChartTooltip,
52
+ ChartTooltipContent,
53
+ ChartLegend,
54
+ ChartLegendContent
55
+ } from "./chart";
56
+ import {
57
+ Table,
58
+ TableBody,
59
+ TableCaption,
60
+ TableCell,
61
+ TableHead,
62
+ TableHeader,
63
+ TableRow,
64
+ } from "./table";
65
+ import {
66
+ Dialog,
67
+ DialogContent,
68
+ DialogDescription,
69
+ DialogFooter,
70
+ DialogHeader,
71
+ DialogTitle,
72
+ } from "./dialog";
73
+ import {
74
+ DropdownMenu,
75
+ DropdownMenuContent,
76
+ DropdownMenuItem,
77
+ DropdownMenuTrigger,
78
+ } from "./dropdown-menu";
79
+ import { Textarea } from "./textarea";
36
80
  import { Button } from './button';
37
81
  import { ScrollArea } from './scroll-area';
38
82
  import { Separator } from './separator';
@@ -295,6 +339,28 @@ export interface XerticaAssistantProps {
295
339
  */
296
340
  className?: string;
297
341
  mobileFloating?: boolean;
342
+ /**
343
+ * Função para gerar respostas customizadas
344
+ * @param message A mensagem do usuário
345
+ * @returns Promessa com a resposta ou a resposta diretamente
346
+ */
347
+ responseGenerator?: (message: string) => Promise<string | Partial<Message>> | string | Partial<Message>;
348
+ /**
349
+ * Sugestões ricas adicionais (exibidas ao clicar em "Mais sugestões")
350
+ */
351
+ richSuggestions?: Suggestion[];
352
+ /**
353
+ * Callback para ações de sugestão rica
354
+ */
355
+ onRichAction?: (actionId: string, actionText: string) => void;
356
+ /**
357
+ * Callback para avaliação de mensagens
358
+ */
359
+ onEvaluation?: (messageId: string, type: 'like' | 'dislike', reason?: string) => void;
360
+ /**
361
+ * Opções de feedback negativo (exibidas no dropdown de dislike)
362
+ */
363
+ feedbackOptions?: string[];
298
364
  }
299
365
 
300
366
  // ============================================================================
@@ -353,7 +419,12 @@ export function XerticaAssistant({
353
419
  className = '',
354
420
  mobileFloating = false,
355
421
  demoMode = false,
356
- customResponses = []
422
+ customResponses = [],
423
+ responseGenerator,
424
+ richSuggestions = [],
425
+ onRichAction,
426
+ onEvaluation,
427
+ feedbackOptions
357
428
  }: XerticaAssistantProps) {
358
429
  // ============================================================================
359
430
  // State Management
@@ -376,6 +447,20 @@ export function XerticaAssistant({
376
447
  content: string;
377
448
  title: string;
378
449
  } | null>(null);
450
+ const [showMoreSuggestions, setShowMoreSuggestions] = useState(false);
451
+ const [evaluationState, setEvaluationState] = useState<{
452
+ isOpen: boolean;
453
+ messageId: string | null;
454
+ type: 'dislike' | null;
455
+ category?: string | null;
456
+ reason: string;
457
+ }>({
458
+ isOpen: false,
459
+ messageId: null,
460
+ type: null,
461
+ category: null,
462
+ reason: ''
463
+ });
379
464
 
380
465
  // ============================================================================
381
466
  // Refs
@@ -446,13 +531,20 @@ export function XerticaAssistant({
446
531
  }
447
532
  };
448
533
 
449
- const handleEnviarMensagem = async () => {
450
- if (!mensagem.trim() || isProcessing) return;
534
+ const handleEnviarMensagem = async (arg?: string | ActionType) => {
535
+ let msgToSend = mensagem;
536
+
537
+ // Check if argument is a message text (from simple string) or ActionType
538
+ if (typeof arg === 'string' && !['document', 'podcast', 'search'].includes(arg)) {
539
+ msgToSend = arg;
540
+ }
541
+
542
+ if (!msgToSend.trim() || isProcessing) return;
451
543
 
452
544
  const novaMensagem: Message = {
453
545
  id: `msg-${Date.now()}`,
454
546
  type: 'user',
455
- content: mensagem,
547
+ content: msgToSend,
456
548
  timestamp: new Date(),
457
549
  isFavorite: false,
458
550
  };
@@ -461,16 +553,19 @@ export function XerticaAssistant({
461
553
 
462
554
 
463
555
  if (onSendMessage) {
464
- onSendMessage(mensagem);
556
+ onSendMessage(msgToSend);
465
557
  }
466
558
 
467
- if (demoMode) {
468
- // Simular resposta no modo demo se não houver handler externo ou se o handler for apenas para log/side-effect
469
- // Mas cuidado para não duplicar se onSendMessage também gerar resposta.
470
- // Assumindo que demoMode é autossuficiente.
471
- const mensagemAtual = mensagem;
472
- setTimeout(() => {
473
- const resposta = gerarResposta(mensagemAtual, customResponses);
559
+ if (demoMode || responseGenerator) {
560
+ const mensagemAtual = msgToSend;
561
+ setTimeout(async () => {
562
+ let resposta: string | Partial<Message>;
563
+
564
+ if (responseGenerator) {
565
+ resposta = await responseGenerator(mensagemAtual);
566
+ } else {
567
+ resposta = gerarResposta(mensagemAtual, customResponses);
568
+ }
474
569
 
475
570
  let novaMensagemIA: Message = {
476
571
  id: `msg-${Date.now()}-ia`,
@@ -637,6 +732,48 @@ export function XerticaAssistant({
637
732
  }, 1500);
638
733
  };
639
734
 
735
+ const handleRichSuggestionClick = (suggestion: Suggestion) => {
736
+ if (onRichAction) {
737
+ onRichAction(suggestion.id, suggestion.texto);
738
+ } else {
739
+ // Fallback: send as message
740
+ handleEnviarMensagem(suggestion.texto);
741
+ }
742
+ setShowMoreSuggestions(false);
743
+ };
744
+
745
+ const handleEvaluationClick = (messageId: string, type: 'like' | 'dislike') => {
746
+ if (type === 'like') {
747
+ if (onEvaluation) onEvaluation(messageId, 'like');
748
+ // Update local state
749
+ setMensagens(prev => prev.map(m => m.id === messageId ? { ...m, evaluation: 'like' } : m));
750
+ }
751
+ };
752
+
753
+ const openFeedbackDialog = (messageId: string, category: string | null) => {
754
+ setEvaluationState({
755
+ isOpen: true,
756
+ messageId,
757
+ type: 'dislike',
758
+ category,
759
+ reason: ''
760
+ });
761
+ };
762
+
763
+ const handleSubmitDislike = () => {
764
+ if (evaluationState.messageId && onEvaluation) {
765
+ const finalReason = evaluationState.category
766
+ ? (evaluationState.reason ? `${evaluationState.category}: ${evaluationState.reason}` : evaluationState.category)
767
+ : evaluationState.reason;
768
+
769
+ onEvaluation(evaluationState.messageId, 'dislike', finalReason);
770
+
771
+ // Update local state
772
+ setMensagens(prev => prev.map(m => m.id === evaluationState.messageId ? { ...m, evaluation: 'dislike', evaluationReason: finalReason } : m));
773
+ }
774
+ setEvaluationState({ isOpen: false, messageId: null, type: null, category: null, reason: '' });
775
+ };
776
+
640
777
  // ============================================================================
641
778
  // Computations
642
779
  // ============================================================================
@@ -986,21 +1123,53 @@ export function XerticaAssistant({
986
1123
  {sugestoes.map((sugestao) => (
987
1124
  <button
988
1125
  key={sugestao.id}
989
- onClick={() => setMensagem(sugestao.texto)}
1126
+ onClick={() => handleEnviarMensagem(sugestao.texto)}
990
1127
  className="w-full p-3 text-left rounded-[var(--radius-card)] bg-muted text-foreground transition-colors duration-200 hover:bg-muted/80"
991
1128
  >
992
1129
  {sugestao.texto}
993
1130
  </button>
994
1131
  ))}
995
1132
 
996
- <Button
997
- variant="ghost"
998
- size="sm"
999
- className="w-full justify-start text-muted-foreground"
1000
- >
1001
- <MoreHorizontal className="w-4 h-4 mr-2" />
1002
- Mais sugestões
1003
- </Button>
1133
+ {!showMoreSuggestions ? (
1134
+ <Button
1135
+ variant="ghost"
1136
+ size="sm"
1137
+ onClick={() => setShowMoreSuggestions(true)}
1138
+ className="w-full justify-start text-muted-foreground"
1139
+ >
1140
+ <MoreHorizontal className="w-4 h-4 mr-2" />
1141
+ Mais sugestões
1142
+ </Button>
1143
+ ) : (
1144
+ <div className="space-y-2 pt-2 border-t border-border mt-2 animate-in slide-in-from-top-2">
1145
+ {richSuggestions.length > 0 ? (
1146
+ richSuggestions.map((sugestao) => (
1147
+ <button
1148
+ key={sugestao.id}
1149
+ onClick={() => handleRichSuggestionClick(sugestao)}
1150
+ className="w-full p-3 text-left rounded-[var(--radius-card)] bg-muted/50 border border-border text-foreground transition-all duration-200 hover:bg-primary/5 hover:border-primary/50 group"
1151
+ >
1152
+ <div className="flex items-center gap-2">
1153
+ {sugestao.id.includes('chart') && <BarChart3 className="w-4 h-4 text-primary" />}
1154
+ {sugestao.id.includes('table') && <TableIcon className="w-4 h-4 text-primary" />}
1155
+ <span className="font-medium">{sugestao.texto}</span>
1156
+ </div>
1157
+ </button>
1158
+ ))
1159
+ ) : (
1160
+ <p className="text-sm text-muted-foreground text-center py-2">Nenhuma sugestão extra.</p>
1161
+ )}
1162
+ <Button
1163
+ variant="ghost"
1164
+ size="sm"
1165
+ onClick={() => setShowMoreSuggestions(false)}
1166
+ className="w-full justify-center text-muted-foreground mt-2"
1167
+ >
1168
+ <ChevronLeft className="w-4 h-4 mr-2" />
1169
+ Voltar
1170
+ </Button>
1171
+ </div>
1172
+ )}
1004
1173
  </div>
1005
1174
  </div>
1006
1175
  ) : (
@@ -1105,6 +1274,54 @@ export function XerticaAssistant({
1105
1274
  </>
1106
1275
  )}
1107
1276
 
1277
+ {/* Chart Rendering */}
1278
+ {msg.chartData && msg.chartConfig && (
1279
+ <div className="mt-4 w-full h-[300px] min-w-[300px]">
1280
+ <ChartContainer config={msg.chartConfig} className="h-full w-full">
1281
+ <BarChart accessibilityLayer data={msg.chartData}>
1282
+ <CartesianGrid vertical={false} />
1283
+ <XAxis
1284
+ dataKey="month"
1285
+ tickLine={false}
1286
+ tickMargin={10}
1287
+ axisLine={false}
1288
+ tickFormatter={(value) => value.slice(0, 3)}
1289
+ />
1290
+ <ChartTooltip content={<ChartTooltipContent />} />
1291
+ <ChartLegend content={<ChartLegendContent />} />
1292
+ {Object.keys(msg.chartConfig).map((key) => (
1293
+ <Bar key={key} dataKey={key} fill={`var(--color-${key})`} radius={4} />
1294
+ ))}
1295
+ </BarChart>
1296
+ </ChartContainer>
1297
+ </div>
1298
+ )}
1299
+
1300
+ {/* Table Rendering */}
1301
+ {msg.tableData && (
1302
+ <div className="mt-4 w-full border rounded-[var(--radius)] overflow-hidden">
1303
+ <Table>
1304
+ {msg.tableData.caption && <TableCaption>{msg.tableData.caption}</TableCaption>}
1305
+ <TableHeader>
1306
+ <TableRow>
1307
+ {msg.tableData.headers.map((header, i) => (
1308
+ <TableHead key={i}>{header}</TableHead>
1309
+ ))}
1310
+ </TableRow>
1311
+ </TableHeader>
1312
+ <TableBody>
1313
+ {msg.tableData.rows.map((row, i) => (
1314
+ <TableRow key={i}>
1315
+ {row.map((cell, j) => (
1316
+ <TableCell key={j}>{cell}</TableCell>
1317
+ ))}
1318
+ </TableRow>
1319
+ ))}
1320
+ </TableBody>
1321
+ </Table>
1322
+ </div>
1323
+ )}
1324
+
1108
1325
  {/* Document Preview */}
1109
1326
  {msg.attachmentType === 'document' && msg.documentContent && (
1110
1327
  <div
@@ -1291,14 +1508,6 @@ export function XerticaAssistant({
1291
1508
  {msg.timestamp.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
1292
1509
  </span>
1293
1510
 
1294
- <Button
1295
- variant="ghost"
1296
- size="sm"
1297
- onClick={() => handleToggleFavorite(msg.id)}
1298
- className={`h-6 w-6 p-0 ${msg.isFavorite ? 'text-destructive' : 'text-muted-foreground'}`}
1299
- >
1300
- <Heart className={`w-3 h-3 ${msg.isFavorite ? 'fill-current' : ''}`} />
1301
- </Button>
1302
1511
 
1303
1512
  {msg.type === 'assistant' && msg.attachmentType !== 'podcast' && (
1304
1513
  <Button
@@ -1317,6 +1526,63 @@ export function XerticaAssistant({
1317
1526
  </Button>
1318
1527
  )}
1319
1528
 
1529
+ {msg.type === 'assistant' && (
1530
+ <div className="flex items-center gap-1 border-l border-border pl-2 ml-1">
1531
+ <Button
1532
+ variant="ghost"
1533
+ size="icon"
1534
+ className={cn(
1535
+ "h-6 w-6 rounded-full hover:bg-green-100 dark:hover:bg-green-900/20 hover:text-green-600",
1536
+ msg.evaluation === 'like' && "text-green-600 bg-green-100 dark:bg-green-900/20"
1537
+ )}
1538
+ onClick={() => handleEvaluationClick(msg.id, 'like')}
1539
+ title="Gostei"
1540
+ >
1541
+ <ThumbsUp className="h-3.5 w-3.5" />
1542
+ </Button>
1543
+
1544
+ <DropdownMenu>
1545
+ <DropdownMenuTrigger asChild>
1546
+ <Button
1547
+ variant="ghost"
1548
+ size="icon"
1549
+ className={cn(
1550
+ "h-6 w-6 rounded-full hover:bg-red-100 dark:hover:bg-red-900/20 hover:text-red-600",
1551
+ msg.evaluation === 'dislike' && "text-red-600 bg-red-100 dark:bg-red-900/20"
1552
+ )}
1553
+ title="Não gostei"
1554
+ >
1555
+ <ThumbsDown className="h-3.5 w-3.5" />
1556
+ </Button>
1557
+ </DropdownMenuTrigger>
1558
+ <DropdownMenuContent align="start">
1559
+ {feedbackOptions && feedbackOptions.length > 0 ? (
1560
+ feedbackOptions.map((option, idx) => (
1561
+ <DropdownMenuItem key={idx} onClick={() => openFeedbackDialog(msg.id, option)}>
1562
+ {option}
1563
+ </DropdownMenuItem>
1564
+ ))
1565
+ ) : (
1566
+ <>
1567
+ <DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Não era o que eu procurava')}>
1568
+ Não era o que eu procurava
1569
+ </DropdownMenuItem>
1570
+ <DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Informação incorreta')}>
1571
+ Informação incorreta
1572
+ </DropdownMenuItem>
1573
+ <DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Resposta incompleta')}>
1574
+ Resposta incompleta
1575
+ </DropdownMenuItem>
1576
+ </>
1577
+ )}
1578
+ <DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, null)}>
1579
+ Outros...
1580
+ </DropdownMenuItem>
1581
+ </DropdownMenuContent>
1582
+ </DropdownMenu>
1583
+ </div>
1584
+ )}
1585
+
1320
1586
  <Button
1321
1587
  variant="ghost"
1322
1588
  size="sm"
@@ -1469,6 +1735,39 @@ export function XerticaAssistant({
1469
1735
  )}
1470
1736
  </AnimatePresence>
1471
1737
  </div>
1738
+
1739
+ {/* Feedback Dialog */}
1740
+ <Dialog open={evaluationState.isOpen} onOpenChange={(open) => !open && setEvaluationState(prev => ({ ...prev, isOpen: false }))}>
1741
+ <DialogContent className="sm:max-w-[600px]">
1742
+ <DialogHeader>
1743
+ <DialogTitle className="pr-8">
1744
+ {evaluationState.category ? `Enviar feedback: ${evaluationState.category}` : 'Enviar feedback'}
1745
+ </DialogTitle>
1746
+ <DialogDescription>
1747
+ {evaluationState.category
1748
+ ? "Gostaria de adicionar algum comentário? (Opcional)"
1749
+ : "Conte-nos por que essa resposta não foi útil para que possamos melhorar."}
1750
+ </DialogDescription>
1751
+ </DialogHeader>
1752
+ <div className="grid gap-4 py-4 px-6">
1753
+ <Textarea
1754
+ className="min-h-[100px]"
1755
+ placeholder={evaluationState.category ? "Comentário adicional..." : "Descreva o motivo..."}
1756
+ value={evaluationState.reason}
1757
+ onChange={(e) => setEvaluationState(prev => ({ ...prev, reason: e.target.value }))}
1758
+ rows={4}
1759
+ />
1760
+ </div>
1761
+ <DialogFooter>
1762
+ <Button variant="outline" onClick={() => setEvaluationState(prev => ({ ...prev, isOpen: false }))}>
1763
+ Cancelar
1764
+ </Button>
1765
+ <Button onClick={handleSubmitDislike} disabled={!evaluationState.category && !evaluationState.reason.trim()}>
1766
+ {evaluationState.category ? 'Confirmar e Enviar' : 'Enviar Feedback'}
1767
+ </Button>
1768
+ </DialogFooter>
1769
+ </DialogContent>
1770
+ </Dialog>
1472
1771
  </>
1473
1772
  );
1474
1773
  }
@@ -4,11 +4,5 @@ interface HomePageProps {
4
4
  } | null;
5
5
  onLogout: () => void;
6
6
  }
7
- interface HomePageProps {
8
- user: {
9
- email: string;
10
- } | null;
11
- onLogout: () => void;
12
- }
13
7
  export declare function HomePage({ user, onLogout }: HomePageProps): import("react/jsx-runtime").JSX.Element;
14
8
  export {};
@@ -180,6 +180,28 @@ export interface XerticaAssistantProps {
180
180
  */
181
181
  className?: string;
182
182
  mobileFloating?: boolean;
183
+ /**
184
+ * Função para gerar respostas customizadas
185
+ * @param message A mensagem do usuário
186
+ * @returns Promessa com a resposta ou a resposta diretamente
187
+ */
188
+ responseGenerator?: (message: string) => Promise<string | Partial<Message>> | string | Partial<Message>;
189
+ /**
190
+ * Sugestões ricas adicionais (exibidas ao clicar em "Mais sugestões")
191
+ */
192
+ richSuggestions?: Suggestion[];
193
+ /**
194
+ * Callback para ações de sugestão rica
195
+ */
196
+ onRichAction?: (actionId: string, actionText: string) => void;
197
+ /**
198
+ * Callback para avaliação de mensagens
199
+ */
200
+ onEvaluation?: (messageId: string, type: 'like' | 'dislike', reason?: string) => void;
201
+ /**
202
+ * Opções de feedback negativo (exibidas no dropdown de dislike)
203
+ */
204
+ feedbackOptions?: string[];
183
205
  }
184
206
  /**
185
207
  * XerticaAssistant - Assistente de IA completo com chat, histórico e favoritos
@@ -212,4 +234,4 @@ export interface XerticaAssistantProps {
212
234
  * />
213
235
  * ```
214
236
  */
215
- export declare function XerticaAssistant({ mode, isExpanded: controlledIsExpanded, onToggle, defaultTab, showApiWarning, apiKey, onNavigateSettings, onNavigateFullPage, userName, initialMessages, savedConversations, suggestions: propSuggestions, onSendMessage, onFileAttach, isProcessing, width, height, className, mobileFloating, demoMode, customResponses }: XerticaAssistantProps): import("react/jsx-runtime").JSX.Element;
237
+ export declare function XerticaAssistant({ mode, isExpanded: controlledIsExpanded, onToggle, defaultTab, showApiWarning, apiKey, onNavigateSettings, onNavigateFullPage, userName, initialMessages, savedConversations, suggestions: propSuggestions, onSendMessage, onFileAttach, isProcessing, width, height, className, mobileFloating, demoMode, customResponses, responseGenerator, richSuggestions, onRichAction, onEvaluation, feedbackOptions }: XerticaAssistantProps): import("react/jsx-runtime").JSX.Element;