mirror of
https://github.com/aimingmed/aimingmed-ai.git
synced 2026-02-08 16:37:29 +08:00
update working
This commit is contained in:
parent
896d38e79f
commit
4e5f9f57c2
@ -435,10 +435,58 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
"question": data_json[0]["content"]
|
"question": data_json[0]["content"]
|
||||||
}
|
}
|
||||||
async for chunk in app.astream(inputs):
|
async for chunk in app.astream(inputs):
|
||||||
await manager.send_personal_message(
|
# Determine if chunk is intermediate or final
|
||||||
json.dumps({"type": "message", "payload": chunk.get("content", str(chunk))}),
|
if isinstance(chunk, dict):
|
||||||
websocket,
|
if len(chunk) == 1:
|
||||||
)
|
step_name = list(chunk.keys())[0]
|
||||||
|
step_value = chunk[step_name]
|
||||||
|
# Check if this step contains the final answer
|
||||||
|
if isinstance(step_value, dict) and 'generation' in step_value:
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({
|
||||||
|
"type": "final",
|
||||||
|
"title": "Answer",
|
||||||
|
"payload": step_value['generation']
|
||||||
|
}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({
|
||||||
|
"type": "intermediate",
|
||||||
|
"title": step_name.replace('_', ' ').title(),
|
||||||
|
"payload": str(step_value)
|
||||||
|
}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
elif 'generation' in chunk:
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({
|
||||||
|
"type": "final",
|
||||||
|
"title": "Answer",
|
||||||
|
"payload": chunk['generation']
|
||||||
|
}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({
|
||||||
|
"type": "intermediate",
|
||||||
|
"title": "Step",
|
||||||
|
"payload": str(chunk)[:500]
|
||||||
|
}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Fallback for non-dict chunks
|
||||||
|
await manager.send_personal_message(
|
||||||
|
json.dumps({
|
||||||
|
"type": "intermediate",
|
||||||
|
"title": "Step",
|
||||||
|
"payload": str(chunk)[:500]
|
||||||
|
}),
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
# Send a final 'done' message to signal completion
|
# Send a final 'done' message to signal completion
|
||||||
await manager.send_personal_message(
|
await manager.send_personal_message(
|
||||||
json.dumps({"type": "done"}),
|
json.dumps({"type": "done"}),
|
||||||
|
|||||||
@ -33,6 +33,8 @@ You must not use any information that is not present in the provided context to
|
|||||||
If you don't know the answer, just say that you don't know.\n
|
If you don't know the answer, just say that you don't know.\n
|
||||||
Provide the answer in a concise and organized manner. \n
|
Provide the answer in a concise and organized manner. \n
|
||||||
|
|
||||||
|
Reformat the answer in a human-readable markdown format, include underlined or new lines to improve readability if needed.\n
|
||||||
|
|
||||||
Question: {question} \n
|
Question: {question} \n
|
||||||
Context: {context} \n
|
Context: {context} \n
|
||||||
Answer:
|
Answer:
|
||||||
|
|||||||
@ -12,7 +12,7 @@ ARG ENV_FILE=.env.test
|
|||||||
COPY ${ENV_FILE} /usr/src/app/.env
|
COPY ${ENV_FILE} /usr/src/app/.env
|
||||||
|
|
||||||
# Copy dependency files and install dependencies
|
# Copy dependency files and install dependencies
|
||||||
RUN npm install && npm i --save-dev @types/jest
|
RUN npm install && npm install --save-dev @types/jest
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD [ "npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "80" ]
|
CMD [ "npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "80" ]
|
||||||
1460
app/frontend/package-lock.json
generated
1460
app/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"daisyui": "^5.0.17",
|
"daisyui": "^5.0.17",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"react-markdown": "^10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
const BASE_DOMAIN_NAME_PORT = import.meta.env.REACT_APP_DOMAIN_NAME_PORT || 'localhost:8004';
|
const BASE_DOMAIN_NAME_PORT = import.meta.env.REACT_APP_DOMAIN_NAME_PORT || 'localhost:8004';
|
||||||
|
|
||||||
@ -8,58 +9,77 @@ interface Message {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ChatTurn {
|
||||||
|
question: string;
|
||||||
|
intermediateMessages: { title: string; payload: string }[];
|
||||||
|
finalAnswer: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
showIntermediate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
|
const [intermediateMessages, setIntermediateMessages] = useState<{title: string, payload: string}[]>([]);
|
||||||
|
const [finalAnswer, setFinalAnswer] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [showIntermediate, setShowIntermediate] = useState(false);
|
||||||
const [newMessage, setNewMessage] = useState('');
|
const [newMessage, setNewMessage] = useState('');
|
||||||
const [socket, setSocket] = useState<WebSocket | null>(null);
|
const [socket, setSocket] = useState<WebSocket | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const mounted = useRef(false);
|
const mounted = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
const ws = new WebSocket(`ws://${BASE_DOMAIN_NAME_PORT}/ws`);
|
const ws = new WebSocket(`ws://${BASE_DOMAIN_NAME_PORT}/ws`);
|
||||||
setSocket(ws);
|
setSocket(ws);
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('WebSocket connection opened');
|
console.log('WebSocket connection opened');
|
||||||
};
|
};
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.type === 'message' && data.payload && mounted.current) {
|
if (data.type === 'message' && data.payload && mounted.current) {
|
||||||
setMessages((prevMessages) => {
|
// legacy support, treat as final
|
||||||
const lastMessage = prevMessages[prevMessages.length - 1];
|
setMessages((prevMessages) => {
|
||||||
if (lastMessage && lastMessage.sender === 'bot') {
|
const lastMessage = prevMessages[prevMessages.length - 1];
|
||||||
return [...prevMessages.slice(0, -1), { ...lastMessage, text: lastMessage.text + data.payload }];
|
if (lastMessage && lastMessage.sender === 'bot') {
|
||||||
} else {
|
return [...prevMessages.slice(0, -1), { ...lastMessage, text: lastMessage.text + data.payload }];
|
||||||
return [...prevMessages, { sender: 'bot', text: data.payload }];
|
} else {
|
||||||
|
return [...prevMessages, { sender: 'bot', text: data.payload }];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setFinalAnswer(data.payload);
|
||||||
|
} else if (data.type === 'intermediate') {
|
||||||
|
setIntermediateMessages((prev) => [...prev, { title: data.title, payload: data.payload }]);
|
||||||
|
} else if (data.type === 'final') {
|
||||||
|
setFinalAnswer(data.payload);
|
||||||
|
} else if (data.type === 'done') {
|
||||||
|
setIsLoading(false);
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected message format:', data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing message:', error);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
} else if (data.type === 'done') {
|
ws.onclose = () => {
|
||||||
setIsLoading(false);
|
console.log('WebSocket connection closed');
|
||||||
} else {
|
};
|
||||||
console.error('Unexpected message format:', data);
|
ws.onerror = (error) => {
|
||||||
}
|
console.error('WebSocket error:', error);
|
||||||
} catch (error) {
|
};
|
||||||
console.error('Error parsing message:', error);
|
return () => {
|
||||||
}
|
mounted.current = false;
|
||||||
};
|
ws.close();
|
||||||
ws.onclose = () => {
|
};
|
||||||
console.log('WebSocket connection closed');
|
}, []);
|
||||||
};
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.error('WebSocket error:', error);
|
|
||||||
};
|
|
||||||
return () => {
|
|
||||||
mounted.current = false;
|
|
||||||
ws.close();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (newMessage.trim() !== '') {
|
if (newMessage.trim() !== '' && !isLoading) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const message = [{ role: 'user', content: newMessage }];
|
setIntermediateMessages([]);
|
||||||
|
setFinalAnswer(null);
|
||||||
setMessages((prevMessages) => [...prevMessages, { sender: 'user', text: newMessage }]);
|
setMessages((prevMessages) => [...prevMessages, { sender: 'user', text: newMessage }]);
|
||||||
|
const message = [{ role: 'user', content: newMessage }];
|
||||||
socket?.send(JSON.stringify(message));
|
socket?.send(JSON.stringify(message));
|
||||||
setNewMessage('');
|
setNewMessage('');
|
||||||
}
|
}
|
||||||
@ -76,6 +96,52 @@ const App: React.FC = () => {
|
|||||||
{msg.text}
|
{msg.text}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{/* Status box for intermediate steps */}
|
||||||
|
{intermediateMessages.length > 0 && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="bg-blue-50 border border-blue-300 rounded-lg p-3 shadow-sm flex items-center">
|
||||||
|
{/* Spinner icon */}
|
||||||
|
{isLoading && (
|
||||||
|
<svg className="animate-spin h-5 w-5 text-blue-500 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<span className="font-semibold text-blue-700 mr-2">Working on:</span>
|
||||||
|
{/* Key steps summary */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{intermediateMessages.map((msg, idx) => (
|
||||||
|
<span key={idx} className="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs font-medium border border-blue-200">
|
||||||
|
{msg.title}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="ml-auto text-xs text-blue-600 underline focus:outline-none"
|
||||||
|
onClick={() => setShowIntermediate((v) => !v)}
|
||||||
|
>
|
||||||
|
{showIntermediate ? 'Hide details' : 'Show details'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/* Expanded details */}
|
||||||
|
{showIntermediate && (
|
||||||
|
<div className="bg-white border border-blue-200 rounded-b-lg p-3 mt-1 text-xs max-h-64 overflow-y-auto">
|
||||||
|
{intermediateMessages.map((msg, idx) => (
|
||||||
|
<div key={idx} className="mb-3">
|
||||||
|
<div className="font-bold text-blue-700 mb-1">{msg.title}</div>
|
||||||
|
<pre className="whitespace-pre-wrap break-words text-gray-800">{msg.payload}</pre>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Final answer for this question */}
|
||||||
|
{finalAnswer && (
|
||||||
|
<div className="p-4 rounded-lg mb-2 bg-gray-200 text-gray-800 prose max-w-none">
|
||||||
|
<ReactMarkdown>{finalAnswer}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-gray-300">
|
<div className="p-4 border-t border-gray-300">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user