Spaces:
Paused
Paused
Soham Waghmare
commited on
Commit
·
27a07a9
1
Parent(s):
73fba58
fix: react fixes
Browse files- backend/app.py +1 -1
- backend/knet.py +25 -19
- backend/research_node.py +9 -1
- frontend/src/components/ChatInterface.tsx +15 -3
- frontend/src/components/ResearchControls.tsx +0 -11
- frontend/src/components/ui/ChatLayout.tsx +17 -14
- frontend/src/lib/types.ts +0 -1
backend/app.py
CHANGED
|
@@ -33,7 +33,7 @@ app.add_middleware(
|
|
| 33 |
|
| 34 |
sio = socketio.AsyncServer(
|
| 35 |
cors_allowed_origins=CORS_ALLOWED_ORIGINS,
|
| 36 |
-
ping_timeout=
|
| 37 |
ping_interval=10,
|
| 38 |
async_mode="asgi",
|
| 39 |
)
|
|
|
|
| 33 |
|
| 34 |
sio = socketio.AsyncServer(
|
| 35 |
cors_allowed_origins=CORS_ALLOWED_ORIGINS,
|
| 36 |
+
ping_timeout=1200,
|
| 37 |
ping_interval=10,
|
| 38 |
async_mode="asgi",
|
| 39 |
)
|
backend/knet.py
CHANGED
|
@@ -138,11 +138,11 @@ class ResearchProgress:
|
|
| 138 |
self.callback = callback
|
| 139 |
|
| 140 |
async def update(self, progress: int, message: str):
|
| 141 |
-
self.progress = min(100, self.progress + progress) # max 100
|
| 142 |
await self.callback({"progress": self.progress, "message": message})
|
| 143 |
|
| 144 |
async def setter(self, progress: int, message: str):
|
| 145 |
-
self.progress = min(100, progress) # max 100
|
| 146 |
await self.callback({"progress": self.progress, "message": message})
|
| 147 |
|
| 148 |
|
|
@@ -189,21 +189,25 @@ class KNet:
|
|
| 189 |
self.research_plan = self.generate_content(self.prompt.research_plan.format(topic=topic), schema=self.schema.research_plan)["steps"]
|
| 190 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 191 |
|
| 192 |
-
|
| 193 |
-
query = self.generate_content(
|
| 194 |
-
self.prompt.search_query.format(vertical=self.research_plan[self.idx_research_plan]), schema=self.schema.search_query
|
| 195 |
-
)["branches"][0]
|
| 196 |
-
|
| 197 |
-
# Initialize research tree
|
| 198 |
-
root_node = ResearchNode(query)
|
| 199 |
-
to_explore = deque([(root_node, 0)]) # (node, depth) pairs
|
| 200 |
-
explored_queries = set() # {string, string, ...}
|
| 201 |
|
| 202 |
await self.progress.update(0, "Starting research...")
|
| 203 |
|
| 204 |
# Iterate on research plan
|
| 205 |
for self.idx_research_plan, _ in enumerate(self.research_plan):
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
await self.progress.update(100 / (len(self.research_plan) + 1), f"{self.research_plan[self.idx_research_plan]}")
|
| 208 |
|
| 209 |
while to_explore:
|
|
@@ -230,9 +234,9 @@ class KNet:
|
|
| 230 |
|
| 231 |
# Generate final report
|
| 232 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 233 |
-
final_report = self._generate_final_report(
|
| 234 |
|
| 235 |
-
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {
|
| 236 |
await self.progress.update(100, "Research complete!")
|
| 237 |
|
| 238 |
with open("output.log.json", "w", encoding="utf-8") as f:
|
|
@@ -256,7 +260,7 @@ class KNet:
|
|
| 256 |
report = []
|
| 257 |
# Fill in report outline
|
| 258 |
for i, heading in enumerate(outline["headings"]):
|
| 259 |
-
self.progress.update(100 / (len(outline["headings"] + 1)
|
| 260 |
content = self.generate_content(
|
| 261 |
self.prompt.report_fillin.format(
|
| 262 |
topic=topic,
|
|
@@ -267,6 +271,8 @@ class KNet:
|
|
| 267 |
schema=self.schema.report_fillin,
|
| 268 |
)["content"]
|
| 269 |
report.append({"heading": heading, "content": content})
|
|
|
|
|
|
|
| 270 |
|
| 271 |
# Collate multimedia content
|
| 272 |
media_content = {"images": [], "videos": [], "links": [], "references": []}
|
|
@@ -297,9 +303,9 @@ class KNet:
|
|
| 297 |
}
|
| 298 |
|
| 299 |
return {
|
| 300 |
-
"topic":
|
| 301 |
"timestamp": datetime.now().isoformat(),
|
| 302 |
-
"content":
|
| 303 |
"media": media_content,
|
| 304 |
"research_tree": build_tree_structure(root_node),
|
| 305 |
"metadata": {
|
|
@@ -337,7 +343,7 @@ class KNet:
|
|
| 337 |
# node -|-> child
|
| 338 |
# |-> child
|
| 339 |
new_nodes = []
|
| 340 |
-
for branch in response.get("branches", []):
|
| 341 |
child_node = node.add_child(branch)
|
| 342 |
new_nodes.append(child_node)
|
| 343 |
|
|
@@ -360,7 +366,7 @@ class KNet:
|
|
| 360 |
if node.data:
|
| 361 |
findings = ("\n" + "-" * 10 + "Next data" + "-" * 10 + "\n").join([json.dumps(d, indent=2) for d in node.data])
|
| 362 |
response = self.generate_content(self.prompt.site_summary.format(query=node.query, findings=findings), temp=0.2)
|
| 363 |
-
self.ctx_manager.append(response)
|
| 364 |
|
| 365 |
# Research manager takes decision to proceed or not
|
| 366 |
prompt = self.prompt.continue_branch.format(
|
|
|
|
| 138 |
self.callback = callback
|
| 139 |
|
| 140 |
async def update(self, progress: int, message: str):
|
| 141 |
+
self.progress = int(min(100, self.progress + progress)) # max 100
|
| 142 |
await self.callback({"progress": self.progress, "message": message})
|
| 143 |
|
| 144 |
async def setter(self, progress: int, message: str):
|
| 145 |
+
self.progress = int(min(100, progress)) # max 100
|
| 146 |
await self.callback({"progress": self.progress, "message": message})
|
| 147 |
|
| 148 |
|
|
|
|
| 189 |
self.research_plan = self.generate_content(self.prompt.research_plan.format(topic=topic), schema=self.schema.research_plan)["steps"]
|
| 190 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 191 |
|
| 192 |
+
master_node = ResearchNode()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
await self.progress.update(0, "Starting research...")
|
| 195 |
|
| 196 |
# Iterate on research plan
|
| 197 |
for self.idx_research_plan, _ in enumerate(self.research_plan):
|
| 198 |
+
# Generate initial search query
|
| 199 |
+
query = self.generate_content(
|
| 200 |
+
self.prompt.search_query.format(
|
| 201 |
+
vertical=self.research_plan[self.idx_research_plan], research_plan="None", past_queries="None", ctx_manager="None", n=1
|
| 202 |
+
),
|
| 203 |
+
schema=self.schema.search_query,
|
| 204 |
+
)["branches"][0]
|
| 205 |
+
|
| 206 |
+
root_node = ResearchNode(query)
|
| 207 |
+
master_node.add_child(root_node)
|
| 208 |
+
to_explore = deque([(root_node, 0)]) # (node, depth) pairs
|
| 209 |
+
explored_queries = set() # {string, string, ...}
|
| 210 |
+
|
| 211 |
await self.progress.update(100 / (len(self.research_plan) + 1), f"{self.research_plan[self.idx_research_plan]}")
|
| 212 |
|
| 213 |
while to_explore:
|
|
|
|
| 234 |
|
| 235 |
# Generate final report
|
| 236 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 237 |
+
final_report = self._generate_final_report(master_node, topic)
|
| 238 |
|
| 239 |
+
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {master_node.max_depth()} levels")
|
| 240 |
await self.progress.update(100, "Research complete!")
|
| 241 |
|
| 242 |
with open("output.log.json", "w", encoding="utf-8") as f:
|
|
|
|
| 260 |
report = []
|
| 261 |
# Fill in report outline
|
| 262 |
for i, heading in enumerate(outline["headings"]):
|
| 263 |
+
self.progress.update(100 / (len(outline["headings"]) + 1), "Generating report...")
|
| 264 |
content = self.generate_content(
|
| 265 |
self.prompt.report_fillin.format(
|
| 266 |
topic=topic,
|
|
|
|
| 271 |
schema=self.schema.report_fillin,
|
| 272 |
)["content"]
|
| 273 |
report.append({"heading": heading, "content": content})
|
| 274 |
+
# Rasterize report
|
| 275 |
+
raster_report = f"# {outline['title']}\n\n" + "\n\n".join([f"## {r['heading']}\n\n{r['content']}" for r in report])
|
| 276 |
|
| 277 |
# Collate multimedia content
|
| 278 |
media_content = {"images": [], "videos": [], "links": [], "references": []}
|
|
|
|
| 303 |
}
|
| 304 |
|
| 305 |
return {
|
| 306 |
+
"topic": topic,
|
| 307 |
"timestamp": datetime.now().isoformat(),
|
| 308 |
+
"content": raster_report,
|
| 309 |
"media": media_content,
|
| 310 |
"research_tree": build_tree_structure(root_node),
|
| 311 |
"metadata": {
|
|
|
|
| 343 |
# node -|-> child
|
| 344 |
# |-> child
|
| 345 |
new_nodes = []
|
| 346 |
+
for branch in response.get("branches", [])[:1]:
|
| 347 |
child_node = node.add_child(branch)
|
| 348 |
new_nodes.append(child_node)
|
| 349 |
|
|
|
|
| 366 |
if node.data:
|
| 367 |
findings = ("\n" + "-" * 10 + "Next data" + "-" * 10 + "\n").join([json.dumps(d, indent=2) for d in node.data])
|
| 368 |
response = self.generate_content(self.prompt.site_summary.format(query=node.query, findings=findings), temp=0.2)
|
| 369 |
+
self.ctx_manager.append(response) if isinstance(response, str) else None
|
| 370 |
|
| 371 |
# Research manager takes decision to proceed or not
|
| 372 |
prompt = self.prompt.continue_branch.format(
|
backend/research_node.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional
|
|
| 3 |
|
| 4 |
|
| 5 |
class ResearchNode:
|
| 6 |
-
def __init__(self, query: str, parent: Optional["ResearchNode"] = None, depth: int = 0):
|
| 7 |
self.query = query
|
| 8 |
self.parent = parent
|
| 9 |
self.depth = depth
|
|
@@ -42,3 +42,11 @@ class ResearchNode:
|
|
| 42 |
for child in self.children:
|
| 43 |
data.extend(child.get_all_data())
|
| 44 |
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
class ResearchNode:
|
| 6 |
+
def __init__(self, query: str = "_", parent: Optional["ResearchNode"] = None, depth: int = 0):
|
| 7 |
self.query = query
|
| 8 |
self.parent = parent
|
| 9 |
self.depth = depth
|
|
|
|
| 42 |
for child in self.children:
|
| 43 |
data.extend(child.get_all_data())
|
| 44 |
return data
|
| 45 |
+
|
| 46 |
+
def __repr__(self) -> dict:
|
| 47 |
+
return {
|
| 48 |
+
"query": self.query,
|
| 49 |
+
"depth": self.depth,
|
| 50 |
+
"children": [child.__repr__() for child in self.children],
|
| 51 |
+
"data": self.data,
|
| 52 |
+
}
|
frontend/src/components/ChatInterface.tsx
CHANGED
|
@@ -45,7 +45,6 @@ const ChatInterface = () => {
|
|
| 45 |
sources: true,
|
| 46 |
citations: false,
|
| 47 |
max_depth: 1,
|
| 48 |
-
max_breadth: 3,
|
| 49 |
num_sites_per_query: 5,
|
| 50 |
});
|
| 51 |
|
|
@@ -131,11 +130,25 @@ const ChatInterface = () => {
|
|
| 131 |
},
|
| 132 |
];
|
| 133 |
|
| 134 |
-
|
|
|
|
| 135 |
...prevState,
|
| 136 |
isLoading: false,
|
| 137 |
messages: newMessages,
|
| 138 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
});
|
| 140 |
});
|
| 141 |
|
|
@@ -230,7 +243,6 @@ const ChatInterface = () => {
|
|
| 230 |
socket.emit("start_research", {
|
| 231 |
topic: content,
|
| 232 |
max_depth: researchOptions.max_depth,
|
| 233 |
-
max_breadth: researchOptions.max_breadth,
|
| 234 |
num_sites_per_query: researchOptions.num_sites_per_query,
|
| 235 |
});
|
| 236 |
} catch (error) {
|
|
|
|
| 45 |
sources: true,
|
| 46 |
citations: false,
|
| 47 |
max_depth: 1,
|
|
|
|
| 48 |
num_sites_per_query: 5,
|
| 49 |
});
|
| 50 |
|
|
|
|
| 130 |
},
|
| 131 |
];
|
| 132 |
|
| 133 |
+
// Save updated messages to localStorage
|
| 134 |
+
const updatedState = {
|
| 135 |
...prevState,
|
| 136 |
isLoading: false,
|
| 137 |
messages: newMessages,
|
| 138 |
};
|
| 139 |
+
|
| 140 |
+
// Update localStorage with the new messages
|
| 141 |
+
const updatedData: ChatData = {
|
| 142 |
+
conversations: conversations.map((conv) => ({
|
| 143 |
+
...conv,
|
| 144 |
+
messages: conv.id === currentConversationId ? newMessages : conv.messages || [],
|
| 145 |
+
lastUpdated: conv.id === currentConversationId ? new Date().toISOString() : conv.lastUpdated,
|
| 146 |
+
})),
|
| 147 |
+
currentConversationId,
|
| 148 |
+
};
|
| 149 |
+
saveToStorage(updatedData);
|
| 150 |
+
|
| 151 |
+
return updatedState;
|
| 152 |
});
|
| 153 |
});
|
| 154 |
|
|
|
|
| 243 |
socket.emit("start_research", {
|
| 244 |
topic: content,
|
| 245 |
max_depth: researchOptions.max_depth,
|
|
|
|
| 246 |
num_sites_per_query: researchOptions.num_sites_per_query,
|
| 247 |
});
|
| 248 |
} catch (error) {
|
frontend/src/components/ResearchControls.tsx
CHANGED
|
@@ -65,17 +65,6 @@ const ResearchControls: React.FC<ResearchControlsProps> = ({ options, onOptionCh
|
|
| 65 |
/>
|
| 66 |
</div>
|
| 67 |
|
| 68 |
-
<div className="space-y-2">
|
| 69 |
-
<Label htmlFor="max-breadth">Max Breadth</Label>
|
| 70 |
-
<Input
|
| 71 |
-
type="number"
|
| 72 |
-
id="max-breadth"
|
| 73 |
-
value={options.max_breadth}
|
| 74 |
-
onChange={(e) => onOptionChange({ ...options, max_breadth: parseInt(e.target.value, 10) })}
|
| 75 |
-
className="w-full"
|
| 76 |
-
/>
|
| 77 |
-
</div>
|
| 78 |
-
|
| 79 |
<div className="space-y-2">
|
| 80 |
<Label htmlFor="num-sites-per-query">Number of Sites per Query</Label>
|
| 81 |
<Input
|
|
|
|
| 65 |
/>
|
| 66 |
</div>
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
<div className="space-y-2">
|
| 69 |
<Label htmlFor="num-sites-per-query">Number of Sites per Query</Label>
|
| 70 |
<Input
|
frontend/src/components/ui/ChatLayout.tsx
CHANGED
|
@@ -2,12 +2,12 @@ import { Button } from "@/components/ui/button";
|
|
| 2 |
import { Card } from "@/components/ui/card";
|
| 3 |
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
| 4 |
import { ScrollArea } from "@/components/ui/scroll-area";
|
| 5 |
-
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
| 6 |
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 7 |
import { LayoutGrid, MessageCircle, Settings } from "lucide-react";
|
| 8 |
import React from "react";
|
| 9 |
import { ThemeToggle } from "./ThemeToggle";
|
| 10 |
import Link from "next/link";
|
|
|
|
| 11 |
|
| 12 |
interface ChatLayoutProps {
|
| 13 |
sidebar: React.ReactNode;
|
|
@@ -24,24 +24,27 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 24 |
</Link>
|
| 25 |
<div className="flex-1" />
|
| 26 |
<ThemeToggle />
|
| 27 |
-
<
|
| 28 |
-
<
|
| 29 |
-
<Button variant="ghost" size="icon">
|
| 30 |
<Settings size={20} />
|
| 31 |
</Button>
|
| 32 |
-
</
|
| 33 |
-
<
|
| 34 |
-
<
|
| 35 |
-
<
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
| 40 |
</header>
|
| 41 |
|
| 42 |
<div className="flex-1 overflow-hidden">
|
| 43 |
<ResizablePanelGroup direction="horizontal">
|
| 44 |
-
<ResizablePanel defaultSize={
|
| 45 |
<Card className="h-full rounded-none border-r border-t-0 border-l-0 border-b-0">
|
| 46 |
<ScrollArea className="h-full">{sidebar}</ScrollArea>
|
| 47 |
</Card>
|
|
@@ -49,7 +52,7 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 49 |
|
| 50 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 51 |
|
| 52 |
-
<ResizablePanel defaultSize={
|
| 53 |
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
| 54 |
<div className="p-4">
|
| 55 |
<TabsList className="">
|
|
|
|
| 2 |
import { Card } from "@/components/ui/card";
|
| 3 |
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
| 4 |
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
|
|
| 5 |
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 6 |
import { LayoutGrid, MessageCircle, Settings } from "lucide-react";
|
| 7 |
import React from "react";
|
| 8 |
import { ThemeToggle } from "./ThemeToggle";
|
| 9 |
import Link from "next/link";
|
| 10 |
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
| 11 |
|
| 12 |
interface ChatLayoutProps {
|
| 13 |
sidebar: React.ReactNode;
|
|
|
|
| 24 |
</Link>
|
| 25 |
<div className="flex-1" />
|
| 26 |
<ThemeToggle />
|
| 27 |
+
<Dialog>
|
| 28 |
+
<DialogTrigger asChild>
|
| 29 |
+
<Button variant="ghost" size="icon" className="h-9 w-9" title="Settings">
|
| 30 |
<Settings size={20} />
|
| 31 |
</Button>
|
| 32 |
+
</DialogTrigger>
|
| 33 |
+
<DialogContent>
|
| 34 |
+
<DialogHeader>
|
| 35 |
+
<DialogTitle>Research Settings</DialogTitle>
|
| 36 |
+
<DialogDescription>
|
| 37 |
+
Configure your research parameters and preferences.
|
| 38 |
+
</DialogDescription>
|
| 39 |
+
</DialogHeader>
|
| 40 |
+
{settingsPanel}
|
| 41 |
+
</DialogContent>
|
| 42 |
+
</Dialog>
|
| 43 |
</header>
|
| 44 |
|
| 45 |
<div className="flex-1 overflow-hidden">
|
| 46 |
<ResizablePanelGroup direction="horizontal">
|
| 47 |
+
<ResizablePanel defaultSize={25} minSize={17} maxSize={30} className="hidden md:block">
|
| 48 |
<Card className="h-full rounded-none border-r border-t-0 border-l-0 border-b-0">
|
| 49 |
<ScrollArea className="h-full">{sidebar}</ScrollArea>
|
| 50 |
</Card>
|
|
|
|
| 52 |
|
| 53 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 54 |
|
| 55 |
+
<ResizablePanel defaultSize={75}>
|
| 56 |
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
| 57 |
<div className="p-4">
|
| 58 |
<TabsList className="">
|
frontend/src/lib/types.ts
CHANGED
|
@@ -16,7 +16,6 @@ export interface ResearchOptions {
|
|
| 16 |
sources: boolean;
|
| 17 |
citations: boolean;
|
| 18 |
max_depth: number;
|
| 19 |
-
max_breadth: number;
|
| 20 |
num_sites_per_query: number;
|
| 21 |
}
|
| 22 |
|
|
|
|
| 16 |
sources: boolean;
|
| 17 |
citations: boolean;
|
| 18 |
max_depth: number;
|
|
|
|
| 19 |
num_sites_per_query: number;
|
| 20 |
}
|
| 21 |
|