This commit is contained in:
Xi Zhang
2026-05-31 23:10:33 +01:00
parent 9b8ad88944
commit 047da86b14
11 changed files with 19 additions and 253 deletions
-11
View File
@@ -2,13 +2,6 @@
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# next.js
/.next/
@@ -29,10 +22,6 @@ next-env.d.ts
# logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# caches
.cache/
+2 -2
View File
@@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "tailwind.config.mjs",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
@@ -15,6 +15,6 @@
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
"hooks": "@/app/hooks"
}
}
+1 -24
View File
@@ -1,30 +1,7 @@
import type { NextConfig } from "next";
// EvoScientist `EvoSci deploy` backend (langgraph dev), default 127.0.0.1:6174.
// Override with LANGGRAPH_API_URL if you used `EvoSci deploy --port <other>`.
const LANGGRAPH_TARGET =
process.env.LANGGRAPH_API_URL || "http://127.0.0.1:6174";
const nextConfig: NextConfig = {
// Allow loading the dev UI (and its HMR resources) from these LAN hosts,
// e.g. opening http://192.168.0.59:3000 from a phone on the same Wi-Fi.
// Add/replace with your Mac's LAN IP (see `ipconfig getifaddr en0`).
allowedDevOrigins: ["192.168.0.59"],
// Same-origin reverse proxy to the EvoScientist backend. The browser talks
// only to this Next server; Next (running on the Mac) forwards to the
// backend on 127.0.0.1. This lets a phone reach the backend WITHOUT the
// EvoScientist repo binding to 0.0.0.0, and avoids cross-origin/CORS issues.
// In the UI's config dialog, set Deployment URL to: <this-origin>/lg
// Dev-only: rewrites don't apply to a future static export build.
async rewrites() {
return [
{
source: "/lg/:path*",
destination: `${LANGGRAPH_TARGET}/:path*`,
},
];
},
/* config options here */
};
export default nextConfig;
-32
View File
@@ -10,7 +10,6 @@
"dependencies": {
"@langchain/core": "1.1.19",
"@langchain/langgraph-sdk": "1.0.3",
"@radix-ui/colors": "^1.0.0",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.9",
@@ -38,8 +37,6 @@
},
"devDependencies": {
"@eslint/js": "^9",
"@headlessui/tailwindcss": "^0.2.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "^20",
@@ -555,19 +552,6 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@headlessui/tailwindcss": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz",
"integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"tailwindcss": "^3.0 || ^4.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1495,12 +1479,6 @@
"node": ">=12.4.0"
}
},
"node_modules/@radix-ui/colors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-1.0.1.tgz",
"integrity": "sha512-xySw8f0ZVsAEP+e7iLl3EvcBXX7gsIlC1Zso/sPBW9gIWerBTgz6axrjU+MZ39wD+WFi5h5zdWpsg3+hwt2Qsg==",
"license": "MIT"
},
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -2222,16 +2200,6 @@
"tslib": "^2.8.0"
}
},
"node_modules/@tailwindcss/container-queries": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
"integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.2.0"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz",
-3
View File
@@ -16,7 +16,6 @@
"dependencies": {
"@langchain/core": "1.1.19",
"@langchain/langgraph-sdk": "1.0.3",
"@radix-ui/colors": "^1.0.0",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.9",
@@ -44,8 +43,6 @@
},
"devDependencies": {
"@eslint/js": "^9",
"@headlessui/tailwindcss": "^0.2.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "^20",
+3 -3
View File
@@ -12,13 +12,13 @@ import {
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { DEFAULT_ASSISTANT_ID, StandaloneConfig } from "@/lib/config";
import { DEFAULT_ASSISTANT_ID, DeploymentConfig } from "@/lib/config";
interface ConfigDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSave: (config: StandaloneConfig) => void;
initialConfig?: StandaloneConfig;
onSave: (config: DeploymentConfig) => void;
initialConfig?: DeploymentConfig;
}
export function ConfigDialog({
+1 -60
View File
@@ -2,11 +2,7 @@
import { useCallback } from "react";
import { useStream } from "@langchain/langgraph-sdk/react";
import {
type Message,
type Assistant,
type Checkpoint,
} from "@langchain/langgraph-sdk";
import { type Message, type Assistant } from "@langchain/langgraph-sdk";
import { v4 as uuidv4 } from "uuid";
import type { UseStreamThread } from "@langchain/langgraph-sdk/react";
import type { TodoItem } from "@/app/types/types";
@@ -71,34 +67,6 @@ export function useChat({
[stream, activeAssistant?.config, onHistoryRevalidate]
);
const runSingleStep = useCallback(
(
messages: Message[],
checkpoint?: Checkpoint,
isRerunningSubagent?: boolean,
optimisticMessages?: Message[]
) => {
if (checkpoint) {
stream.submit(undefined, {
...(optimisticMessages
? { optimisticValues: { messages: optimisticMessages } }
: {}),
config: activeAssistant?.config,
checkpoint: checkpoint,
...(isRerunningSubagent
? { interruptAfter: ["tools"] }
: { interruptBefore: ["tools"] }),
});
} else {
stream.submit(
{ messages },
{ config: activeAssistant?.config, interruptBefore: ["tools"] }
);
}
},
[stream, activeAssistant?.config]
);
const setFiles = useCallback(
async (files: Record<string, string>) => {
if (!threadId) return;
@@ -109,29 +77,6 @@ export function useChat({
[client, threadId]
);
const continueStream = useCallback(
(hasTaskToolCall?: boolean) => {
stream.submit(undefined, {
config: {
...(activeAssistant?.config || {}),
recursion_limit: 100,
},
...(hasTaskToolCall
? { interruptAfter: ["tools"] }
: { interruptBefore: ["tools"] }),
});
// Update thread list when continuing stream
onHistoryRevalidate?.();
},
[stream, activeAssistant?.config, onHistoryRevalidate]
);
const markCurrentThreadAsResolved = useCallback(() => {
stream.submit(null, { command: { goto: "__end__", update: null } });
// Update thread list when marking thread as resolved
onHistoryRevalidate?.();
}, [stream, onHistoryRevalidate]);
const resumeInterrupt = useCallback(
(value: any) => {
stream.submit(null, { command: { resume: value } });
@@ -156,12 +101,8 @@ export function useChat({
isLoading: stream.isLoading,
isThreadLoading: stream.isThreadLoading,
interrupt: stream.interrupt,
getMessagesMetadata: stream.getMessagesMetadata,
sendMessage,
runSingleStep,
continueStream,
stopStream,
markCurrentThreadAsResolved,
resumeInterrupt,
};
}
+7 -7
View File
@@ -3,7 +3,7 @@
import React, { useState, useEffect, useCallback, Suspense } from "react";
import Image from "next/image";
import { useQueryState } from "nuqs";
import { getConfig, saveConfig, StandaloneConfig } from "@/lib/config";
import { getConfig, saveConfig, DeploymentConfig } from "@/lib/config";
import { ConfigDialog } from "@/app/components/ConfigDialog";
import { Button } from "@/components/ui/button";
import { Assistant } from "@langchain/langgraph-sdk";
@@ -19,10 +19,10 @@ import { ChatProvider } from "@/providers/ChatProvider";
import { ChatInterface } from "@/app/components/ChatInterface";
interface HomePageInnerProps {
config: StandaloneConfig;
config: DeploymentConfig;
configDialogOpen: boolean;
setConfigDialogOpen: (open: boolean) => void;
handleSaveConfig: (config: StandaloneConfig) => void;
handleSaveConfig: (config: DeploymentConfig) => void;
}
function HomePageInner({
@@ -170,7 +170,7 @@ function HomePageInner({
<div className="flex-1 overflow-hidden">
<ResizablePanelGroup
direction="horizontal"
autoSaveId="standalone-chat"
autoSaveId="evoscientist-chat"
>
{sidebar && (
<>
@@ -214,7 +214,7 @@ function HomePageInner({
}
function HomePageContent() {
const [config, setConfig] = useState<StandaloneConfig | null>(null);
const [config, setConfig] = useState<DeploymentConfig | null>(null);
const [configDialogOpen, setConfigDialogOpen] = useState(false);
const [assistantId, setAssistantId] = useQueryState("assistantId");
@@ -239,7 +239,7 @@ function HomePageContent() {
}
}, [config, assistantId, setAssistantId]);
const handleSaveConfig = useCallback((newConfig: StandaloneConfig) => {
const handleSaveConfig = useCallback((newConfig: DeploymentConfig) => {
saveConfig(newConfig);
setConfig(newConfig);
}, []);
@@ -257,7 +257,7 @@ function HomePageContent() {
/>
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold">Welcome to Standalone Chat</h1>
<h1 className="text-2xl font-bold">Welcome to EvoScientist</h1>
<p className="mt-2 text-muted-foreground">
Configure your deployment to get started
</p>
-101
View File
@@ -1,10 +1,4 @@
import { Message } from "@langchain/langgraph-sdk";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function extractStringFromMessageContent(message: Message): string {
return typeof message.content === "string"
@@ -60,98 +54,3 @@ export function extractSubAgentContent(data: unknown): string {
// Fallback for any other type
return JSON.stringify(data, null, 2);
}
export function isPreparingToCallTaskTool(messages: Message[]): boolean {
const lastMessage = messages[messages.length - 1];
return (
(lastMessage.type === "ai" &&
lastMessage.tool_calls?.some(
(call: { name?: string }) => call.name === "task"
)) ||
false
);
}
export function formatMessageForLLM(message: Message): string {
let role: string;
if (message.type === "human") {
role = "Human";
} else if (message.type === "ai") {
role = "Assistant";
} else if (message.type === "tool") {
role = `Tool Result`;
} else {
role = message.type || "Unknown";
}
const timestamp = message.id ? ` (${message.id.slice(0, 8)})` : "";
let contentText = "";
// Extract content text
if (typeof message.content === "string") {
contentText = message.content;
} else if (Array.isArray(message.content)) {
const textParts: string[] = [];
message.content.forEach((part: any) => {
if (typeof part === "string") {
textParts.push(part);
} else if (part && typeof part === "object" && part.type === "text") {
textParts.push(part.text || "");
}
// Ignore other types like tool_use in content - we handle tool calls separately
});
contentText = textParts.join("\n\n").trim();
}
// For tool messages, include additional tool metadata
if (message.type === "tool") {
const toolName = (message as any).name || "unknown_tool";
const toolCallId = (message as any).tool_call_id || "";
role = `Tool Result [${toolName}]`;
if (toolCallId) {
role += ` (call_id: ${toolCallId.slice(0, 8)})`;
}
}
// Handle tool calls from .tool_calls property (for AI messages)
const toolCallsText: string[] = [];
if (
message.type === "ai" &&
message.tool_calls &&
Array.isArray(message.tool_calls) &&
message.tool_calls.length > 0
) {
message.tool_calls.forEach((call: any) => {
const toolName = call.name || "unknown_tool";
const toolArgs = call.args ? JSON.stringify(call.args, null, 2) : "{}";
toolCallsText.push(`[Tool Call: ${toolName}]\nArguments: ${toolArgs}`);
});
}
// Combine content and tool calls
const parts: string[] = [];
if (contentText) {
parts.push(contentText);
}
if (toolCallsText.length > 0) {
parts.push(...toolCallsText);
}
if (parts.length === 0) {
return `${role}${timestamp}: [Empty message]`;
}
if (parts.length === 1) {
return `${role}${timestamp}: ${parts[0]}`;
}
return `${role}${timestamp}:\n${parts.join("\n\n")}`;
}
export function formatConversationForLLM(messages: Message[]): string {
const formattedMessages = messages.map(formatMessageForLLM);
return formattedMessages.join("\n\n---\n\n");
}
+4 -4
View File
@@ -1,4 +1,4 @@
export interface StandaloneConfig {
export interface DeploymentConfig {
deploymentUrl: string;
assistantId: string;
langsmithApiKey?: string;
@@ -11,14 +11,14 @@ export const DEFAULT_ASSISTANT_ID = "EvoScientist";
const CONFIG_KEY = "evoscientist-config";
export function getConfig(): StandaloneConfig | null {
export function getConfig(): DeploymentConfig | null {
if (typeof window === "undefined") return null;
const stored = localStorage.getItem(CONFIG_KEY);
if (!stored) return null;
try {
const parsed = JSON.parse(stored) as StandaloneConfig;
const parsed = JSON.parse(stored) as DeploymentConfig;
// Always pin the assistant to the EvoScientist main agent.
return { ...parsed, assistantId: DEFAULT_ASSISTANT_ID };
} catch {
@@ -26,7 +26,7 @@ export function getConfig(): StandaloneConfig | null {
}
}
export function saveConfig(config: StandaloneConfig): void {
export function saveConfig(config: DeploymentConfig): void {
if (typeof window === "undefined") return;
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
}
-5
View File
@@ -1,10 +1,7 @@
import { blackA, green, mauve, slate, violet } from "@radix-ui/colors";
import plugin from "tailwindcss/plugin";
import containerQueries from "@tailwindcss/container-queries";
import typography from "@tailwindcss/typography";
import forms from "@tailwindcss/forms";
import tailwindcssAnimate from "tailwindcss-animate";
import headlessui from "@headlessui/tailwindcss";
/** @type {import('tailwindcss').Config} */
export default {
@@ -366,11 +363,9 @@ export default {
},
},
plugins: [
containerQueries,
typography,
forms,
tailwindcssAnimate,
headlessui,
plugin(({ addUtilities, addBase }) => {
addBase({
input: {