Skip to content

Commit 5a8fd0f

Browse files
committed
Merge branch 'development' of github.com:enso-labs/orchestra into development
2 parents 3ab9664 + c378eac commit 5a8fd0f

13 files changed

Lines changed: 675 additions & 46 deletions

File tree

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- bug/370-anthropic-streaming (2025-09-16)
2020

2121
### Changed
22+
- feat/490-can-read-deepagents-agent-files (2025-11-10)
2223
- feat/485-user-env (2025-11-08)
2324
- feat/482-teams-webhook (2025-11-05)
2425
- feat/476-html-chart-construct-tool (2025-11-02)

backend/src/utils/stream.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ async def stream_generator(
172172
subagents: list[SubAgent],
173173
config: RunnableConfig,
174174
service_context: ServiceContext,
175-
):
175+
):
176+
files_map = {}
176177
async with get_checkpoint_db() as checkpointer:
177178
try:
178179
service_context.config["configurable"]["user_id"] = service_context.user_id
@@ -193,6 +194,10 @@ async def stream_generator(
193194
# Serialize and yield each chunk as SSE
194195
stream_chunk = handle_multi_mode(chunk)
195196
if stream_chunk:
197+
stream_type = stream_chunk[0]
198+
chunk_data = stream_chunk[1]
199+
if stream_type == "values" and "files" in chunk_data:
200+
files_map = {**files_map, **chunk_data["files"]}
196201
data = ujson.dumps(stream_chunk)
197202
log_to_file(str(data), agent.model) and APP_LOG_LEVEL == "DEBUG"
198203
logger.debug(f"data: {str(data)}")
@@ -230,6 +235,7 @@ async def stream_generator(
230235
"thread_id": thread_id,
231236
"checkpoint_id": checkpoint_id,
232237
"messages": [last_message.model_dump()],
238+
"files": files_map,
233239
"updated_at": get_time(),
234240
},
235241
)

frontend/package-lock.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"clsx": "^2.1.1",
3939
"date-fns": "^4.1.0",
4040
"debug": "^4.4.0",
41+
"jszip": "^3.10.1",
4142
"jwt-decode": "^4.0.0",
4243
"katex": "^0.16.8",
4344
"lodash": "^4.17.21",

frontend/src/components/drawers/app-sidebar.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ interface ThreadItemProps {
123123
}
124124

