Enable LLM Call Observability in AGS (#5457)

<!-- Thank you for your contribution! Please review
https://microsoft.github.io/autogen/docs/Contribute before opening a
pull request. -->

<!-- Please add a reviewer to the assignee section when you create a PR.
If you don't have the access to it, we will shortly find a reviewer and
assign them to your PR. -->

It is often helpful to inspect the raw request and response to/from an
LLM as agents act.
This PR, does the following:

- Updates TeamManager to yield LLMCallEvents from core library
- Run in an async background task to listen for LLMCallEvent JIT style
when a team is run
   - Add events to   an async queue and 
- yield those events in addition to whatever actual agentchat
team.run_stream yields.
- Update the AGS UI to show those LLMCallEvents in the messages section
as a team runs
   - Add settings panel to show/hide llm call events in messages. 
- Minor updates to default team

<img width="1539" alt="image"
src="https://github.com/user-attachments/assets/bfbb19fe-3560-4faa-b600-7dd244e0e974"
/>
<img width="1554" alt="image"
src="https://github.com/user-attachments/assets/775624f5-ba83-46e8-81ff-512bfeed6bab"
/>
<img width="1538" alt="image"
src="https://github.com/user-attachments/assets/3becbf85-b75a-4506-adb7-e5575ebbaec4"
/>


## Why are these changes needed?

<!-- Please give a short summary of the change and the problem this
solves. -->

## Related issue number

<!-- For example: "Closes #1234" -->

Closes #5440 

## Checks

- [ ] I've included any doc changes needed for
https://microsoft.github.io/autogen/. See
https://microsoft.github.io/autogen/docs/Contribute#documentation to
build and test documentation locally.
- [ ] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [ ] I've made sure all auto checks have passed.
This commit is contained in:
Victor Dibia 2025-02-08 20:50:12 -08:00 committed by GitHub
parent b868e32b05
commit 7fc7f383f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 801 additions and 209 deletions

View File

@ -4,6 +4,7 @@ from .types import (
GalleryComponents,
GalleryItems,
GalleryMetadata,
LLMCallEventMessage,
MessageConfig,
MessageMeta,
Response,
@ -22,4 +23,5 @@ __all__ = [
"TeamResult",
"Response",
"SocketMessage",
"LLMCallEventMessage",
]

View File

@ -2,6 +2,7 @@ from datetime import datetime
from typing import Any, Dict, List, Optional
from autogen_agentchat.base import TaskResult
from autogen_agentchat.messages import BaseChatMessage
from autogen_core import ComponentModel
from pydantic import BaseModel
@ -18,6 +19,11 @@ class TeamResult(BaseModel):
duration: float
class LLMCallEventMessage(BaseChatMessage):
source: str = "llm_call_event"
content: str
class MessageMeta(BaseModel):
task: Optional[str] = None
task_result: Optional[TaskResult] = None

View File

@ -240,7 +240,7 @@ Read the above conversation. Then select the next role from {participants} to pl
builder.add_team(
websurfer_team.dump_component(),
label="Web Agent Team (Operator)",
description="A group chat team that have participants takes turn to publish a message\n to all, using a ChatCompletion model to select the next speaker after each message.",
description="A team with 3 agents - a Web Surfer agent that can browse the web, a Verification Assistant that verifies and summarizes information, and a User Proxy that provides human feedback when needed.",
)
builder.add_tool(
@ -347,7 +347,7 @@ Read the above conversation. Then select the next role from {participants} to pl
builder.add_team(
deep_research_team.dump_component(),
label="Deep Research Team",
description="A team that performs deep research using web searches, verification, and summarization.",
description="A team with 3 agents - a Research Assistant that performs web searches and analyzes information, a Verifier that ensures research quality and completeness, and a Summary Agent that provides a detailed markdown summary of the research as a report to the user.",
)
return builder.build()

View File

@ -12,10 +12,10 @@ from bs4 import BeautifulSoup
async def bing_search(
query: str,
num_results: int = 5,
num_results: int = 3,
include_snippets: bool = True,
include_content: bool = True,
content_max_length: Optional[int] = 15000,
content_max_length: Optional[int] = 10000,
language: str = "en",
country: Optional[str] = None,
safe_search: str = "moderate",

View File

@ -11,10 +11,10 @@ from bs4 import BeautifulSoup
async def google_search(
query: str,
num_results: int = 5,
num_results: int = 3,
include_snippets: bool = True,
include_content: bool = True,
content_max_length: Optional[int] = 15000,
content_max_length: Optional[int] = 10000,
language: str = "en",
country: Optional[str] = None,
safe_search: bool = True,

View File

@ -1,3 +1,4 @@
import asyncio
import json
import logging
import time
@ -8,13 +9,26 @@ import aiofiles
import yaml
from autogen_agentchat.base import TaskResult, Team
from autogen_agentchat.messages import AgentEvent, ChatMessage
from autogen_core import CancellationToken, Component, ComponentModel
from autogen_core import EVENT_LOGGER_NAME, CancellationToken, Component, ComponentModel
from autogen_core.logging import LLMCallEvent
from ..datamodel.types import TeamResult
from ..datamodel.types import LLMCallEventMessage, TeamResult
logger = logging.getLogger(__name__)
class RunEventLogger(logging.Handler):
"""Event logger that queues LLMCallEvents for streaming"""
def __init__(self):
super().__init__()
self.events = asyncio.Queue()
def emit(self, record: logging.LogRecord):
if isinstance(record.msg, LLMCallEvent):
self.events.put_nowait(LLMCallEventMessage(content=str(record.msg)))
class TeamManager:
"""Manages team operations including loading configs and running teams"""
@ -35,14 +49,7 @@ class TeamManager:
@staticmethod
async def load_from_directory(directory: Union[str, Path]) -> List[dict]:
"""Load all team configurations from a directory
Args:
directory (Union[str, Path]): Path to directory containing config files
Returns:
List[dict]: List of loaded team configurations
"""
"""Load all team configurations from a directory"""
directory = Path(directory)
configs = []
valid_extensions = {".json", ".yaml", ".yml"}
@ -61,7 +68,6 @@ class TeamManager:
self, team_config: Union[str, Path, dict, ComponentModel], input_func: Optional[Callable] = None
) -> Component:
"""Create team instance from config"""
# Handle different input types
if isinstance(team_config, (str, Path)):
config = await self.load_from_file(team_config)
elif isinstance(team_config, dict):
@ -69,14 +75,12 @@ class TeamManager:
else:
config = team_config.model_dump()
# Use Component.load_component directly
team = Team.load_component(config)
for agent in team._participants:
if hasattr(agent, "input_func"):
agent.input_func = input_func
# TBD - set input function
return team
async def run_stream(
@ -85,11 +89,17 @@ class TeamManager:
team_config: Union[str, Path, dict, ComponentModel],
input_func: Optional[Callable] = None,
cancellation_token: Optional[CancellationToken] = None,
) -> AsyncGenerator[Union[AgentEvent | ChatMessage, ChatMessage, TaskResult], None]:
) -> AsyncGenerator[Union[AgentEvent | ChatMessage | LLMCallEvent, ChatMessage, TeamResult], None]:
"""Stream team execution results"""
start_time = time.time()
team = None
# Setup logger correctly
logger = logging.getLogger(EVENT_LOGGER_NAME)
logger.setLevel(logging.INFO)
llm_event_logger = RunEventLogger()
logger.handlers = [llm_event_logger] # Replace all handlers
try:
team = await self._create_team(team_config, input_func)
@ -102,7 +112,15 @@ class TeamManager:
else:
yield message
# Check for any LLM events
while not llm_event_logger.events.empty():
event = await llm_event_logger.events.get()
yield event
finally:
# Cleanup - remove our handler
logger.handlers.remove(llm_event_logger)
# Ensure cleanup happens
if team and hasattr(team, "_participants"):
for agent in team._participants:
@ -127,7 +145,6 @@ class TeamManager:
return TeamResult(task_result=result, usage="", duration=time.time() - start_time)
finally:
# Ensure cleanup happens
if team and hasattr(team, "_participants"):
for agent in team._participants:
if hasattr(agent, "close"):

View File

@ -15,11 +15,6 @@ from .deps import cleanup_managers, init_managers
from .initialization import AppInitializer
from .routes import runs, sessions, teams, ws
# Configure logging
# logger = logging.getLogger(__name__)
# logging.basicConfig(level=logging.INFO)
# Initialize application
app_file_path = os.path.dirname(os.path.abspath(__file__))
initializer = AppInitializer(settings, app_file_path)

View File

@ -21,7 +21,7 @@ from autogen_core import Image as AGImage
from fastapi import WebSocket, WebSocketDisconnect
from ...database import DatabaseManager
from ...datamodel import Message, MessageConfig, Run, RunStatus, TeamResult
from ...datamodel import LLMCallEventMessage, Message, MessageConfig, Run, RunStatus, TeamResult
from ...teammanager import TeamManager
logger = logging.getLogger(__name__)
@ -111,6 +111,7 @@ class WebSocketManager:
HandoffMessage,
ToolCallRequestEvent,
ToolCallExecutionEvent,
LLMCallEventMessage,
),
):
await self._save_message(run_id, message)
@ -328,7 +329,15 @@ class WebSocketManager:
}
elif isinstance(
message, (TextMessage, StopMessage, HandoffMessage, ToolCallRequestEvent, ToolCallExecutionEvent)
message,
(
TextMessage,
StopMessage,
HandoffMessage,
ToolCallRequestEvent,
ToolCallExecutionEvent,
LLMCallEventMessage,
),
):
return {"type": "message", "data": message.model_dump()}

