JeCabrera commited on
Commit
89d620c
·
unverified ·
2 Parent(s): c4869b898ad257

Merge pull request #9 from squaalldev/codex/analyze-.py-files-for-react-migration-vk9f81

Browse files

FastAPI backend + React frontend integration, google-genai, and multi-stage Docker for HF Spaces

frontend/src/components/ChatWindow.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
2
 
3
  interface Message {
4
  role: 'user' | 'assistant' | 'error';
@@ -36,10 +36,47 @@ function toUiMessage(msg: ApiMessage, idx: number): Message {
36
  };
37
  }
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
40
  const [messages, setMessages] = useState<Message[]>([]);
41
  const [input, setInput] = useState('');
42
  const [loading, setLoading] = useState(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  const withUid = (path: string) => `${path}${path.includes('?') ? '&' : '?'}uid=${encodeURIComponent(uid)}`;
45
 
@@ -55,15 +92,21 @@ function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
55
 
56
  useEffect(() => {
57
  if (!chatId) {
58
- setMessages([]);
 
 
59
  return;
60
  }
61
 
 
 
62
  loadMessages(chatId).catch((error) => {
63
  console.error(error);
64
- setMessages([]);
 
 
65
  });
66
- }, [chatId]);
67
 
68
  const animateAssistantText = async (messageId: string, fullText: string) => {
69
  let partial = '';
@@ -84,6 +127,8 @@ function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
84
  };
85
 
86
  const sendToCurrentChat = async (content: string, isExample = false) => {
 
 
87
  const activeChatId = await onEnsureChat();
88
 
89
  const userMessage: Message = {
@@ -94,7 +139,6 @@ function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
94
  };
95
 
96
  setMessages((prev) => [...prev, userMessage]);
97
- setLoading(true);
98
 
99
  try {
100
  const response = await fetch(withUid(`/api/chats/${activeChatId}/messages`), {
@@ -133,6 +177,7 @@ function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
133
  ]);
134
  } finally {
135
  setLoading(false);
 
136
  }
137
  };
138
 
@@ -172,9 +217,7 @@ function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
172
  <span className="avatar">{msg.avatar}</span>
173
  <div className="content">
174
  {msg.role === 'assistant' ? (
175
- <div className="assistant-markdown">
176
- {msg.content}
177
- </div>
178
  ) : (
179
  <div className="user-plain">{msg.content}</div>
180
  )}
 
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
 
3
  interface Message {
4
  role: 'user' | 'assistant' | 'error';
 
36
  };
37
  }
38
 
39
+ function formatInline(text: string) {
40
+ const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*)/g).filter(Boolean);
41
+ return parts.map((part, idx) => {
42
+ if (part.startsWith('**') && part.endsWith('**')) {
43
+ return <strong key={`b-${idx}`}>{part.slice(2, -2)}</strong>;
44
+ }
45
+ if (part.startsWith('*') && part.endsWith('*')) {
46
+ return <em key={`i-${idx}`}>{part.slice(1, -1)}</em>;
47
+ }
48
+ return <React.Fragment key={`t-${idx}`}>{part}</React.Fragment>;
49
+ });
50
+ }
51
+
52
+ function renderFormattedText(content: string) {
53
+ return content.split('\n').map((line, idx) => (
54
+ <p key={`line-${idx}`}>{formatInline(line)}</p>
55
+ ));
56
+ }
57
+
58
  function ChatWindow({ chatId, uid, onRefreshChats, onEnsureChat }: Props) {
59
  const [messages, setMessages] = useState<Message[]>([]);
60
  const [input, setInput] = useState('');
61
  const [loading, setLoading] = useState(false);
62
+ const sendingRef = useRef(false);
63
+
64
+ const withUid = useMemo(
65
+ () => (path: string) => `${path}${path.includes('?') ? '&' : '?'}uid=${encodeURIComponent(uid)}`,
66
+ [uid],
67
+ );
68
+
69
+ const loadMessages = async (targetChatId: string) => {
70
+ const response = await fetch(withUid(`/api/chats/${targetChatId}/messages`));
71
+ if (!response.ok) {
72
+ throw new Error('No se pudo cargar el historial');
73
+ }
74
+
75
+ const data: ApiMessage[] = await response.json();
76
+ if (!sendingRef.current) {
77
+ setMessages(data.map((msg, idx) => toUiMessage(msg, idx)));
78
+ }
79
+ };
80
 
81
  const withUid = (path: string) => `${path}${path.includes('?') ? '&' : '?'}uid=${encodeURIComponent(uid)}`;
82
 
 
92
 
93
  useEffect(() => {
94
  if (!chatId) {
95
+ if (!sendingRef.current) {
96
+ setMessages([]);
97
+ }
98
  return;
99
  }
100
 
101
+ if (sendingRef.current) return;
102
+
103
  loadMessages(chatId).catch((error) => {
104
  console.error(error);
105
+ if (!sendingRef.current) {
106
+ setMessages([]);
107
+ }
108
  });
109
+ }, [chatId, withUid]);
110
 
111
  const animateAssistantText = async (messageId: string, fullText: string) => {
112
  let partial = '';
 
127
  };
128
 
129
  const sendToCurrentChat = async (content: string, isExample = false) => {
130
+ sendingRef.current = true;
131
+ setLoading(true);
132
  const activeChatId = await onEnsureChat();
133
 
134
  const userMessage: Message = {
 
139
  };
140
 
141
  setMessages((prev) => [...prev, userMessage]);
 
142
 
143
  try {
144
  const response = await fetch(withUid(`/api/chats/${activeChatId}/messages`), {
 
177
  ]);
178
  } finally {
179
  setLoading(false);
180
+ sendingRef.current = false;
181
  }
182
  };
183
 
 
217
  <span className="avatar">{msg.avatar}</span>
218
  <div className="content">
219
  {msg.role === 'assistant' ? (
220
+ <div className="assistant-markdown">{renderFormattedText(msg.content)}</div>
 
 
221
  ) : (
222
  <div className="user-plain">{msg.content}</div>
223
  )}