125125
function ThreadItem({ thread }: ThreadItemProps) {
126-
const { metadata, setMessages, setMetadata } = useChatContext();
126+
const { metadata, setMessages, setMetadata, setFilesMap } = useChatContext();
127127
const { isMobile, setOpenMobile } = useSidebar();
128128
const messages = thread.value?.messages || [];
129129
const messageCount = messages.length;
@@ -145,6 +145,24 @@ function ThreadItem({ thread }: ThreadItemProps) {
145145

146146
const handleThreadClick = async () => {
147147
const checkpoints = await searchThreads("list_checkpoints", thread.value);
148+
149+
// Set filesMap by associating files with the last AI message
150+
if (thread.value.files && Object.keys(thread.value.files).length > 0) {
151+
const messages = formatMessages(checkpoints[0].values.messages);
152+
const latestAiMessage = messages
153+
.slice()
154+
.reverse()
155+
.find((msg: any) => ["ai", "assistant"].includes(msg.role));
156+
157+
if (latestAiMessage) {
158+
const newFilesMap = new Map();
159+
newFilesMap.set(latestAiMessage.id, thread.value.files);
160+
setFilesMap(newFilesMap);
161+
}
162+
} else {
163+
setFilesMap(new Map());
164+
}
165+
148166
setModel(
149167
thread.value.messages[thread.value.messages.length - 1].model ||
150168
DEFAULT_CHAT_MODEL,

frontend/src/components/inputs/MonacoEditor.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
33

44
interface Props {
55
value: string;
6-
handleChange: (val: string) => void;
6+
handleChange?: (val: string) => void;
77
language?: string;
88
readOnly?: boolean;
99
height?: string;
@@ -69,8 +69,8 @@ function MonacoEditor({
6969
setError("");
7070
}
7171

72-
// Always call handleChange to update the parent
73-
handleChange(val);
72+
// Call handleChange if provided
73+
handleChange?.(val);
7474
};
7575

7676
return (

frontend/src/components/lists/ChatMessages.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { formatContent, truncateFrom } from "@/lib/utils/format";
1111
import SearchEngineTool from "../tools/SearchEngine";
1212
import ChartRenderWidget from "../tools/ChartRenderWidget";
1313
import CopyTextButton from "../buttons/CopyTextButton";
14+
import FileViewer from "../viewers/FileViewer";
1415

1516
const MAX_LENGTH = 1000;
1617

@@ -228,6 +229,9 @@ export function Message({
228229
}
229230

230231
if (["ai", "assistant"].includes(message.role)) {
232+
const { filesMap, viewMode } = useChatContext();
233+
const messageFiles = filesMap.get(message.id);
234+
231235
return (
232236
<div className="group">
233237
<div className="max-w-[90vw] md:max-w-[80%] rounded-lg rounded-bl-sm">
@@ -236,6 +240,15 @@ export function Message({
236240
content={formatContent(message.content) || "Invalid message"}
237241
/>
238242
</div>
243+
244+
{/* Add file viewer if files exist - only show in chat mode */}
245+
{viewMode === "chat" &&
246+
messageFiles &&
247+
Object.keys(messageFiles).length > 0 && (
248+
<div className="mt-2 px-3">
249+
<FileViewer files={messageFiles} />
250+
</div>
251+
)}
239252
</div>
240253
<div className="flex justify-start opacity-100 transition-opacity duration-200 mt-1 px-3">
241254
<div className="flex gap-1">

frontend/src/components/nav/ChatNav.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,49 @@
11
import { ColorModeButton } from "@/components/buttons/ColorModeButton";
22
import SelectModel from "../lists/SelectModel";
33
import NewThreadButton from "../buttons/NewThreadButton";
4+
import { LayoutGrid, MessageSquare } from "lucide-react";
5+
import { useChatContext } from "@/context/ChatContext";
6+
import { Button } from "@/components/ui/button";
47

58
export function ChatNav({
69
sidebarTrigger,
710
}: {
811
sidebarTrigger?: React.ReactNode | undefined;
912
}) {
13+
const { viewMode, setViewMode, filesMap } = useChatContext();
14+
const hasFiles = filesMap.size > 0;
15+
1016
return (
1117
<header className="bg-transparent mb-1">
1218
<div className="mx-auto px-4 sm:px-6 lg:px-4 pt-4">
1319
<div className="flex items-center justify-between">
1420
<div className="flex items-center">{sidebarTrigger}</div>
1521

1622
<div className="flex items-center gap-2">
23+
{/* Mode toggle - only visible when files exist */}
24+
{hasFiles && (
25+
<div className="hidden md:flex items-center gap-1 border border-border rounded-lg p-1">
26+
<Button
27+
variant={viewMode === "chat" ? "secondary" : "ghost"}
28+
size="sm"
29+
onClick={() => setViewMode("chat")}
30+
className="h-8 gap-2"
31+
>
32+
<MessageSquare className="h-4 w-4" />
33+
Chat
34+
</Button>
35+
<Button
36+
variant={viewMode === "editor" ? "secondary" : "ghost"}
37+
size="sm"
38+
onClick={() => setViewMode("editor")}
39+
className="h-8 gap-2"
40+
>
41+
<LayoutGrid className="h-4 w-4" />
42+
Editor
43+
</Button>
44+
</div>
45+
)}
46+
1747
<div className="w-56">
1848
<SelectModel />
1949
</div>

0 commit comments

Comments
 (0)