View File

@ -220,7 +220,7 @@ const Sidebar = ({ link, meta, isMobile }: SidebarProps) => {
],
})
}
className="group hidden flex gap-x-3 rounded-md p-2 text-sm font-medium text-primary hover:text-accent hover:bg-secondary justify-center"
className="group flex gap-x-3 rounded-md p-2 text-sm font-medium text-primary hover:text-accent hover:bg-secondary justify-center"
>
<Settings className="h-6 w-6 shrink-0 text-secondary group-hover:text-accent" />
</Link>
@ -248,7 +248,7 @@ const Sidebar = ({ link, meta, isMobile }: SidebarProps) => {
) : (
<div className="flex items-center gap-2">
<div className="w-full ">
<div className="hidden">
<div className="">
{" "}
<Link
to="/settings"

View File

@ -252,7 +252,13 @@ export interface SessionRuns {
}
export interface WebSocketMessage {
type: "message" | "result" | "completion" | "input_request" | "error";
type:
| "message"
| "result"
| "completion"
| "input_request"
| "error"
| "llm_call_event";
data?: AgentMessageConfig | TaskResult;
status?: RunStatus;
error?: string;

View File

@ -1,6 +1,17 @@
import React, { memo, useState } from "react";
import { Loader2, Maximize2, Minimize2, X } from "lucide-react";
import {
ArrowDown,
ArrowUp,
ChevronDown,
ChevronUp,
Fullscreen,
Loader2,
Maximize2,
Minimize2,
X,
} from "lucide-react";
import ReactMarkdown from "react-markdown";
import { Tooltip } from "antd";
export const LoadingIndicator = ({ size = 16 }: { size: number }) => (
<div className="inline-flex items-center gap-2 text-accent mr-2">
@ -41,6 +52,11 @@ export const LoadingDots = ({ size = 8 }) => {
);
};
// import { memo, useState } from 'react';
// import ReactMarkdown from 'react-markdown';
// import { Minimize2, Maximize2, ArrowsMaximize, X } from 'lucide-react';
// import { Tooltip } from 'antd';
export const TruncatableText = memo(
({
content,
@ -48,14 +64,17 @@ export const TruncatableText = memo(
className = "",
jsonThreshold = 1000,
textThreshold = 500,
showFullscreen = true,
}: {
content: string;
isJson?: boolean;
className?: string;
jsonThreshold?: number;
textThreshold?: number;
showFullscreen?: boolean;
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
const threshold = isJson ? jsonThreshold : textThreshold;
const shouldTruncate = content.length > threshold;
@ -72,7 +91,7 @@ export const TruncatableText = memo(
<div className="relative">
<div
className={`
transition-[max-height,opacity] duration-500 ease-in-out
transition-[max-height,opacity] overflow-auto scroll duration-500 ease-in-out
${
shouldTruncate && !isExpanded
? "max-h-[300px]"
@ -81,32 +100,71 @@ export const TruncatableText = memo(
${className}
`}
>
{/* {displayContent} */}
<ReactMarkdown>{displayContent}</ReactMarkdown>
{shouldTruncate && !isExpanded && (
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-secondary/20 to-transparent" />
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-secondary to-transparent opacity-20" />
)}
</div>
{shouldTruncate && (
<div className="mt-2 flex items-center justify-end">
<button
type="button"
onClick={toggleExpand}
className={`
inline-flex items-center gap-2 px-3 py-1.5
rounded bg-secondary/80
text-xs font-medium
transition-all duration-300
hover:text-accent
hover:scale-105
z-10
`}
aria-label={isExpanded ? "less" : "more"}
<div className="mt-2 flex items-center justify-end gap-2">
<Tooltip title={isExpanded ? "Show less" : "Show more"}>
<button
type="button"
onClick={toggleExpand}
className="inline-flex items-center justify-center p-2 rounded bg-secondary text-primary hover:text-accent hover:scale-105 transition-all duration-300 z-10"
aria-label={isExpanded ? "Show less" : "Show more"}
>
{isExpanded ? (
<ChevronUp size={18} />
) : (
<ChevronDown size={18} />
)}
</button>
</Tooltip>
{showFullscreen && (
<Tooltip title="Fullscreen">
<button
type="button"
onClick={() => setIsFullscreen(true)}
className="inline-flex items-center justify-center p-2 rounded bg-secondary text-primary hover:text-accent hover:scale-105 transition-all duration-300 z-10"
aria-label="Toggle fullscreen"
>
<Maximize2 size={18} />
</button>
</Tooltip>
)}
</div>
)}
{isFullscreen && (
<div
className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center"
onClick={() => setIsFullscreen(false)}
>
<div
className="relative bg-secondary w-full h-full md:w-4/5 md:h-4/5 md:rounded-lg p-8 overflow-auto"
style={{ opacity: 0.95 }}
onClick={(e) => e.stopPropagation()}
>
<span>{isExpanded ? "Show less" : "Show more"}</span>
{isExpanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button>
<Tooltip title="Close">
<button
onClick={() => setIsFullscreen(false)}
className="absolute top-4 right-4 p-2 rounded-full bg-black/50 hover:bg-black/70 text-primary transition-colors"
aria-label="Close fullscreen view"
>
<X size={24} />
</button>
</Tooltip>
<div className="mt-8 text-base text-primary">
{isJson ? (
<pre className="whitespace-pre-wrap">{content}</pre>
) : (
<ReactMarkdown>{content}</ReactMarkdown>
)}
</div>
</div>
</div>
)}
</div>

View File

@ -828,7 +828,7 @@
"description": "A tool that performs Google searches using the Google Custom Search API. Requires the requests library, [GOOGLE_API_KEY, GOOGLE_CSE_ID] to be set, env variable to function.",
"label": "Google Search Tool",
"config": {
"source_code": "async def google_search(\n query: str,\n num_results: int = 5,\n include_snippets: bool = True,\n include_content: bool = True,\n content_max_length: Optional[int] = 15000,\n language: str = \"en\",\n country: Optional[str] = None,\n safe_search: bool = True,\n) -> List[Dict[str, str]]:\n \"\"\"\n Perform a Google search using the Custom Search API and optionally fetch webpage content.\n\n Args:\n query: Search query string\n num_results: Number of results to return (max 10)\n include_snippets: Include result snippets in output\n include_content: Include full webpage content in markdown format\n content_max_length: Maximum length of webpage content (if included)\n language: Language code for search results (e.g., en, es, fr)\n country: Optional country code for search results (e.g., us, uk)\n safe_search: Enable safe search filtering\n\n Returns:\n List[Dict[str, str]]: List of search results, each containing:\n - title: Result title\n - link: Result URL\n - snippet: Result description (if include_snippets=True)\n - content: Webpage content in markdown (if include_content=True)\n \"\"\"\n api_key = os.getenv(\"GOOGLE_API_KEY\")\n cse_id = os.getenv(\"GOOGLE_CSE_ID\")\n\n if not api_key or not cse_id:\n raise ValueError(\"Missing required environment variables. Please set GOOGLE_API_KEY and GOOGLE_CSE_ID.\")\n\n num_results = min(max(1, num_results), 10)\n\n async def fetch_page_content(url: str, max_length: Optional[int] = 50000) -> str:\n \"\"\"Helper function to fetch and convert webpage content to markdown\"\"\"\n headers = {\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"}\n\n try:\n async with httpx.AsyncClient() as client:\n response = await client.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n\n soup = BeautifulSoup(response.text, \"html.parser\")\n\n # Remove script and style elements\n for script in soup([\"script\", \"style\"]):\n script.decompose()\n\n # Convert relative URLs to absolute\n for tag in soup.find_all([\"a\", \"img\"]):\n if tag.get(\"href\"):\n tag[\"href\"] = urljoin(url, tag[\"href\"])\n if tag.get(\"src\"):\n tag[\"src\"] = urljoin(url, tag[\"src\"])\n\n h2t = html2text.HTML2Text()\n h2t.body_width = 0\n h2t.ignore_images = False\n h2t.ignore_emphasis = False\n h2t.ignore_links = False\n h2t.ignore_tables = False\n\n markdown = h2t.handle(str(soup))\n\n if max_length and len(markdown) > max_length:\n markdown = markdown[:max_length] + \"\\n...(truncated)\"\n\n return markdown.strip()\n\n except Exception as e:\n return f\"Error fetching content: {str(e)}\"\n\n params = {\n \"key\": api_key,\n \"cx\": cse_id,\n \"q\": query,\n \"num\": num_results,\n \"hl\": language,\n \"safe\": \"active\" if safe_search else \"off\",\n }\n\n if country:\n params[\"gl\"] = country\n\n try:\n async with httpx.AsyncClient() as client:\n response = await client.get(\"https://www.googleapis.com/customsearch/v1\", params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n\n results = []\n if \"items\" in data:\n for item in data[\"items\"]:\n result = {\"title\": item.get(\"title\", \"\"), \"link\": item.get(\"link\", \"\")}\n if include_snippets:\n result[\"snippet\"] = item.get(\"snippet\", \"\")\n\n if include_content:\n result[\"content\"] = await fetch_page_content(result[\"link\"], max_length=content_max_length)\n\n results.append(result)\n\n return results\n\n except httpx.RequestError as e:\n raise ValueError(f\"Failed to perform search: {str(e)}\") from e\n except KeyError as e:\n raise ValueError(f\"Invalid API response format: {str(e)}\") from e\n except Exception as e:\n raise ValueError(f\"Error during search: {str(e)}\") from e\n",
"source_code": "async def google_search(\n query: str,\n num_results: int = 3,\n include_snippets: bool = True,\n include_content: bool = True,\n content_max_length: Optional[int] = 15000,\n language: str = \"en\",\n country: Optional[str] = None,\n safe_search: bool = True,\n) -> List[Dict[str, str]]:\n \"\"\"\n Perform a Google search using the Custom Search API and optionally fetch webpage content.\n\n Args:\n query: Search query string\n num_results: Number of results to return (max 10)\n include_snippets: Include result snippets in output\n include_content: Include full webpage content in markdown format\n content_max_length: Maximum length of webpage content (if included)\n language: Language code for search results (e.g., en, es, fr)\n country: Optional country code for search results (e.g., us, uk)\n safe_search: Enable safe search filtering\n\n Returns:\n List[Dict[str, str]]: List of search results, each containing:\n - title: Result title\n - link: Result URL\n - snippet: Result description (if include_snippets=True)\n - content: Webpage content in markdown (if include_content=True)\n \"\"\"\n api_key = os.getenv(\"GOOGLE_API_KEY\")\n cse_id = os.getenv(\"GOOGLE_CSE_ID\")\n\n if not api_key or not cse_id:\n raise ValueError(\"Missing required environment variables. Please set GOOGLE_API_KEY and GOOGLE_CSE_ID.\")\n\n num_results = min(max(1, num_results), 10)\n\n async def fetch_page_content(url: str, max_length: Optional[int] = 50000) -> str:\n \"\"\"Helper function to fetch and convert webpage content to markdown\"\"\"\n headers = {\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"}\n\n try:\n async with httpx.AsyncClient() as client:\n response = await client.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n\n soup = BeautifulSoup(response.text, \"html.parser\")\n\n # Remove script and style elements\n for script in soup([\"script\", \"style\"]):\n script.decompose()\n\n # Convert relative URLs to absolute\n for tag in soup.find_all([\"a\", \"img\"]):\n if tag.get(\"href\"):\n tag[\"href\"] = urljoin(url, tag[\"href\"])\n if tag.get(\"src\"):\n tag[\"src\"] = urljoin(url, tag[\"src\"])\n\n h2t = html2text.HTML2Text()\n h2t.body_width = 0\n h2t.ignore_images = False\n h2t.ignore_emphasis = False\n h2t.ignore_links = False\n h2t.ignore_tables = False\n\n markdown = h2t.handle(str(soup))\n\n if max_length and len(markdown) > max_length:\n markdown = markdown[:max_length] + \"\\n...(truncated)\"\n\n return markdown.strip()\n\n except Exception as e:\n return f\"Error fetching content: {str(e)}\"\n\n params = {\n \"key\": api_key,\n \"cx\": cse_id,\n \"q\": query,\n \"num\": num_results,\n \"hl\": language,\n \"safe\": \"active\" if safe_search else \"off\",\n }\n\n if country:\n params[\"gl\"] = country\n\n try:\n async with httpx.AsyncClient() as client:\n response = await client.get(\"https://www.googleapis.com/customsearch/v1\", params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n\n results = []\n if \"items\" in data:\n for item in data[\"items\"]:\n result = {\"title\": item.get(\"title\", \"\"), \"link\": item.get(\"link\", \"\")}\n if include_snippets:\n result[\"snippet\"] = item.get(\"snippet\", \"\")\n\n if include_content:\n result[\"content\"] = await fetch_page_content(result[\"link\"], max_length=content_max_length)\n\n results.append(result)\n\n return results\n\n except httpx.RequestError as e:\n raise ValueError(f\"Failed to perform search: {str(e)}\") from e\n except KeyError as e:\n raise ValueError(f\"Invalid API response format: {str(e)}\") from e\n except Exception as e:\n raise ValueError(f\"Error during search: {str(e)}\") from e\n",
"name": "google_search",
"description": "\n Perform Google searches using the Custom Search API with optional webpage content fetching.\n Requires GOOGLE_API_KEY and GOOGLE_CSE_ID environment variables to be set.\n ",
"global_imports": [

View File

@ -151,7 +151,7 @@ export const useGalleryStore = create<GalleryStore>()(
},
}),
{
name: "gallery-storage-v3",
name: "gallery-storage-v4",
}
)
);

View File

@ -195,23 +195,6 @@ const createNode = (
};
}
// if (type === "task") {
// return {
// id,
// type: "agentNode",
// position: { x: 0, y: 0 },
// data: {
// type: "task",
// label: "Task",
// description: run?.task.content || "",
// isActive: false,
// status: null,
// reason: null,
// draggable: false,
// },
// };
// }
return {
id,
type: "agentNode",
@ -567,7 +550,7 @@ const AgentFlow: React.FC<AgentFlowProps> = ({ teamConfig, run }) => {
<ReactFlow {...reactFlowProps}>
{settings.showGrid && <Background />}
{settings.showMiniMap && <MiniMap />}
<div className="absolute top-0 right-0 z-50">
<div className="absolute top-0 right-0 z-10">
<AgentFlowToolbar {...toolbarProps} />
</div>
</ReactFlow>

View File

@ -0,0 +1,187 @@
import React, { useMemo, useState } from "react";
import { Terminal, Maximize2, X } from "lucide-react";
import { TruncatableText } from "../../atoms";
import { Tooltip } from "antd";
interface LLMLogEvent {
type: "LLMCall";
messages: {
content: string;
role: string;
name?: string;
}[];
response: {
id: string;
choices: {
message: {
content: string;
role: string;
};
}[];
usage: {
completion_tokens: number;
prompt_tokens: number;
total_tokens: number;
};
model: string;
};
prompt_tokens: number;
completion_tokens: number;
agent_id: string;
}
interface LLMLogRendererProps {
content: string;
}
const formatTokens = (tokens: number) => {
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : tokens;
};
const FullLogView = ({
event,
onClose,
}: {
event: LLMLogEvent;
onClose: () => void;
}) => (
<div
className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center transition-opacity duration-300"
onClick={onClose}
>
<div
className="relative bg-primary w-full h-full md:w-4/5 md:h-4/5 md:rounded-lg p-8 overflow-auto"
style={{ opacity: 0.95 }}
onClick={(e) => e.stopPropagation()}
>
<Tooltip title="Close">
<button
onClick={onClose}
className="absolute top-4 right-4 p-2 rounded-full bg-black/50 hover:bg-black/70 text-primary transition-colors"
>
<X size={24} />
</button>
</Tooltip>
<div className="space-y-4">
<div className="flex items-center gap-2 mb-4">
<Terminal size={20} className="text-accent" />
<h3 className="text-lg font-medium">LLM Call Details</h3>
<h4 className="text-sm text-secondary">
{event.agent_id.split("/")[0]} {event.response.model} {" "}
{formatTokens(event.response.usage.total_tokens)} tokens
</h4>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Messages</h4>
{event.messages.map((msg, idx) => (
<div key={idx} className="p-4 bg-tertiary rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-xs font-medium uppercase text-secondary">
{msg.role} {msg.name && `(${msg.name})`}
</span>
</div>
<TruncatableText
content={msg.content}
textThreshold={1000}
showFullscreen={false}
/>
</div>
))}
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Response</h4>
<div className="p-4 bg-tertiary rounded-lg">
<TruncatableText
content={event.response.choices[0]?.message.content}
textThreshold={1000}
/>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="p-3 bg-tertiary rounded-lg">
<div className="text-xs text-secondary mb-1">Model</div>
<div className="font-medium">{event.response.model}</div>
</div>
<div className="p-3 bg-tertiary rounded-lg">
<div className="text-xs text-secondary mb-1">Prompt Tokens</div>
<div className="font-medium">
{event.response.usage.prompt_tokens}
</div>
</div>
<div className="p-3 bg-tertiary rounded-lg">
<div className="text-xs text-secondary mb-1">Completion Tokens</div>
<div className="font-medium">
{event.response.usage.completion_tokens}
</div>
</div>
<div className="p-3 bg-tertiary rounded-lg">
<div className="text-xs text-secondary mb-1">Total Tokens</div>
<div className="font-medium">
{event.response.usage.total_tokens}
</div>
</div>
</div>
</div>
</div>
</div>
);
const LLMLogRenderer: React.FC<LLMLogRendererProps> = ({ content }) => {
const [showFullLog, setShowFullLog] = useState(false);
const parsedContent = useMemo(() => {
try {
return JSON.parse(content) as LLMLogEvent;
} catch (e) {
console.error("Failed to parse LLM log content:", e);
return null;
}
}, [content]);
if (!parsedContent) {
return (
<div className="flex items-center gap-2 text-red-500 p-2 bg-red-500/10 rounded">
<Terminal size={16} />
<span>Invalid log format</span>
</div>
);
}
const { messages, response, agent_id } = parsedContent;
const agentName = messages[0]?.name || "Agent";
const totalTokens = response.usage.total_tokens;
const shortAgentId = agent_id ? `${agent_id.split("/")[0]}` : "";
return (
<>
<div className="flex items-center gap-2 py-2 bg-secondary/20 rounded-lg text-sm text-secondary hover:text-primary transition-colors group">
<Terminal size={14} className="text-accent" />
<span className="flex-1">
{shortAgentId ? `${shortAgentId}` : ""} {response.model} {" "}
{formatTokens(totalTokens)} tokens
</span>
<Tooltip title="View details">
<button
onClick={() => setShowFullLog(true)}
className="p-1 mr-1 hover:bg-secondary rounded-md transition-colors"
>
<Maximize2 size={14} className="group-hover:text-accent" />
</button>
</Tooltip>
</div>
{showFullLog && (
<FullLogView
event={parsedContent}
onClose={() => setShowFullLog(false)}
/>
)}
</>
);
};
export default LLMLogRenderer;

View File

@ -1,5 +1,12 @@
import React, { useState, memo } from "react";
import { User, Bot, Maximize2, Minimize2, DraftingCompass } from "lucide-react";
import {
User,
Bot,
Maximize2,
Minimize2,
DraftingCompass,
Brain,
} from "lucide-react";
import {
AgentMessageConfig,
FunctionCall,
@ -7,6 +14,7 @@ import {
ImageContent,
} from "../../../types/datamodel";
import { ClickableImage, TruncatableText } from "../../atoms";
import LLMLogRenderer from "./logrenderer";
const TEXT_THRESHOLD = 400;
const JSON_THRESHOLD = 800;
@ -143,9 +151,14 @@ export const RenderMessage: React.FC<MessageProps> = ({
if (!message) return null;
const isUser = messageUtils.isUser(message.source);
const content = message.content;
const isLLMEventMessage = message.source === "llm_call_event";
return (
<div className={`relative group ${!isLast ? "mb-2" : ""} ${className}`}>
<div
className={`relative group ${!isLast ? "mb-2" : ""} ${className} ${
isLLMEventMessage ? "border-accent" : ""
}`}
>
<div
className={`
flex items-start gap-2 p-2 rounded
@ -160,7 +173,13 @@ export const RenderMessage: React.FC<MessageProps> = ({
${isUser ? "text-accent" : "text-primary"}
`}
>
{isUser ? <User size={14} /> : <Bot size={14} />}
{isUser ? (
<User size={14} />
) : message.source == "llm_call_event" ? (
<Brain size={14} />
) : (
<Bot size={14} />
)}
</div>
<div className="flex-1 min-w-0">
@ -177,6 +196,8 @@ export const RenderMessage: React.FC<MessageProps> = ({
<RenderMultiModal content={content} />
) : messageUtils.isFunctionExecutionResult(content) ? (
<RenderToolResult content={content} />
) : message.source === "llm_call_event" ? (
<LLMLogRenderer content={String(content)} />
) : (
<TruncatableText
content={String(content)}

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
StopCircle,
MessageSquare,
@ -6,12 +6,10 @@ import {
CheckCircle,
AlertTriangle,
TriangleAlertIcon,
GroupIcon,
ChevronDown,
ChevronUp,
Bot,
PanelRightClose,
PanelLeftOpen,
PanelRightOpen,
} from "lucide-react";
import { Run, Message, TeamConfig, Component } from "../../../types/datamodel";
@ -19,11 +17,8 @@ import AgentFlow from "./agentflow/agentflow";
import { RenderMessage } from "./rendermessage";
import InputRequestView from "./inputrequest";
import { Tooltip } from "antd";
import {
getRelativeTimeString,
LoadingDots,
TruncatableText,
} from "../../atoms";
import { getRelativeTimeString, LoadingDots } from "../../atoms";
import { useSettingsStore } from "../../settings/store";
interface RunViewProps {
run: Run;
@ -33,6 +28,23 @@ interface RunViewProps {
isFirstRun?: boolean;
}
export const getAgentMessages = (messages: Message[]): Message[] => {
return messages.filter((msg) => msg.config.source !== "llm_call_event");
};
export const getLastMeaningfulMessage = (
messages: Message[]
): Message | undefined => {
return messages
.filter((msg) => msg.config.source !== "llm_call_event")
.slice(-1)[0];
};
// Type guard for message arrays
export const isAgentMessage = (message: Message): boolean => {
return message.config.source !== "llm_call_event";
};
const RunView: React.FC<RunViewProps> = ({
run,
onInputResponse,
@ -45,6 +57,18 @@ const RunView: React.FC<RunViewProps> = ({
const isActive = run.status === "active" || run.status === "awaiting_input";
const [isFlowVisible, setIsFlowVisible] = useState(true);
const showLLMEvents = useSettingsStore(
(state) => state.playground.showLLMEvents
);
console.log("showLLMEvents", showLLMEvents);
const visibleMessages = useMemo(() => {
if (showLLMEvents) {
return run.messages;
}
return run.messages.filter((msg) => msg.config.source !== "llm_call_event");
}, [run.messages, showLLMEvents]);
// Replace existing scroll effect with this simpler one
useEffect(() => {
setTimeout(() => {
@ -56,7 +80,7 @@ const RunView: React.FC<RunViewProps> = ({
}
}, 450);
}, [run.messages]); // Only depend on messages changing
// console.log("run", run);
const calculateThreadTokens = (messages: Message[]) => {
// console.log("messages", messages);
return messages.reduce((total, msg) => {
@ -123,7 +147,7 @@ const RunView: React.FC<RunViewProps> = ({
};
const lastResultMessage = run.team_result?.task_result.messages.slice(-1)[0];
const lastMessage = run.messages.slice(-1)[0];
const lastMessage = getLastMeaningfulMessage(visibleMessages);
return (
<div className="space-y-6 mr-2 ">
@ -202,19 +226,7 @@ const RunView: React.FC<RunViewProps> = ({
</div>
{lastMessage ? (
// <TruncatableText
// key={"_" + run.id}
// textThreshold={700}
// content={
// run.messages[run.messages.length - 1]?.config?.content +
// ""
// }
// className="break-all"
// />
<RenderMessage
message={run.messages[run.messages.length - 1]?.config}
isLast={true}
/>
<RenderMessage message={lastMessage.config} isLast={true} />
) : (
<>
{lastResultMessage && (
@ -228,7 +240,7 @@ const RunView: React.FC<RunViewProps> = ({
{/* Thread Section */}
<div className="">
{run.messages.length > 0 && (
{visibleMessages.length > 0 && (
<div className="mt-2 pl-4 border-secondary rounded-b border-l-2 border-secondary/30">
<div className="flex pt-2">
<div className="flex-1">
@ -262,8 +274,8 @@ const RunView: React.FC<RunViewProps> = ({
</div>
<div className="text-sm text-secondary">
{calculateThreadTokens(run.messages)} tokens |{" "}
{run.messages.length} messages
{calculateThreadTokens(visibleMessages)} tokens |{" "}
{visibleMessages.length} messages
</div>
</div>
@ -290,14 +302,14 @@ const RunView: React.FC<RunViewProps> = ({
{" "}
<span className=" inline-block h-6"></span>{" "}
</div>
{run.messages.map((msg, idx) => (
{visibleMessages.map((msg, idx) => (
<div
key={"message_id" + idx + run.id}
className=" mr-2"
>
<RenderMessage
message={msg.config}
isLast={idx === run.messages.length - 1}
isLast={idx === visibleMessages.length - 1}
/>
</div>
))}
@ -322,7 +334,7 @@ const RunView: React.FC<RunViewProps> = ({
{/* Agent Flow Visualization */}
{isFlowVisible && (
<div className="bg-tertiary flex-1 rounded mt-2 relative">
<div className="z-50 absolute left-2 top-2 p-2 hover:opacity-100 opacity-80">
<div className="z-10 absolute left-2 top-2 p-2 hover:opacity-100 opacity-80">
<Tooltip title="Hide message flow">
<button
onClick={() => setIsFlowVisible(false)}
@ -333,7 +345,13 @@ const RunView: React.FC<RunViewProps> = ({
</Tooltip>
</div>
{teamConfig && (
<AgentFlow teamConfig={teamConfig} run={run} />
<AgentFlow
teamConfig={teamConfig}
run={{
...run,
messages: getAgentMessages(visibleMessages),
}}
/>
)}
</div>
)}

View File

@ -0,0 +1,163 @@
import React, { useState, useEffect } from "react";
import { ChevronRight, RotateCcw } from "lucide-react";
import { Switch, Button, Tooltip } from "antd";
import { MessagesSquare } from "lucide-react";
import { useSettingsStore } from "./store";
import { SettingsSidebar } from "./sidebar";
import { SettingsSection } from "./types";
import { LucideIcon } from "lucide-react";
interface SettingToggleProps {
checked: boolean;
onChange: (checked: boolean) => void;
label: string;
description?: string;
}
interface SectionHeaderProps {
title: string;
icon: LucideIcon;
onReset: () => void;
}
const SettingToggle: React.FC<SettingToggleProps> = ({
checked,
onChange,
label,
description,
}) => (
<div className="flex justify-between items-start p-4 hover:bg-secondary/5 rounded-lg transition-colors">
<div className="flex flex-col gap-1">
<label className="font-medium">{label}</label>
{description && (
<span className="text-sm text-secondary">{description}</span>
)}
</div>
<Switch defaultValue={checked} onChange={onChange} />
</div>
);
const SectionHeader: React.FC<SectionHeaderProps> = ({
title,
icon: Icon,
onReset,
}) => (
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<Icon className="text-accent" size={20} />
<h2 className="text-lg font-semibold">{title}</h2>
</div>
<Tooltip title="Reset section settings">
<Button
icon={<RotateCcw className="w-4 h-4" />}
onClick={onReset}
type="text"
/>
</Tooltip>
</div>
);
export const SettingsManager: React.FC = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(() => {
if (typeof window !== "undefined") {
const stored = localStorage.getItem("settingsSidebar");
return stored !== null ? JSON.parse(stored) : true;
}
return true;
});
const {
playground,
updatePlaygroundSettings,
resetPlaygroundSettings,
resetAllSettings,
} = useSettingsStore();
const sections: SettingsSection[] = [
{
id: "playground",
title: "Playground",
icon: MessagesSquare,
content: () => (
<>
<SectionHeader
title="Playground"
icon={MessagesSquare}
onReset={resetPlaygroundSettings}
/>
<div className="space-y-2 rounded-xl border border-secondary">
<SettingToggle
checked={playground.showLLMEvents}
onChange={(checked) =>
updatePlaygroundSettings({ showLLMEvents: checked })
}
label={"Show LLM Events"}
description="Display detailed LLM call logs in the message thread"
/>
</div>
</>
),
},
];
const [currentSection, setCurrentSection] = useState<SettingsSection>(
sections[0]
);
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("settingsSidebar", JSON.stringify(isSidebarOpen));
}
}, [isSidebarOpen]);
return (
<div className="relative flex h-full w-full">
<div
className={`absolute left-0 top-0 h-full transition-all duration-200 ease-in-out ${
isSidebarOpen ? "w-64" : "w-12"
}`}
>
<SettingsSidebar
isOpen={isSidebarOpen}
sections={sections}
currentSection={currentSection}
onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
onSelectSection={setCurrentSection}
/>
</div>
<div
className={`flex-1 transition-all max-w-5xl -mr-6 duration-200 ${
isSidebarOpen ? "ml-64" : "ml-12"
}`}
>
<div className="p-4 pt-2">
<div className="flex items-center gap-2 mb-4 text-sm">
<span className="text-primary font-medium">Settings</span>
<ChevronRight className="w-4 h-4 text-secondary" />
<span className="text-secondary">{currentSection.title}</span>
</div>
<currentSection.content />
<div className="mt-12 pt-6 border-t border-secondary flex justify-between items-center">
<p className="text-xs text-secondary">
Settings are automatically saved and synced across browser
sessions
</p>
<Button
type="text"
danger
icon={<RotateCcw className="w-4 h-4 mr-1" />}
onClick={resetAllSettings}
>
Reset All Settings
</Button>
</div>
</div>
</div>
</div>
);
};
export default SettingsManager;

View File

@ -0,0 +1,87 @@
import React from "react";
import { Button, Tooltip } from "antd";
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
import { SettingsSection } from "./types";
interface SettingsSidebarProps {
isOpen: boolean;
sections: SettingsSection[];
currentSection: SettingsSection;
onToggle: () => void;
onSelectSection: (section: SettingsSection) => void;
}
export const SettingsSidebar: React.FC<SettingsSidebarProps> = ({
isOpen,
sections,
currentSection,
onToggle,
onSelectSection,
}) => {
// Render collapsed state
if (!isOpen) {
return (
<div className="h-full border-r border-secondary">
<div className="p-2 -ml-2">
<Tooltip title={`Settings (${sections.length})`}>
<button
onClick={onToggle}
className="p-2 rounded-md hover:bg-secondary hover:text-accent text-secondary transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-opacity-50"
>
<PanelLeftOpen strokeWidth={1.5} className="h-6 w-6" />
</button>
</Tooltip>
</div>
</div>
);
}
return (
<div className="h-full border-r border-secondary">
{/* Header */}
<div className="flex items-center justify-between pt-0 p-4 pl-2 pr-2 border-b border-secondary">
<div className="flex items-center gap-2">
<span className="text-primary font-medium">Settings</span>
<span className="px-2 py-0.5 text-xs bg-accent/10 text-accent rounded">
{sections.length}
</span>
</div>
<Tooltip title="Close Sidebar">
<button
onClick={onToggle}
className="p-2 rounded-md hover:bg-secondary hover:text-accent text-secondary transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-opacity-50"
>
<PanelLeftClose strokeWidth={1.5} className="h-6 w-6" />
</button>
</Tooltip>
</div>
<div className="overflow-y-auto h-[calc(100%-64px)]">
{sections.map((section) => (
<div key={section.id} className="relative">
<div
className={`absolute top-1 left-0.5 z-50 h-[calc(100%-8px)] w-1 bg-opacity-80 rounded
${
currentSection.id === section.id ? "bg-accent" : "bg-tertiary"
}`}
/>
<div
className={`group ml-1 flex flex-col p-3 rounded-l cursor-pointer hover:bg-secondary
${
currentSection.id === section.id
? "border-accent bg-secondary"
: "border-transparent"
}`}
onClick={() => onSelectSection(section)}
>
<div className="flex items-center gap-2">
<section.icon className="w-4 h-4" />
<span className="text-sm">{section.title}</span>
</div>
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -0,0 +1,121 @@
//settings/store.tsx
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface PlaygroundSettings {
showLLMEvents: boolean;
// Future playground settings
expandedMessagesByDefault?: boolean;
showAgentFlowByDefault?: boolean;
}
interface TeamBuilderSettings {
// Future teambuilder settings
showAdvancedOptions?: boolean;
defaultAgentLayout?: "grid" | "list";
}
interface GallerySettings {
// Future gallery settings
viewMode?: "grid" | "list";
sortBy?: "date" | "popularity";
}
interface SettingsState {
playground: PlaygroundSettings;
teamBuilder: TeamBuilderSettings;
gallery: GallerySettings;
// Actions to update settings
updatePlaygroundSettings: (settings: Partial<PlaygroundSettings>) => void;
updateTeamBuilderSettings: (settings: Partial<TeamBuilderSettings>) => void;
updateGallerySettings: (settings: Partial<GallerySettings>) => void;
// Reset functions
resetPlaygroundSettings: () => void;
resetTeamBuilderSettings: () => void;
resetGallerySettings: () => void;
resetAllSettings: () => void;
}
const DEFAULT_PLAYGROUND_SETTINGS: PlaygroundSettings = {
showLLMEvents: true, // Default to hiding LLM events
};
const DEFAULT_TEAMBUILDER_SETTINGS: TeamBuilderSettings = {
showAdvancedOptions: false,
defaultAgentLayout: "grid",
};
const DEFAULT_GALLERY_SETTINGS: GallerySettings = {
viewMode: "grid",
sortBy: "date",
};
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
// Initial state
playground: DEFAULT_PLAYGROUND_SETTINGS,
teamBuilder: DEFAULT_TEAMBUILDER_SETTINGS,
gallery: DEFAULT_GALLERY_SETTINGS,
// Update functions
updatePlaygroundSettings: (settings) =>
set((state) => ({
playground: { ...state.playground, ...settings },
})),
updateTeamBuilderSettings: (settings) =>
set((state) => ({
teamBuilder: { ...state.teamBuilder, ...settings },
})),
updateGallerySettings: (settings) =>
set((state) => ({
gallery: { ...state.gallery, ...settings },
})),
// Reset functions
resetPlaygroundSettings: () =>
set((state) => ({
playground: DEFAULT_PLAYGROUND_SETTINGS,
})),
resetTeamBuilderSettings: () =>
set((state) => ({
teamBuilder: DEFAULT_TEAMBUILDER_SETTINGS,
})),
resetGallerySettings: () =>
set((state) => ({
gallery: DEFAULT_GALLERY_SETTINGS,
})),
resetAllSettings: () =>
set({
playground: DEFAULT_PLAYGROUND_SETTINGS,
teamBuilder: DEFAULT_TEAMBUILDER_SETTINGS,
gallery: DEFAULT_GALLERY_SETTINGS,
}),
}),
{
name: "ags-app-settings",
partialize: (state) => ({
playground: state.playground,
teamBuilder: state.teamBuilder,
gallery: state.gallery,
}),
}
)
);
// Example usage:
/*
import { useSettingsStore } from './stores/settings';
// In a component:
const { showLLMEvents } = useSettingsStore((state) => state.playground);
const updatePlaygroundSettings = useSettingsStore((state) => state.updatePlaygroundSettings);
// Toggle LLM events
updatePlaygroundSettings({ showLLMEvents: !showLLMEvents });
*/

View File

@ -0,0 +1,8 @@
import { LucideIcon } from "lucide-react";
export interface SettingsSection {
id: string;
title: string;
icon: LucideIcon;
content: () => JSX.Element;
}

View File

@ -41,7 +41,7 @@ import { MonacoEditor } from "../../monaco";
import { NodeEditor } from "./node-editor/node-editor";
import debounce from "lodash.debounce";
import { appContext } from "../../../../hooks/provider";
import { sessionAPI } from "../../session/api";
import { sessionAPI } from "../../playground/api";
import TestDrawer from "./testdrawer";
const { Sider, Content } = Layout;
@ -179,7 +179,6 @@ export const TeamBuilder: React.FC<TeamBuilderProps> = ({
}
if (onChange) {
console.log("Saving team configuration", component);
const teamData: Partial<Team> = team
? {
...team,
@ -288,7 +287,7 @@ export const TeamBuilder: React.FC<TeamBuilderProps> = ({
};
const handleTestDrawerClose = () => {
console.log("TestDrawer closed");
// console.log("TestDrawer closed");
setTestDrawerVisible(false);
};

View File

@ -2,9 +2,9 @@ import React, { useContext, useEffect, useState } from "react";
import { Drawer, Button, message, Checkbox } from "antd";
import { Team, Session } from "../../../types/datamodel";
import ChatView from "../../session/chat/chat";
import ChatView from "../../playground/chat/chat";
import { appContext } from "../../../../hooks/provider";
import { sessionAPI } from "../../session/api";
import { sessionAPI } from "../../playground/api";
interface TestDrawerProps {
isVisble: boolean;

View File

@ -3,11 +3,9 @@ import { Button, Tooltip } from "antd";
import {
Bot,
Plus,
Edit,
Trash2,
PanelLeftClose,
PanelLeftOpen,
Calendar,
Copy,
GalleryHorizontalEnd,
InfoIcon,
@ -15,7 +13,6 @@ import {
} from "lucide-react";
import type { Team } from "../../types/datamodel";
import { getRelativeTimeString } from "../atoms";
import { defaultTeam } from "./types";
import { useGalleryStore } from "../gallery/store";
interface TeamSidebarProps {
@ -43,8 +40,12 @@ export const TeamSidebar: React.FC<TeamSidebarProps> = ({
}) => {
const defaultGallery = useGalleryStore((state) => state.getDefaultGallery());
const createTeam = () => {
const newTeam = Object.assign({}, defaultTeam);
newTeam.component.label = "new_team_" + new Date().getTime();
const newTeam = Object.assign(
{},
{ component: defaultGallery?.items.teams[0] }
);
newTeam.component.label =
"default_team" + new Date().getTime().toString().slice(0, 2);
onCreateTeam(newTeam);
};

View File

@ -15,85 +15,3 @@ export interface TeamListProps {
onDelete: (teamId: number) => void;
isLoading?: boolean;
}
export const defaultTeamConfig: Component<TeamConfig> = {
provider: "autogen_agentchat.teams.RoundRobinGroupChat",
component_type: "team",
version: 1,
component_version: 1,
description:
"A team of agents that chat with users in a round-robin fashion.",
label: "General Team",
config: {
participants: [
{
provider: "autogen_agentchat.agents.AssistantAgent",
component_type: "agent",
version: 1,
component_version: 1,
config: {
name: "weather_agent",
model_client: {
provider: "autogen_ext.models.openai.OpenAIChatCompletionClient",
component_type: "model",
version: 1,
component_version: 1,
config: { model: "gpt-4o-mini" },
},
tools: [
{
provider: "autogen_core.tools.FunctionTool",
component_type: "tool",
version: 1,
component_version: 1,
config: {
source_code:
'async def get_weather(city: str) -> str:\n return f"The weather in {city} is 73 degrees and Sunny."\n',
name: "get_weather",
description: "",
global_imports: [],
has_cancellation_support: false,
},
},
],
handoffs: [],
description:
"An agent that provides assistance with ability to use tools.",
system_message:
"You are a helpful AI assistant. Solve tasks using your tools. Reply with TERMINATE when the task has been completed.",
reflect_on_tool_use: false,
tool_call_summary_format: "{result}",
},
},
],
termination_condition: {
provider: "autogen_agentchat.base.OrTerminationCondition",
component_type: "termination",
version: 1,
component_version: 1,
config: {
conditions: [
{
provider: "autogen_agentchat.conditions.MaxMessageTermination",
component_type: "termination",
version: 1,
component_version: 1,
config: { max_messages: 10 },
},
{
provider: "autogen_agentchat.conditions.TextMentionTermination",
component_type: "termination",
version: 1,
component_version: 1,
config: { text: "TERMINATE" },
},
],
},
},
max_turns: 1,
},
};
export const defaultTeam: Team = {
component: defaultTeamConfig,
};

View File

@ -1,8 +1,8 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import ChatView from "../components/views/session/chat/chat";
import { SessionManager } from "../components/views/session/manager";
import ChatView from "../components/views/playground/chat/chat";
import { SessionManager } from "../components/views/playground/manager";
// markup
const IndexPage = ({ data }: any) => {

View File

@ -1,21 +1,14 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import { TriangleAlertIcon } from "lucide-react";
import { SettingsManager } from "../components/views/settings/manager";
// markup
const SettingsPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/build"}>
<Layout meta={data.site.siteMetadata} title="Home" link={"/settings"}>
<main style={{ height: "100%" }} className=" h-full ">
<div className="mb-2"> Settings</div>
<div className="p-2 mt-4 bg-tertiary rounded ">
<TriangleAlertIcon
className="w-5 h-5 text-primary inline-block -mt-1"
strokeWidth={1.5}
/>{" "}
Work in progress ..
</div>
<SettingsManager />
</main>
</Layout>
);