Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion argi-studio/agent-chat-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"test": "node --test src/lib/argi-api.test.mjs",
"test": "node --test src/lib/argi-api.test.mjs src/components/graph/graph-output.test.mjs",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
Expand Down
15 changes: 5 additions & 10 deletions argi-studio/agent-chat-ui/src/components/graph/GraphChatArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import React, { useState, FormEvent, useMemo } from "react";
import { useGraphStream } from "@/providers/GraphStream";
import { useGraphThreads } from "@/providers/GraphThread";
import { Button } from "@/components/ui/button";

function extractTextFromState(state: Record<string, unknown> | null): string {
if (!state?.messages || !Array.isArray(state.messages)) return "";
const messages = state.messages as Array<{ content?: string; text?: string }>;
const last = messages[messages.length - 1];
return last?.content ?? last?.text ?? "";
}
import { extractTextContent, extractTextFromState } from "./graph-output";

export function GraphChatArea() {
const {
Expand Down Expand Up @@ -40,10 +34,11 @@ export function GraphChatArea() {
if (accumulatedChunk) return accumulatedChunk;
const fromState = extractTextFromState(currentState);
if (fromState) return fromState;
const lastWithMessage = [...nodeOutputs]
const lastMessageText = [...nodeOutputs]
.reverse()
.find((o) => o.message?.content);
return lastWithMessage?.message?.content ?? "";
.map((output) => extractTextContent(output.message?.content))
.find((text) => text.length > 0);
return lastMessageText ?? "";
}, [accumulatedChunk, currentState, nodeOutputs]);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import vm from "node:vm";
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const ts = require("typescript");

function loadGraphOutputModule() {
const sourcePath = path.resolve("src/components/graph/graph-output.ts");
const source = fs.readFileSync(sourcePath, "utf8");

const { outputText } = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
},
});

const module = { exports: {} };

vm.runInNewContext(outputText, {
exports: module.exports,
module,
});

return module.exports;
}

test("preserves legacy string message content", () => {
const { extractTextContent } = loadGraphOutputModule();

assert.equal(extractTextContent("Hello ARGI"), "Hello ARGI");
});

test("extracts text from structured content parts", () => {
const { extractTextContent } = loadGraphOutputModule();
const content = [
{ type: "TEXT", text: "Hello " },
{ type: "TEXT", text: "ARGI" },
];

assert.equal(extractTextContent(content), "Hello ARGI");
});

test("uses textual state result when no message text exists", () => {
const { extractTextFromState } = loadGraphOutputModule();
const state = { result: "echo: hello" };

assert.equal(extractTextFromState(state), "echo: hello");
});

test("uses state result when messages contain only user content", () => {
const { extractTextFromState } = loadGraphOutputModule();
const state = {
result: "echo: hello",
messages: [
{
role: "USER",
content: [{ type: "TEXT", text: "hello" }],
},
],
};

assert.equal(extractTextFromState(state), "echo: hello");
});
77 changes: 77 additions & 0 deletions argi-studio/agent-chat-ui/src/components/graph/graph-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}

export function extractTextContent(content: unknown): string {
if (typeof content === "string") {
return content;
}

if (!Array.isArray(content)) {
return "";
}

return content
.map((part) => {
if (
!isRecord(part) ||
part.type !== "TEXT" ||
typeof part.text !== "string"
) {
return "";
}

return part.text;
})
.join("");
}

export function extractTextFromState(
state: Record<string, unknown> | null,
): string {
if (!state) {
return "";
}

let legacyMessageText = "";
if (Array.isArray(state.messages)) {
for (let index = state.messages.length - 1; index >= 0; index -= 1) {
const message = state.messages[index];
if (!isRecord(message)) {
continue;
}

let messageText = extractTextContent(message.content);
if (!messageText && typeof message.text === "string") {
messageText = message.text;
}
if (!messageText) {
continue;
}

const role =
typeof message.role === "string"
? message.role
: typeof message.messageType === "string"
? message.messageType
: "";
if (role.toUpperCase() === "ASSISTANT") {
return messageText;
}

if (
!("role" in message) &&
!("messageType" in message) &&
!legacyMessageText
) {
legacyMessageText = messageText;
}
}
}

if (typeof state.result === "string") {
return state.result;
}

return legacyMessageText;
}
4 changes: 3 additions & 1 deletion argi-studio/agent-chat-ui/src/providers/GraphStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React, {
import { createApiClient, GraphRunResponse } from "@/lib/argi-api";
import { toast } from "sonner";
import { useGraphThreads } from "./GraphThread";
import { extractTextContent } from "@/components/graph/graph-output";

export interface GraphNodeOutput extends GraphRunResponse {
/** Index in execution order */
Expand Down Expand Up @@ -105,7 +106,8 @@ export function GraphStreamProvider({ children }: GraphStreamProviderProps) {
if (event.state && Object.keys(event.state).length > 0) {
setCurrentState(event.state as Record<string, unknown>);
}
const text = event.chunk ?? event.message?.content ?? "";
const text =
event.chunk ?? extractTextContent(event.message?.content);
if (text) {
setAccumulatedChunk((prev) => prev + text);
}
Expand Down