Spaces:
Sleeping
Sleeping
parallel research carousel UI
Browse files- assets/app.js +151 -1
- assets/index.html +2 -2
- assets/server.css +147 -0
- ui/agent/graph/nodes/researcher.py +19 -2
- ui/agent/graph/respond.py +8 -0
assets/app.js
CHANGED
|
@@ -32,6 +32,7 @@ const state = {
|
|
| 32 |
choices: null,
|
| 33 |
busy: false,
|
| 34 |
view: "form",
|
|
|
|
| 35 |
};
|
| 36 |
|
| 37 |
const els = {
|
|
@@ -477,9 +478,158 @@ async function renderToolMessage(node, message, metadata) {
|
|
| 477 |
node.appendChild(body);
|
| 478 |
}
|
| 479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
async function renderMessages() {
|
| 481 |
els.chatMessages.innerHTML = "";
|
| 482 |
-
for (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
const node = document.createElement("div");
|
| 484 |
const metadata = message.metadata || {};
|
| 485 |
const isTool = Boolean(metadata.title || metadata.status);
|
|
|
|
| 32 |
choices: null,
|
| 33 |
busy: false,
|
| 34 |
view: "form",
|
| 35 |
+
activeResearchTaskId: null,
|
| 36 |
};
|
| 37 |
|
| 38 |
const els = {
|
|
|
|
| 478 |
node.appendChild(body);
|
| 479 |
}
|
| 480 |
|
| 481 |
+
function isResearchMessage(message) {
|
| 482 |
+
return Boolean(message?.metadata?.research_task);
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
function researchTaskKey(task) {
|
| 486 |
+
const id = task?.id;
|
| 487 |
+
return id === undefined || id === null ? String(task?.title || "research") : String(id);
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
function groupResearchMessages(messages) {
|
| 491 |
+
const groups = new Map();
|
| 492 |
+
for (const message of messages) {
|
| 493 |
+
const task = message.metadata?.research_task || {};
|
| 494 |
+
const key = researchTaskKey(task);
|
| 495 |
+
if (!groups.has(key)) {
|
| 496 |
+
groups.set(key, {
|
| 497 |
+
key,
|
| 498 |
+
task,
|
| 499 |
+
messages: [],
|
| 500 |
+
hasFinding: false,
|
| 501 |
+
hasPending: false,
|
| 502 |
+
});
|
| 503 |
+
}
|
| 504 |
+
const group = groups.get(key);
|
| 505 |
+
group.messages.push(message);
|
| 506 |
+
group.hasFinding = group.hasFinding || Boolean(message.metadata?.research_finding);
|
| 507 |
+
group.hasPending = group.hasPending || message.metadata?.status === "pending";
|
| 508 |
+
}
|
| 509 |
+
return Array.from(groups.values()).sort((left, right) => {
|
| 510 |
+
const leftId = Number(left.task?.id);
|
| 511 |
+
const rightId = Number(right.task?.id);
|
| 512 |
+
if (Number.isFinite(leftId) && Number.isFinite(rightId)) {
|
| 513 |
+
return leftId - rightId;
|
| 514 |
+
}
|
| 515 |
+
return String(left.task?.title || "").localeCompare(String(right.task?.title || ""));
|
| 516 |
+
});
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
function researchGroupStatus(group) {
|
| 520 |
+
if (group.hasFinding) {
|
| 521 |
+
return "done";
|
| 522 |
+
}
|
| 523 |
+
if (group.hasPending) {
|
| 524 |
+
return "running";
|
| 525 |
+
}
|
| 526 |
+
return "working";
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
function defaultResearchTask(groups) {
|
| 530 |
+
const active = groups.find((group) => group.key === state.activeResearchTaskId);
|
| 531 |
+
if (active) {
|
| 532 |
+
return active;
|
| 533 |
+
}
|
| 534 |
+
return (
|
| 535 |
+
groups.find((group) => researchGroupStatus(group) !== "done") ||
|
| 536 |
+
groups[0]
|
| 537 |
+
);
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
async function renderResearchMessage(parent, message) {
|
| 541 |
+
const node = document.createElement("div");
|
| 542 |
+
const metadata = message.metadata || {};
|
| 543 |
+
node.className = `research-event ${metadata.status === "pending" ? "pending" : ""}`;
|
| 544 |
+
|
| 545 |
+
const title = document.createElement("div");
|
| 546 |
+
title.className = "research-event-title";
|
| 547 |
+
title.textContent = metadata.title || "Research update";
|
| 548 |
+
node.appendChild(title);
|
| 549 |
+
|
| 550 |
+
if (metadata.log) {
|
| 551 |
+
await renderToolMessage(node, message, metadata);
|
| 552 |
+
} else {
|
| 553 |
+
const body = document.createElement("div");
|
| 554 |
+
await renderMessageBody(body, message, false);
|
| 555 |
+
node.appendChild(body);
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
parent.appendChild(node);
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
async function renderResearchCarousel(messages) {
|
| 562 |
+
const groups = groupResearchMessages(messages);
|
| 563 |
+
const active = defaultResearchTask(groups);
|
| 564 |
+
if (!active) {
|
| 565 |
+
return null;
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
const panel = document.createElement("div");
|
| 569 |
+
panel.className = "chat-message assistant research-carousel";
|
| 570 |
+
|
| 571 |
+
const header = document.createElement("div");
|
| 572 |
+
header.className = "research-carousel-header";
|
| 573 |
+
const title = document.createElement("div");
|
| 574 |
+
title.className = "research-carousel-title";
|
| 575 |
+
title.textContent = "Parallel country research";
|
| 576 |
+
const progress = document.createElement("div");
|
| 577 |
+
progress.className = "research-carousel-progress";
|
| 578 |
+
const doneCount = groups.filter((group) => researchGroupStatus(group) === "done").length;
|
| 579 |
+
progress.textContent = `${doneCount}/${groups.length} complete`;
|
| 580 |
+
header.appendChild(title);
|
| 581 |
+
header.appendChild(progress);
|
| 582 |
+
panel.appendChild(header);
|
| 583 |
+
|
| 584 |
+
const tabs = document.createElement("div");
|
| 585 |
+
tabs.className = "research-carousel-tabs";
|
| 586 |
+
for (const group of groups) {
|
| 587 |
+
const status = researchGroupStatus(group);
|
| 588 |
+
const button = document.createElement("button");
|
| 589 |
+
button.type = "button";
|
| 590 |
+
button.className = `research-carousel-tab ${group.key === active.key ? "is-active" : ""}`;
|
| 591 |
+
button.dataset.status = status;
|
| 592 |
+
button.textContent = group.task?.title || `Research ${group.key}`;
|
| 593 |
+
button.addEventListener("click", () => {
|
| 594 |
+
state.activeResearchTaskId = group.key;
|
| 595 |
+
renderMessages();
|
| 596 |
+
});
|
| 597 |
+
tabs.appendChild(button);
|
| 598 |
+
}
|
| 599 |
+
panel.appendChild(tabs);
|
| 600 |
+
|
| 601 |
+
const body = document.createElement("div");
|
| 602 |
+
body.className = "research-carousel-body";
|
| 603 |
+
const activeStatus = document.createElement("div");
|
| 604 |
+
activeStatus.className = "research-carousel-active-status";
|
| 605 |
+
activeStatus.textContent = `${active.task?.title || "Research"} 路 ${researchGroupStatus(active)}`;
|
| 606 |
+
body.appendChild(activeStatus);
|
| 607 |
+
|
| 608 |
+
for (const message of active.messages) {
|
| 609 |
+
await renderResearchMessage(body, message);
|
| 610 |
+
}
|
| 611 |
+
panel.appendChild(body);
|
| 612 |
+
return panel;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
async function renderMessages() {
|
| 616 |
els.chatMessages.innerHTML = "";
|
| 617 |
+
for (let index = 0; index < state.history.length; index += 1) {
|
| 618 |
+
const message = state.history[index];
|
| 619 |
+
if (isResearchMessage(message)) {
|
| 620 |
+
const researchMessages = [];
|
| 621 |
+
while (index < state.history.length && isResearchMessage(state.history[index])) {
|
| 622 |
+
researchMessages.push(state.history[index]);
|
| 623 |
+
index += 1;
|
| 624 |
+
}
|
| 625 |
+
index -= 1;
|
| 626 |
+
const carousel = await renderResearchCarousel(researchMessages);
|
| 627 |
+
if (carousel) {
|
| 628 |
+
els.chatMessages.appendChild(carousel);
|
| 629 |
+
}
|
| 630 |
+
continue;
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
const node = document.createElement("div");
|
| 634 |
const metadata = message.metadata || {};
|
| 635 |
const isTool = Boolean(metadata.title || metadata.status);
|
assets/index.html
CHANGED
|
@@ -155,7 +155,7 @@
|
|
| 155 |
</div>
|
| 156 |
|
| 157 |
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
|
| 158 |
-
<script type="module" src="/assets/globe.js
|
| 159 |
-
<script type="module" src="/assets/app.js
|
| 160 |
</body>
|
| 161 |
</html>
|
|
|
|
| 155 |
</div>
|
| 156 |
|
| 157 |
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
|
| 158 |
+
<script type="module" src="/assets/globe.js"></script>
|
| 159 |
+
<script type="module" src="/assets/app.js"></script>
|
| 160 |
</body>
|
| 161 |
</html>
|
assets/server.css
CHANGED
|
@@ -454,6 +454,153 @@ button:disabled {
|
|
| 454 |
margin: 0.45em 0;
|
| 455 |
}
|
| 456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
.chat-input-row {
|
| 458 |
display: flex;
|
| 459 |
gap: 8px;
|
|
|
|
| 454 |
margin: 0.45em 0;
|
| 455 |
}
|
| 456 |
|
| 457 |
+
.research-carousel {
|
| 458 |
+
display: flex;
|
| 459 |
+
flex-direction: column;
|
| 460 |
+
gap: 10px;
|
| 461 |
+
background: rgba(17, 24, 39, 0.94);
|
| 462 |
+
border: 1px solid rgba(245, 158, 11, 0.28);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
.research-carousel-header {
|
| 466 |
+
display: flex;
|
| 467 |
+
align-items: center;
|
| 468 |
+
justify-content: space-between;
|
| 469 |
+
gap: 12px;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.research-carousel-title {
|
| 473 |
+
font-weight: 700;
|
| 474 |
+
color: #fbbf24;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.research-carousel-progress {
|
| 478 |
+
flex: 0 0 auto;
|
| 479 |
+
border-radius: 999px;
|
| 480 |
+
border: 1px solid rgba(148, 163, 184, 0.22);
|
| 481 |
+
background: rgba(15, 23, 42, 0.75);
|
| 482 |
+
color: #d1d5db;
|
| 483 |
+
padding: 3px 8px;
|
| 484 |
+
font-size: 0.75rem;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
.research-carousel-tabs {
|
| 488 |
+
display: flex;
|
| 489 |
+
gap: 8px;
|
| 490 |
+
overflow-x: auto;
|
| 491 |
+
padding-bottom: 2px;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.research-carousel-tab {
|
| 495 |
+
flex: 0 0 min(240px, 78%);
|
| 496 |
+
display: flex;
|
| 497 |
+
align-items: center;
|
| 498 |
+
gap: 6px;
|
| 499 |
+
padding: 8px 10px;
|
| 500 |
+
border-radius: 10px;
|
| 501 |
+
border-color: rgba(148, 163, 184, 0.24);
|
| 502 |
+
background: rgba(15, 23, 42, 0.82);
|
| 503 |
+
color: #e5e7eb;
|
| 504 |
+
text-align: left;
|
| 505 |
+
line-height: 1.25;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
.research-carousel-tab::before {
|
| 509 |
+
content: "";
|
| 510 |
+
width: 8px;
|
| 511 |
+
height: 8px;
|
| 512 |
+
flex: 0 0 auto;
|
| 513 |
+
border-radius: 999px;
|
| 514 |
+
background: #fbbf24;
|
| 515 |
+
box-shadow: 0 0 12px rgba(251, 191, 36, 0.35);
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
.research-carousel-tab[data-status="done"]::before {
|
| 519 |
+
background: #34d399;
|
| 520 |
+
box-shadow: 0 0 12px rgba(52, 211, 153, 0.35);
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.research-carousel-tab[data-status="running"]::before {
|
| 524 |
+
background: #60a5fa;
|
| 525 |
+
box-shadow: 0 0 12px rgba(96, 165, 250, 0.35);
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
.research-carousel-tab.is-active {
|
| 529 |
+
border-color: #f59e0b;
|
| 530 |
+
background: rgba(245, 158, 11, 0.16);
|
| 531 |
+
color: #f9fafb;
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
.research-carousel-body {
|
| 535 |
+
display: flex;
|
| 536 |
+
flex-direction: column;
|
| 537 |
+
gap: 8px;
|
| 538 |
+
border-radius: 10px;
|
| 539 |
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
| 540 |
+
background: rgba(2, 6, 23, 0.28);
|
| 541 |
+
padding: 10px;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.research-carousel-active-status {
|
| 545 |
+
color: #9ca3af;
|
| 546 |
+
font-size: 0.8rem;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
.research-event {
|
| 550 |
+
border-radius: 9px;
|
| 551 |
+
border: 1px solid rgba(148, 163, 184, 0.14);
|
| 552 |
+
background: rgba(15, 23, 42, 0.62);
|
| 553 |
+
padding: 8px 10px;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
.research-event.pending {
|
| 557 |
+
opacity: 0.82;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
.research-event-title {
|
| 561 |
+
margin-bottom: 6px;
|
| 562 |
+
color: #fbbf24;
|
| 563 |
+
font-weight: 600;
|
| 564 |
+
font-size: 0.9rem;
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
.research-event details.tool-log > summary {
|
| 568 |
+
cursor: pointer;
|
| 569 |
+
color: #e5e7eb;
|
| 570 |
+
font-weight: 600;
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
.research-event .tool-log-section {
|
| 574 |
+
margin-top: 8px;
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
.research-event .tool-log-section strong {
|
| 578 |
+
display: block;
|
| 579 |
+
margin-bottom: 4px;
|
| 580 |
+
color: #9ca3af;
|
| 581 |
+
font-size: 0.75rem;
|
| 582 |
+
text-transform: uppercase;
|
| 583 |
+
letter-spacing: 0.04em;
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
.research-event .tool-log-section pre {
|
| 587 |
+
margin: 0;
|
| 588 |
+
padding: 8px;
|
| 589 |
+
max-height: 220px;
|
| 590 |
+
overflow: auto;
|
| 591 |
+
border-radius: 8px;
|
| 592 |
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
| 593 |
+
background: rgba(2, 6, 23, 0.5);
|
| 594 |
+
color: #e5e7eb;
|
| 595 |
+
white-space: pre-wrap;
|
| 596 |
+
word-break: break-word;
|
| 597 |
+
font-size: 0.78rem;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
.research-event .chat-message-body {
|
| 601 |
+
white-space: normal;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
.chat-input-row {
|
| 605 |
display: flex;
|
| 606 |
gap: 8px;
|
ui/agent/graph/nodes/researcher.py
CHANGED
|
@@ -32,6 +32,7 @@ from .prompts import RESEARCHER_SYSTEM_PROMPT
|
|
| 32 |
|
| 33 |
def execute_tool_call(
|
| 34 |
writer: Any,
|
|
|
|
| 35 |
todo_title: str,
|
| 36 |
tool_name: str,
|
| 37 |
args: dict[str, Any],
|
|
@@ -45,6 +46,7 @@ def execute_tool_call(
|
|
| 45 |
"title": f"{title} 路 {todo_title}",
|
| 46 |
"message": pending_message,
|
| 47 |
"log": {"tool": tool_name, "arguments": args},
|
|
|
|
| 48 |
}
|
| 49 |
)
|
| 50 |
|
|
@@ -66,6 +68,7 @@ def execute_tool_call(
|
|
| 66 |
"message": _done_tool_message(tool_name, args, result),
|
| 67 |
"duration": duration,
|
| 68 |
"log": _tool_log_metadata(tool_name, args, result),
|
|
|
|
| 69 |
}
|
| 70 |
)
|
| 71 |
return result
|
|
@@ -101,7 +104,13 @@ def researcher_node(task: ResearchTask, config: RunnableConfig) -> dict[str, Any
|
|
| 101 |
messages.append(AIMessage(content=""))
|
| 102 |
|
| 103 |
for tool_name, tool_args, tool_call_id in tool_calls:
|
| 104 |
-
result = execute_tool_call(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
messages.append(
|
| 106 |
ToolMessage(
|
| 107 |
content=truncate(result, TOOL_RESULT_LIMIT),
|
|
@@ -132,5 +141,13 @@ def researcher_node(task: ResearchTask, config: RunnableConfig) -> dict[str, Any
|
|
| 132 |
"todo_title": todo["title"],
|
| 133 |
"summary": summary,
|
| 134 |
}
|
| 135 |
-
writer(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
return {"findings": [finding]}
|
|
|
|
| 32 |
|
| 33 |
def execute_tool_call(
|
| 34 |
writer: Any,
|
| 35 |
+
todo_id: int,
|
| 36 |
todo_title: str,
|
| 37 |
tool_name: str,
|
| 38 |
args: dict[str, Any],
|
|
|
|
| 46 |
"title": f"{title} 路 {todo_title}",
|
| 47 |
"message": pending_message,
|
| 48 |
"log": {"tool": tool_name, "arguments": args},
|
| 49 |
+
"research_task": {"id": todo_id, "title": todo_title},
|
| 50 |
}
|
| 51 |
)
|
| 52 |
|
|
|
|
| 68 |
"message": _done_tool_message(tool_name, args, result),
|
| 69 |
"duration": duration,
|
| 70 |
"log": _tool_log_metadata(tool_name, args, result),
|
| 71 |
+
"research_task": {"id": todo_id, "title": todo_title},
|
| 72 |
}
|
| 73 |
)
|
| 74 |
return result
|
|
|
|
| 104 |
messages.append(AIMessage(content=""))
|
| 105 |
|
| 106 |
for tool_name, tool_args, tool_call_id in tool_calls:
|
| 107 |
+
result = execute_tool_call(
|
| 108 |
+
writer,
|
| 109 |
+
todo["id"],
|
| 110 |
+
todo["title"],
|
| 111 |
+
tool_name,
|
| 112 |
+
tool_args,
|
| 113 |
+
)
|
| 114 |
messages.append(
|
| 115 |
ToolMessage(
|
| 116 |
content=truncate(result, TOOL_RESULT_LIMIT),
|
|
|
|
| 141 |
"todo_title": todo["title"],
|
| 142 |
"summary": summary,
|
| 143 |
}
|
| 144 |
+
writer(
|
| 145 |
+
{
|
| 146 |
+
"type": "finding",
|
| 147 |
+
"todo_id": todo["id"],
|
| 148 |
+
"todo_title": todo["title"],
|
| 149 |
+
"summary": summary,
|
| 150 |
+
"research_task": {"id": todo["id"], "title": todo["title"]},
|
| 151 |
+
}
|
| 152 |
+
)
|
| 153 |
return {"findings": [finding]}
|
ui/agent/graph/respond.py
CHANGED
|
@@ -119,6 +119,7 @@ class _UiState:
|
|
| 119 |
"title": str(event.get("title") or "Tool"),
|
| 120 |
"status": "pending",
|
| 121 |
"log": event.get("log") or {},
|
|
|
|
| 122 |
},
|
| 123 |
)
|
| 124 |
)
|
|
@@ -135,6 +136,7 @@ class _UiState:
|
|
| 135 |
"status": "done",
|
| 136 |
"duration": event.get("duration"),
|
| 137 |
"log": event.get("log") or {},
|
|
|
|
| 138 |
},
|
| 139 |
)
|
| 140 |
if index is not None and index < len(self.ui_messages):
|
|
@@ -159,6 +161,12 @@ class _UiState:
|
|
| 159 |
"title": f"Findings 路 {todo_title}",
|
| 160 |
"status": "done",
|
| 161 |
"markdown": True,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
},
|
| 163 |
)
|
| 164 |
)
|
|
|
|
| 119 |
"title": str(event.get("title") or "Tool"),
|
| 120 |
"status": "pending",
|
| 121 |
"log": event.get("log") or {},
|
| 122 |
+
"research_task": event.get("research_task"),
|
| 123 |
},
|
| 124 |
)
|
| 125 |
)
|
|
|
|
| 136 |
"status": "done",
|
| 137 |
"duration": event.get("duration"),
|
| 138 |
"log": event.get("log") or {},
|
| 139 |
+
"research_task": event.get("research_task"),
|
| 140 |
},
|
| 141 |
)
|
| 142 |
if index is not None and index < len(self.ui_messages):
|
|
|
|
| 161 |
"title": f"Findings 路 {todo_title}",
|
| 162 |
"status": "done",
|
| 163 |
"markdown": True,
|
| 164 |
+
"research_task": event.get("research_task")
|
| 165 |
+
or {
|
| 166 |
+
"id": event.get("todo_id"),
|
| 167 |
+
"title": todo_title,
|
| 168 |
+
},
|
| 169 |
+
"research_finding": True,
|
| 170 |
},
|
| 171 |
)
|
| 172 |
)
|