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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genui-widget",
"version": "0.6.2",
"version": "0.6.3",
"type": "module",
"description": "An embeddable chat widget that lets your AI chatbots render rich, interactive UI like buttons, forms, charts, cards and more instead of plain text. Works out of the box with LangGraph/LangChain and n8n.",
"main": "./dist/genui-widget.umd.js",
Expand Down
46 changes: 38 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import "@crayonai/react-ui/styles/index.css";
import type { ChatConfig, ChatInstance } from "./types";
import { createStorageAdapter, LangGraphStorageAdapter } from "./storage";
import type { StorageAdapter } from "./storage";
import { createChatProvider, type ChatProvider } from "./providers";
import {
createChatProvider,
N8NProvider,
type ChatProvider,
} from "./providers";
import { log, handleError, normalizeError } from "./utils/logger";
import "./styles/widget.css";

Expand Down Expand Up @@ -165,6 +169,7 @@ function ChatWithPersistence({
}

const isLangGraph = storage instanceof LangGraphStorageAdapter;
const isN8N = provider instanceof N8NProvider;

// Save user messages (skip for LangGraph - messages are persisted via runs)
if (!isLangGraph) {
Expand All @@ -188,17 +193,12 @@ function ChatWithPersistence({
);
const response = await sendMessage();

// For LangGraph, messages are automatically persisted by the run
// Just return the response directly
if (isLangGraph) {
return response;
}

// For other storage types, wrap stream to save assistant message when complete
// Wrap stream to check for thesys=true and optionally save messages
if (response.body) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = "";
let isContentThesysChunkPresent = false;

const wrappedStream = new ReadableStream({
async start(controller) {
Expand All @@ -209,9 +209,37 @@ function ChatWithPersistence({

const text = decoder.decode(value, { stream: true });
fullContent += text;

// Check if response body contains thesys=true
const regex = new RegExp(/thesys=\\?"true\\?"/);
if (!isContentThesysChunkPresent && regex.test(fullContent)) {
isContentThesysChunkPresent = true;
}

controller.enqueue(value);
}

if (!isContentThesysChunkPresent) {
console.log("fullContent", fullContent);
const errorDetails =
"Widget received invalid response format. " +
(isN8N
? "Please follow the integration steps in www.thesys.dev/n8n"
: "Please check the documentation for integration");
const ERROR_MESSAGE = {
message: "INVALID_RESPONSE_FORMAT",
details: errorDetails,
};
throw new Error(JSON.stringify(ERROR_MESSAGE));
}

// For LangGraph, messages are automatically persisted by the run
// Skip saving for LangGraph
if (isLangGraph) {
controller.close();
return;
}

// Save complete thread after stream ends
const assistantMessage: Message = {
id: responseId,
Expand All @@ -237,6 +265,8 @@ function ChatWithPersistence({

controller.close();
} catch (error) {
// Handle streaming errors (e.g., "No thesys=true chunk found")
handleError(error, "[Provider] Streaming failed", config.onError);
controller.error(error);
}
},
Expand Down