Spaces:
Sleeping
Sleeping
Commit
·
9f543c6
1
Parent(s):
5baa7eb
Updates to model, and UI
Browse files
backend/main.py
CHANGED
@@ -657,11 +657,11 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
657 |
gen_pipe = pipeline(
|
658 |
"text2text-generation",#"text-generation",#"text2text-generation",
|
659 |
model=llm_model,
|
660 |
-
tokenizer=AutoTokenizer.from_pretrained(settings.llm_model),
|
661 |
device=-1, # force CPU
|
662 |
max_new_tokens=256,
|
663 |
do_sample=True,
|
664 |
-
temperature=0.
|
665 |
)
|
666 |
# Wrap in LangChain's HuggingFacePipeline
|
667 |
llm = HuggingFacePipeline(pipeline=gen_pipe)
|
@@ -715,7 +715,11 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
715 |
fs = gcsfs.GCSFileSystem()
|
716 |
with fs.open(settings.parquet_path, "rb") as f:
|
717 |
df = pl.read_parquet(f)
|
718 |
-
|
|
|
|
|
|
|
|
|
719 |
# lowercase for filtering
|
720 |
for col in ("title", "status", "legalBasis","fundingScheme"):
|
721 |
df = df.with_columns(pl.col(col).str.to_lowercase().alias(f"_{col}_lc"))
|
|
|
657 |
gen_pipe = pipeline(
|
658 |
"text2text-generation",#"text-generation",#"text2text-generation",
|
659 |
model=llm_model,
|
660 |
+
tokenizer=AutoTokenizer.from_pretrained(settings.llm_model,use_fast= False),
|
661 |
device=-1, # force CPU
|
662 |
max_new_tokens=256,
|
663 |
do_sample=True,
|
664 |
+
temperature=0.7,
|
665 |
)
|
666 |
# Wrap in LangChain's HuggingFacePipeline
|
667 |
llm = HuggingFacePipeline(pipeline=gen_pipe)
|
|
|
715 |
fs = gcsfs.GCSFileSystem()
|
716 |
with fs.open(settings.parquet_path, "rb") as f:
|
717 |
df = pl.read_parquet(f)
|
718 |
+
|
719 |
+
df = df.with_columns(
|
720 |
+
pl.col("id").cast(pl.Int64).alias("id")
|
721 |
+
)
|
722 |
+
|
723 |
# lowercase for filtering
|
724 |
for col in ("title", "status", "legalBasis","fundingScheme"):
|
725 |
df = df.with_columns(pl.col(col).str.to_lowercase().alias(f"_{col}_lc"))
|
frontend/src/components/ProjectDetails.tsx
CHANGED
@@ -97,7 +97,7 @@ export default function ProjectDetails({
|
|
97 |
: [51.505, -0.09];
|
98 |
|
99 |
// format date string to YYYY-MM-DD
|
100 |
-
const fmtDate = (iso?: string) => iso ? new Date(iso).toISOString().split('T')[0] : '-';
|
101 |
|
102 |
// format numbers with two decimals
|
103 |
const fmtNum = (num: number | null | undefined): string =>
|
@@ -125,11 +125,11 @@ export default function ProjectDetails({
|
|
125 |
<Text fontWeight="bold">Acronym</Text>
|
126 |
<Text>{project.acronym}</Text>
|
127 |
</Box>
|
128 |
-
<Box><Text fontWeight="bold">Start Date</Text><Text>{
|
129 |
-
<Box><Text fontWeight="bold">End Date</Text><Text>{
|
130 |
<Box><Text fontWeight="bold">Funding (EC max)</Text><Text>€{fmtNum(project.ecMaxContribution)}</Text></Box>
|
131 |
<Box><Text fontWeight="bold">Total Cost</Text><Text>€{fmtNum(project.totalCost)}</Text></Box>
|
132 |
-
<Box><Text fontWeight="bold">Funding Scheme</Text><Text>project.fundingScheme</Text></Box>
|
133 |
<Box>
|
134 |
<Text fontWeight="bold">Legal Basis</Text>
|
135 |
<Text>{project.legalBasis}</Text>
|
|
|
97 |
: [51.505, -0.09];
|
98 |
|
99 |
// format date string to YYYY-MM-DD
|
100 |
+
//const fmtDate = (iso?: string) => iso ? new Date(iso).toISOString().split('T')[0] : '-';
|
101 |
|
102 |
// format numbers with two decimals
|
103 |
const fmtNum = (num: number | null | undefined): string =>
|
|
|
125 |
<Text fontWeight="bold">Acronym</Text>
|
126 |
<Text>{project.acronym}</Text>
|
127 |
</Box>
|
128 |
+
<Box><Text fontWeight="bold">Start Date</Text><Text>{new Date(project.startDate).toISOString().slice(0,10)}</Text></Box>
|
129 |
+
<Box><Text fontWeight="bold">End Date</Text><Text>{new Date(project.endDate).toISOString().slice(0,10)}</Text></Box>
|
130 |
<Box><Text fontWeight="bold">Funding (EC max)</Text><Text>€{fmtNum(project.ecMaxContribution)}</Text></Box>
|
131 |
<Box><Text fontWeight="bold">Total Cost</Text><Text>€{fmtNum(project.totalCost)}</Text></Box>
|
132 |
+
<Box><Text fontWeight="bold">Funding Scheme</Text><Text>{project.fundingScheme}</Text></Box>
|
133 |
<Box>
|
134 |
<Text fontWeight="bold">Legal Basis</Text>
|
135 |
<Text>{project.legalBasis}</Text>
|
frontend/src/components/ProjectExplorer.tsx
CHANGED
@@ -63,6 +63,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
63 |
setQuestion,
|
64 |
chatHistory,
|
65 |
askChatbot,
|
|
|
66 |
messagesEndRef,
|
67 |
}) => {
|
68 |
const [filterOpts, setFilterOpts] = useState<FilterOptions>({
|
@@ -84,7 +85,7 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
84 |
if (orgFilter) params.set("organization", orgFilter);
|
85 |
if (countryFilter) params.set("country", countryFilter);
|
86 |
if (search) params.set("search", search);
|
87 |
-
if (idFilter.length >= MIN_SEARCH_LEN) params.set("
|
88 |
if (fundingSchemeFilter) params.set("fundingScheme", fundingSchemeFilter);
|
89 |
params.set("sortField", sortField);
|
90 |
params.set("sortOrder", sortOrder);
|
@@ -251,50 +252,74 @@ const ProjectExplorer: React.FC<ProjectExplorerProps> = ({
|
|
251 |
|
252 |
{/* Right Pane: Assistant */}
|
253 |
<Box
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
Assistant
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
</Box>
|
288 |
-
<HStack>
|
289 |
-
<Input
|
290 |
-
placeholder="Ask something..."
|
291 |
-
value={question}
|
292 |
-
onChange={(e) => setQuestion(e.target.value)}
|
293 |
-
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); askChatbot(); } }}
|
294 |
-
/>
|
295 |
-
<Button onClick={askChatbot} colorScheme="blue" aria-label="Ask the chatbot">Send</Button>
|
296 |
-
</HStack>
|
297 |
-
</Box>
|
298 |
</Flex>
|
299 |
);
|
300 |
};
|
|
|
63 |
setQuestion,
|
64 |
chatHistory,
|
65 |
askChatbot,
|
66 |
+
loading,
|
67 |
messagesEndRef,
|
68 |
}) => {
|
69 |
const [filterOpts, setFilterOpts] = useState<FilterOptions>({
|
|
|
85 |
if (orgFilter) params.set("organization", orgFilter);
|
86 |
if (countryFilter) params.set("country", countryFilter);
|
87 |
if (search) params.set("search", search);
|
88 |
+
if (idFilter.length >= MIN_SEARCH_LEN) params.set("proj_id", idFilter);
|
89 |
if (fundingSchemeFilter) params.set("fundingScheme", fundingSchemeFilter);
|
90 |
params.set("sortField", sortField);
|
91 |
params.set("sortOrder", sortOrder);
|
|
|
252 |
|
253 |
{/* Right Pane: Assistant */}
|
254 |
<Box
|
255 |
+
w={{ base: "100%", md: "30%" }}
|
256 |
+
bg="gray.50"
|
257 |
+
p={4}
|
258 |
+
borderRadius="md"
|
259 |
+
height="500px"
|
260 |
+
display="flex"
|
261 |
+
flexDirection="column"
|
262 |
+
mt={6} // spacing from above
|
263 |
+
>
|
264 |
+
<Heading size="sm" mb={2}>Assistant</Heading>
|
265 |
+
<Text fontSize="xs" color="gray.500" mb={3}>
|
266 |
+
⚠️ The model may occasionally produce incorrect or misleading answers.
|
267 |
+
</Text>
|
268 |
+
<Box flex={1} overflowY="auto" mb={4}>
|
269 |
+
<VStack spacing={3} align="stretch">
|
270 |
+
{chatHistory.map((msg, i) => (
|
271 |
+
<HStack
|
272 |
+
key={i}
|
273 |
+
alignSelf={msg.role === "user" ? "flex-end" : "flex-start"}
|
274 |
+
maxW="90%"
|
275 |
+
>
|
276 |
+
{msg.role === "assistant" && <Avatar size="sm" name="Bot" />}
|
277 |
+
<Box>
|
278 |
+
<Text
|
279 |
+
fontSize="sm"
|
280 |
+
bg={msg.role === "user" ? "blue.100" : "gray.200"}
|
281 |
+
px={3}
|
282 |
+
py={2}
|
283 |
+
borderRadius="md"
|
284 |
+
>
|
285 |
+
{msg.content}
|
286 |
+
{msg.content === "Generating answer..." && (
|
287 |
+
<Spinner size="xs" ml={2} />
|
288 |
+
)}
|
289 |
+
</Text>
|
290 |
+
</Box>
|
291 |
+
{msg.role === "user" && (
|
292 |
+
<Avatar size="sm" name="You" bg="blue.300" />
|
293 |
+
)}
|
294 |
+
</HStack>
|
295 |
+
))}
|
296 |
+
<div ref={messagesEndRef} />
|
297 |
+
</VStack>
|
298 |
+
</Box>
|
299 |
+
<HStack>
|
300 |
+
<Input
|
301 |
+
placeholder="Ask something..."
|
302 |
+
value={question}
|
303 |
+
onChange={(e) => setQuestion(e.target.value)}
|
304 |
+
onKeyDown={(e) => {
|
305 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
306 |
+
e.preventDefault();
|
307 |
+
askChatbot();
|
308 |
+
}
|
309 |
+
}}
|
310 |
+
isDisabled={loading}
|
311 |
+
/>
|
312 |
+
<Button
|
313 |
+
onClick={askChatbot}
|
314 |
+
colorScheme="blue"
|
315 |
+
aria-label="Ask the chatbot"
|
316 |
+
isLoading={loading}
|
317 |
+
loadingText="Waiting..."
|
318 |
+
>
|
319 |
+
Send
|
320 |
+
</Button>
|
321 |
+
</HStack>
|
322 |
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
</Flex>
|
324 |
);
|
325 |
};
|
frontend/src/hooks/types.ts
CHANGED
@@ -101,8 +101,9 @@ export interface ProjectExplorerProps {
|
|
101 |
setPage: React.Dispatch<React.SetStateAction<number>>;
|
102 |
setSelectedProject: (project: Project) => void;
|
103 |
question: string;
|
104 |
-
setQuestion: (
|
105 |
chatHistory: ChatMessage[];
|
106 |
-
askChatbot: () => void
|
|
|
107 |
messagesEndRef: React.RefObject<HTMLDivElement>;
|
108 |
}
|
|
|
101 |
setPage: React.Dispatch<React.SetStateAction<number>>;
|
102 |
setSelectedProject: (project: Project) => void;
|
103 |
question: string;
|
104 |
+
setQuestion: (q: string) => void;
|
105 |
chatHistory: ChatMessage[];
|
106 |
+
askChatbot: () => Promise<void>;
|
107 |
+
loading: boolean;
|
108 |
messagesEndRef: React.RefObject<HTMLDivElement>;
|
109 |
}
|
frontend/src/hooks/useAppState.ts
CHANGED
@@ -38,6 +38,7 @@ export const useAppState = () => {
|
|
38 |
});
|
39 |
|
40 |
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
|
|
41 |
|
42 |
const [availableFilters, setAvailableFilters] = useState<AvailableFilters>({
|
43 |
statuses: ["SIGNED", "CLOSED", "TERMINATED","UNKNOWN"],
|
@@ -49,7 +50,7 @@ export const useAppState = () => {
|
|
49 |
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
50 |
|
51 |
const fetchProjects = () => {
|
52 |
-
fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}&legalBasis=${legalFilter}&organization=${orgFilter}&country=${countryFilter}&fundingScheme=${fundingSchemeFilter}&
|
53 |
.then(res => res.json())
|
54 |
.then((data: Project[]) => setProjects(data))
|
55 |
.catch(console.error);
|
@@ -84,13 +85,20 @@ export const useAppState = () => {
|
|
84 |
}
|
85 |
|
86 |
const askChatbot = async () => {
|
87 |
-
if (!question.trim()) return;
|
88 |
const newChat: ChatMessage[] = [
|
89 |
...chatHistory,
|
90 |
{ role: "user", content: question },
|
91 |
];
|
92 |
setChatHistory(newChat);
|
93 |
setQuestion("");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
try {
|
96 |
const res = await fetch("/api/rag", {
|
@@ -99,49 +107,42 @@ export const useAppState = () => {
|
|
99 |
body: JSON.stringify({ query: question }),
|
100 |
});
|
101 |
|
102 |
-
// Log the raw response for debugging
|
103 |
-
console.log("RAG API status:", res.status, res.statusText);
|
104 |
const text = await res.text();
|
105 |
-
console.log("RAG API raw body:", text);
|
106 |
-
|
107 |
if (!res.ok) {
|
108 |
-
|
109 |
-
let errDetail: string;
|
110 |
try {
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
errDetail = text;
|
115 |
-
}
|
116 |
-
throw new Error(`API error ${res.status}: ${errDetail}`);
|
117 |
}
|
118 |
|
119 |
-
// Now parse the successful JSON
|
120 |
const data: RagResponse = JSON.parse(text);
|
121 |
-
console.log("RAG API parsed:", data);
|
122 |
-
|
123 |
const idList = data.source_ids.join(", ") || "none";
|
124 |
const assistantContent = `${data.answer}
|
125 |
|
126 |
-
|
127 |
|
128 |
-
|
129 |
-
|
|
|
130 |
{ role: "assistant", content: assistantContent },
|
131 |
]);
|
132 |
} catch (err: any) {
|
133 |
-
|
134 |
-
setChatHistory([
|
135 |
-
...
|
136 |
{
|
137 |
role: "assistant",
|
138 |
content: `Something went wrong: ${err.message}`,
|
139 |
},
|
140 |
]);
|
|
|
|
|
|
|
|
|
141 |
}
|
142 |
};
|
143 |
|
144 |
-
|
145 |
useEffect(fetchProjects, [page, search, statusFilter,legalFilter, orgFilter, countryFilter, fundingSchemeFilter, idFilter, sortField, sortOrder]);
|
146 |
useEffect(() => {
|
147 |
console.log("Updated filters:", filters);
|
@@ -185,6 +186,7 @@ export const useAppState = () => {
|
|
185 |
chatHistory,
|
186 |
setChatHistory,
|
187 |
askChatbot,
|
|
|
188 |
messagesEndRef
|
189 |
},
|
190 |
detailsProps: {
|
@@ -193,6 +195,7 @@ export const useAppState = () => {
|
|
193 |
setQuestion,
|
194 |
chatHistory,
|
195 |
askChatbot,
|
|
|
196 |
messagesEndRef
|
197 |
}
|
198 |
};
|
|
|
38 |
});
|
39 |
|
40 |
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
41 |
+
const [loading, setLoading] = useState<boolean>(false);
|
42 |
|
43 |
const [availableFilters, setAvailableFilters] = useState<AvailableFilters>({
|
44 |
statuses: ["SIGNED", "CLOSED", "TERMINATED","UNKNOWN"],
|
|
|
50 |
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
51 |
|
52 |
const fetchProjects = () => {
|
53 |
+
fetch(`/api/projects?page=${page}&search=${encodeURIComponent(search)}&status=${statusFilter}&legalBasis=${legalFilter}&organization=${orgFilter}&country=${countryFilter}&fundingScheme=${fundingSchemeFilter}&proj_id=${idFilter}&sortField=${sortField}&sortOrder=${sortOrder}`)
|
54 |
.then(res => res.json())
|
55 |
.then((data: Project[]) => setProjects(data))
|
56 |
.catch(console.error);
|
|
|
85 |
}
|
86 |
|
87 |
const askChatbot = async () => {
|
88 |
+
if (!question.trim() || loading) return;
|
89 |
const newChat: ChatMessage[] = [
|
90 |
...chatHistory,
|
91 |
{ role: "user", content: question },
|
92 |
];
|
93 |
setChatHistory(newChat);
|
94 |
setQuestion("");
|
95 |
+
setLoading(true);
|
96 |
+
|
97 |
+
// 1) placeholder
|
98 |
+
setChatHistory((h) => [
|
99 |
+
...h,
|
100 |
+
{ role: "assistant", content: "Generating answer..." },
|
101 |
+
]);
|
102 |
|
103 |
try {
|
104 |
const res = await fetch("/api/rag", {
|
|
|
107 |
body: JSON.stringify({ query: question }),
|
108 |
});
|
109 |
|
|
|
|
|
110 |
const text = await res.text();
|
|
|
|
|
111 |
if (!res.ok) {
|
112 |
+
let errDetail = text;
|
|
|
113 |
try {
|
114 |
+
errDetail = JSON.parse(text).detail;
|
115 |
+
} catch {}
|
116 |
+
throw new Error(errDetail);
|
|
|
|
|
|
|
117 |
}
|
118 |
|
|
|
119 |
const data: RagResponse = JSON.parse(text);
|
|
|
|
|
120 |
const idList = data.source_ids.join(", ") || "none";
|
121 |
const assistantContent = `${data.answer}
|
122 |
|
123 |
+
The output was based on the following Project IDs: ${idList}`;
|
124 |
|
125 |
+
// 2) replace placeholder with real answer
|
126 |
+
setChatHistory((h) => [
|
127 |
+
...h.slice(0, -1),
|
128 |
{ role: "assistant", content: assistantContent },
|
129 |
]);
|
130 |
} catch (err: any) {
|
131 |
+
// replace placeholder with error message
|
132 |
+
setChatHistory((h) => [
|
133 |
+
...h.slice(0, -1),
|
134 |
{
|
135 |
role: "assistant",
|
136 |
content: `Something went wrong: ${err.message}`,
|
137 |
},
|
138 |
]);
|
139 |
+
} finally {
|
140 |
+
setLoading(false);
|
141 |
+
// scroll to bottom
|
142 |
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
143 |
}
|
144 |
};
|
145 |
|
|
|
146 |
useEffect(fetchProjects, [page, search, statusFilter,legalFilter, orgFilter, countryFilter, fundingSchemeFilter, idFilter, sortField, sortOrder]);
|
147 |
useEffect(() => {
|
148 |
console.log("Updated filters:", filters);
|
|
|
186 |
chatHistory,
|
187 |
setChatHistory,
|
188 |
askChatbot,
|
189 |
+
loading,
|
190 |
messagesEndRef
|
191 |
},
|
192 |
detailsProps: {
|
|
|
195 |
setQuestion,
|
196 |
chatHistory,
|
197 |
askChatbot,
|
198 |
+
loading,
|
199 |
messagesEndRef
|
200 |
}
|
201 |
};
|