diff --git a/app/chat/components/chat-sidebar.tsx b/app/chat/components/chat-sidebar.tsx index 5f629186..47b0141a 100644 --- a/app/chat/components/chat-sidebar.tsx +++ b/app/chat/components/chat-sidebar.tsx @@ -487,63 +487,63 @@ export function ChatSidebar() { onValueChange={(v) => { setActiveTab(v as TabKey); }} className="flex flex-col flex-1 min-h-0 overflow-hidden" > -
- +
+ - Threads + Threads - Agents + Agents - Tools + Tools - Flows + Flows - Traces + Traces - Vectors + Vectors - Memory + Memory - Config + Config
diff --git a/app/chat/components/main-sidebar.tsx b/app/chat/components/main-sidebar.tsx index 7d290bde..8c9c4833 100644 --- a/app/chat/components/main-sidebar.tsx +++ b/app/chat/components/main-sidebar.tsx @@ -2,11 +2,11 @@ import { useCallback, useMemo } from 'react' import Link from 'next/link' -import { usePathname } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' import { useChatContext } from '@/app/chat/providers/chat-context-hooks' import { useAuthQuery } from '@/lib/hooks/use-auth-query' -import { useThreads } from '@/lib/hooks/use-mastra-query' +import { useAgent, useThreads } from '@/lib/hooks/use-mastra-query' import { LogoutButton } from '../_components/logout-button' import { Sidebar, @@ -42,7 +42,6 @@ import { WrenchIcon, } from 'lucide-react' import { cn } from '@/lib/utils' -import router from 'next/router' interface SidebarThread { id?: string @@ -87,11 +86,40 @@ const formatThreadMeta = (thread: SidebarThread) => { */ export function MainSidebar() { const pathname = usePathname() + const router = useRouter() const { data: session } = useAuthQuery() const { selectedAgent, setThreadId, threadId } = useChatContext() + const selectedAgentQuery = useAgent(selectedAgent) const threadsResult = useThreads({ agentId: selectedAgent }) const threads = (threadsResult.data ?? []) as unknown as SidebarThread[] + const selectedAgentSummary = useMemo(() => { + const agent = selectedAgentQuery.data as Record | undefined + if (!agent) { + return `Loading details for ${selectedAgent}...` + } + + const agentName = + typeof agent.name === 'string' && agent.name.trim().length > 0 + ? agent.name + : selectedAgent + const modelId = + typeof agent.modelId === 'string' && agent.modelId.trim().length > 0 + ? agent.modelId + : typeof agent.model === 'string' && agent.model.trim().length > 0 + ? agent.model + : '' + const provider = + typeof agent.provider === 'string' && agent.provider.trim().length > 0 + ? agent.provider + : typeof agent.modelProvider === 'string' && agent.modelProvider.trim().length > 0 + ? agent.modelProvider + : '' + + return [agentName, provider ? `Provider: ${provider}` : '', modelId ? `Model: ${modelId}` : ''] + .filter((part) => part.length > 0) + .join(' • ') + }, [selectedAgent, selectedAgentQuery.data]) const pageItems = useMemo( () => [ @@ -210,7 +238,7 @@ export function MainSidebar() { setThreadId(nextThreadId) router.push(`/chat/agents/${selectedAgent}`) }, - [selectedAgent, setThreadId] + [router, selectedAgent, setThreadId] ) return ( @@ -244,7 +272,15 @@ export function MainSidebar() { - Return to the main chat dashboard. +
+
Return to the main chat dashboard.
+
+ {selectedAgentSummary} +
+
+ {threads.length} thread{threads.length === 1 ? '' : 's'} loaded +
+
diff --git a/app/components/landing-hero.tsx b/app/components/landing-hero.tsx index 412f2b95..19c05a86 100644 --- a/app/components/landing-hero.tsx +++ b/app/components/landing-hero.tsx @@ -74,7 +74,7 @@ export function LandingHero() { return (
{/* Network Architectural Background (Strict Grid) */} @@ -86,11 +86,11 @@ export function LandingHero() {
{/* Left: Logical Architecture */}
-
+
@@ -100,7 +100,7 @@ export function LandingHero() {
-

+

AGENT
@@ -108,25 +108,25 @@ export function LandingHero() {

-

+

The framework for high-precision multi-agent systems. Engineered for architectural stability, observability, and production deployment at scale.

-
- -
diff --git a/app/components/navbar.tsx b/app/components/navbar.tsx index 3e7e0909..12c63efa 100644 --- a/app/components/navbar.tsx +++ b/app/components/navbar.tsx @@ -175,20 +175,20 @@ export function Navbar() { -
+
Products @@ -265,8 +265,8 @@ export function Navbar() {
Products diff --git a/app/components/tools-list.tsx b/app/components/tools-list.tsx index 7bc6c3a5..169d9fd4 100644 --- a/app/components/tools-list.tsx +++ b/app/components/tools-list.tsx @@ -165,9 +165,9 @@ export function ToolsList() { const popularTools = tools.slice(0, 4) return ( -
+
{/* Header */} -
+
-

+

Popular Tools

-
+
{popularTools.map((tool, index) => ( -
-
+
+
Featured
-

+

{tool.name}

-

+

{tool.description}

@@ -231,13 +231,13 @@ export function ToolsList() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, delay: 0.2 }} - className="mx-auto mb-12 max-w-4xl space-y-6" + className="mx-auto mb-10 max-w-4xl space-y-5 lg:mb-12 lg:space-y-6" >
{ setSearch(e.target.value) @@ -245,7 +245,7 @@ export function ToolsList() { />
-
+
{categories.map((category) => ( {/* Tools grid */} -
+
{displayedTools.map((tool, index) => ( -
-
+
+
@@ -307,10 +307,10 @@ export function ToolsList() {
-

+

{tool.name}

-

+

{tool.description}

diff --git a/app/login/page.tsx b/app/login/page.tsx index 31c48ed7..2ceab821 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -51,7 +51,6 @@ function LoginPageContent() { const router = useRouter() const searchParams = useSearchParams() const authQuery = useAuthQuery() - const isHydrated = true const [isLoading, setIsLoading] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [identifier, setIdentifier] = useState('') @@ -68,10 +67,6 @@ function LoginPageContent() { const canSubmit = normalizedIdentifier.length > 0 && password.length > 0 && !isLoading useEffect(() => { - if (!isHydrated) { - return - } - if (authQuery.isPending) { return } @@ -79,13 +74,9 @@ function LoginPageContent() { if (authQuery.data) { router.replace(nextPath) } - }, [authQuery.data, authQuery.isPending, isHydrated, nextPath, router]) + }, [authQuery.data, authQuery.isPending, nextPath, router]) useEffect(() => { - if (!isHydrated) { - return - } - if (authQuery.isPending || authQuery.data || !hasGoogleOneTapClient) { return } @@ -93,7 +84,7 @@ function LoginPageContent() { void startGoogleOneTap({ callbackURL: nextPath, }) - }, [authQuery.data, authQuery.isPending, isHydrated, nextPath]) + }, [authQuery.data, authQuery.isPending, nextPath]) /** Starts the Google OAuth flow through Better Auth. */ const handleGoogleSignIn = async () => { @@ -151,7 +142,7 @@ function LoginPageContent() { router.replace(nextPath) } - if (!isHydrated || authQuery.isPending || authQuery.data) { + if (authQuery.isPending || authQuery.data) { return (
diff --git a/app/login/signup/page.tsx b/app/login/signup/page.tsx index 1acb3ac6..d6d8c166 100644 --- a/app/login/signup/page.tsx +++ b/app/login/signup/page.tsx @@ -48,7 +48,6 @@ function SignupPageContent() { const router = useRouter() const searchParams = useSearchParams() const authQuery = useAuthQuery() - const isHydrated = true const [isLoading, setIsLoading] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [name, setName] = useState('') @@ -77,10 +76,6 @@ function SignupPageContent() { !isLoading useEffect(() => { - if (!isHydrated) { - return - } - if (authQuery.isPending) { return } @@ -88,13 +83,9 @@ function SignupPageContent() { if (authQuery.data) { router.replace(nextPath) } - }, [authQuery.data, authQuery.isPending, isHydrated, nextPath, router]) + }, [authQuery.data, authQuery.isPending, nextPath, router]) useEffect(() => { - if (!isHydrated) { - return - } - if (authQuery.isPending || authQuery.data || !hasGoogleOneTapClient) { return } @@ -102,7 +93,7 @@ function SignupPageContent() { void startGoogleOneTap({ callbackURL: nextPath, }) - }, [authQuery.data, authQuery.isPending, isHydrated, nextPath]) + }, [authQuery.data, authQuery.isPending, nextPath]) /** Starts the Google OAuth flow through Better Auth. */ const handleGoogleSignIn = async () => { @@ -168,7 +159,7 @@ function SignupPageContent() { router.replace(nextPath) } - if (!isHydrated || authQuery.isPending || authQuery.data) { + if (authQuery.isPending || authQuery.data) { return (
diff --git a/src/components/ai-elements/tools/__tests__/weather-tool.test.tsx b/src/components/ai-elements/tools/__tests__/weather-tool.test.tsx index 83ebb6a2..6e2cd8c8 100644 --- a/src/components/ai-elements/tools/__tests__/weather-tool.test.tsx +++ b/src/components/ai-elements/tools/__tests__/weather-tool.test.tsx @@ -12,12 +12,28 @@ describe('ForecastView', () => { input: { location: 'London', days: 3 }, output: { data: { + current: { + temp_c: 20, + condition: { + text: 'Partly cloudy', + icon: '//cdn.weatherapi.com/icon.png', + }, + humidity: 65, + wind_kph: 12, + }, + location: { + name: 'London', + country: 'UK', + localtime: '2023-01-02 12:00', + }, forecast: { forecastday: [ { date: '2023-01-02', day: { avgtemp_c: 22, + mintemp_c: 16, + maxtemp_c: 24, condition: { text: 'Partly cloudy', icon: '//cdn.weatherapi.com/icon.png', @@ -28,6 +44,8 @@ describe('ForecastView', () => { date: '2023-01-03', day: { avgtemp_c: 18, + mintemp_c: 12, + maxtemp_c: 20, condition: { text: 'Rain', icon: '//cdn.weatherapi.com/rain.png', @@ -36,7 +54,6 @@ describe('ForecastView', () => { }, ], }, - location: { name: 'London' }, }, }, } diff --git a/src/components/ai-elements/tools/polygon-tools.tsx b/src/components/ai-elements/tools/polygon-tools.tsx index 6edda742..386d3d50 100644 --- a/src/components/ai-elements/tools/polygon-tools.tsx +++ b/src/components/ai-elements/tools/polygon-tools.tsx @@ -1,9 +1,11 @@ 'use client' +import * as React from 'react' +import type { InferUITool } from '@mastra/core/tools' import type { PolygonCryptoAggregatesUITool, PolygonCryptoQuotesUITool, - + PolygonCryptoSnapshotsUITool, PolygonStockAggregatesUITool, PolygonStockFundamentalsUITool, PolygonStockQuotesUITool, @@ -30,7 +32,7 @@ import { CodeBlock, CodeBlockCopyButton } from '../code-block' /* Utility helpers */ function formatNumber(n?: number | string) { - if (n == null) {return 'N/A'} + if (n === null || n === undefined) {return 'N/A'} const num = typeof n === 'string' ? Number(n) : n if (Number.isNaN(num)) {return String(n)} return num >= 1000 ? num.toLocaleString() : num.toString() @@ -384,15 +386,15 @@ export function PolygonCryptoSnapshotsCard({ errorText?: string }) { if (errorText) {return } - if (!output) {return } />} + if (!output) {return } />} if ('error' in output) {return } const data = (output as any).data ?? [] return ( - Crypto Snapshots: {(input as any).symbol} -
+ Crypto Snapshots +
{Array.isArray(data) ? ( diff --git a/src/components/ai-elements/tools/types.ts b/src/components/ai-elements/tools/types.ts index 28d19360..94e9f321 100644 --- a/src/components/ai-elements/tools/types.ts +++ b/src/components/ai-elements/tools/types.ts @@ -1,5 +1,4 @@ import type { - addIssueComment, alphaVantageCryptoTool, alphaVantageStockTool, alphaVantageTool, @@ -16,9 +15,6 @@ import type { clickAndExtractTool, colorChangeTool, // copywriterTool, - createIssue, - createPullRequest, - createRelease, csvToJsonTool, documentRerankerTool, editorTool, @@ -34,11 +30,6 @@ import type { finnhubFinancialsTool, finnhubQuotesTool, finnhubTechnicalTool, - getFileContent, - getIssue, - getPullRequest, - getRepoFileTree, - getRepositoryInfo, getTodayEvents, getUpcomingEvents, googleAiOverviewTool, @@ -53,15 +44,10 @@ import type { homeDepotSearchTool, jsonToCsvTool, jwtAuthTool, - listCommits, listEvents, - listIssues, - listPullRequests, - listRepositories, mastraChunker, matrixCalculatorTool, mdocumentChunker, - mergePullRequest, monitorPageTool, pdfGeneratorTool, polygonCryptoAggregatesTool, @@ -69,16 +55,76 @@ import type { polygonStockAggregatesTool, polygonStockFundamentalsTool, polygonStockQuotesTool, + polygonCryptoSnapshotsTool, + coinbaseExchangeMarketDataTool, + yahooFinanceStockQuotesTool, + scheduledFetchTool, + stooqStockQuotesTool, + urlValidationTool, + urlManipulationTool, + textAnalysisTool, + textProcessingTool, + dateTimeTool, + timeZoneTool, + chartJsTool, + cytoscapeTool, + downsampleTool, + discordWebhookTool, + leafletTool, + //ocrTool, + //imageProcessorTool, + //imageToMarkdownTool, + randomGeneratorTool, + spatialIndexTool, + binanceSpotMarketDataTool, + walmartSearchTool, + ebaySearchTool, + gitStatusTool, + gitDiffTool, + gitCommitTool, + gitLogTool, + gitBranchTool, + gitStashTool, + gitConfigTool, + ichimokuCloudTool, + fibonacciTool, + pivotPointsTool, + trendAnalysisTool, + momentumAnalysisTool, + volatilityAnalysisTool, + volumeAnalysisTool, + statisticalAnalysisTool, + heikinAshiTool, + marketSummaryTool, + candlestickPatternTool, + technicalAnalysisTool, readPDF, screenshotTool, - searchCode, unitConverterTool, weatherTool, writeNoteTool, yelpSearchTool, } from '@/src/mastra/tools' +import type { + addIssueComment, + createIssue, + createPullRequest, + createRelease, + getFileContent, + getIssue, + getPullRequest, + getRepoFileTree, + getRepositoryInfo, + listCommits, + listIssues, + listPullRequests, + listRepositories, + mergePullRequest, + searchCode, +} from '@/src/mastra/tools/github' import type { InferUITool } from '@mastra/core/tools' + //export type ActiveDistTagUITool = InferUITool export type AddIssueCommentUITool = InferUITool export type AlphaVantageCryptoUITool = InferUITool< @@ -167,6 +213,19 @@ export type PolygonStockFundamentalsUITool = InferUITool< export type PolygonStockQuotesUITool = InferUITool< typeof polygonStockQuotesTool > +export type PolygonCryptoSnapshotsUITool = InferUITool< + typeof polygonCryptoSnapshotsTool +> +export type CoinbaseExchangeMarketDataUITool = InferUITool< + typeof coinbaseExchangeMarketDataTool +> +export type YahooFinanceStockQuotesUITool = InferUITool< + typeof yahooFinanceStockQuotesTool +> +export type ScheduledFetchUITool = InferUITool< + typeof scheduledFetchTool +> + export type ReadPDFUITool = InferUITool export type CalculatorUITool = InferUITool export type UnitConverterUITool = InferUITool @@ -176,3 +235,42 @@ export type SearchCodeUITool = InferUITool export type WeatherUITool = InferUITool export type WriteNoteUITool = InferUITool export type YelpSearchUITool = InferUITool +export type BinanceSpotMarketDataUITool = InferUITool +export type ChartJsUITool = InferUITool +export type CytoscapeUITool = InferUITool +export type DownsampleUITool = InferUITool +export type DiscordWebhookUITool = InferUITool +export type DateTimeUITool = InferUITool +export type TimeZoneUITool = InferUITool +//export type OcrUITool = InferUITool +//export type ImageProcessorUITool = InferUITool +//export type ImageToMarkdownUITool = InferUITool +export type GitStatusUITool = InferUITool +export type GitDiffUITool = InferUITool +export type GitCommitUITool = InferUITool +export type GitLogUITool = InferUITool +export type GitBranchUITool = InferUITool +export type GitStashUITool = InferUITool +export type GitConfigUITool = InferUITool +export type RandomGeneratorUITool = InferUITool +export type LeafletUITool = InferUITool +export type WalmartSearchUITool = InferUITool +export type EbaySearchUITool = InferUITool +export type SpatialIndexUITool = InferUITool +export type StooqStockQuotesUITool = InferUITool +export type IchimokuCloudUITool = InferUITool +export type FibonacciUITool = InferUITool +export type PivotPointsUITool = InferUITool +export type TrendAnalysisUITool = InferUITool +export type MomentumAnalysisUITool = InferUITool +export type VolatilityAnalysisUITool = InferUITool +export type VolumeAnalysisUITool = InferUITool +export type StatisticalAnalysisUITool = InferUITool +export type HeikinAshiUITool = InferUITool +export type MarketSummaryUITool = InferUITool +export type CandlestickPatternUITool = InferUITool +export type TechnicalAnalysisUITool = InferUITool +export type TextAnalysisUITool = InferUITool +export type TextProcessingUITool = InferUITool +export type UrlValidationUITool = InferUITool +export type UrlManipulationUITool = InferUITool diff --git a/src/mastra/config/libsql.ts b/src/mastra/config/libsql.ts index 49b3e366..8211828d 100644 --- a/src/mastra/config/libsql.ts +++ b/src/mastra/config/libsql.ts @@ -10,14 +10,19 @@ import { createGraphRAGTool, createVectorQueryTool } from '@mastra/rag' export const libsqlstorage = new LibSQLStore({ id: 'libsql-storage', - url: process.env.TURSO_DATABASE_URL ?? 'file:./database.db', - authToken: process.env.TURSO_AUTH_TOKEN, + url: 'file:./database.db', + maxRetries: 5, // Optional retry configuration for transient errors + initialBackoffMs: 100, // Initial backoff for retries + //disableInit: process.env.DB_DISABLE_INIT === 'true', // Disable auto-init if specified + authToken: process.env.TURSO_AUTH_TOKEN, // Optional: for Turso cloud databases + // Additional options can be added here as needed }) + // Create a new vector store instance export const libsqlvector = new LibSQLVector({ id: 'libsql-vector', - url: process.env.TURSO_DATABASE_URL ?? 'file:./vectors.db', + url: 'file:./database.db', // Optional: for Turso cloud databases authToken: process.env.TURSO_AUTH_TOKEN, syncInterval: 10000, // Sync every 10 seconds (optional) @@ -27,11 +32,11 @@ export const libsqlvector = new LibSQLVector({ }) // Create an index -await libsqlvector.createIndex({ - indexName: 'memory_messages_3072', - dimension: 3072, - metric: 'cosine', -}) +//await libsqlvector.createIndex({ +// indexName: 'memory_messages_3072', +// dimension: 3072, +// metric: 'cosine', +//}) export const LibsqlMemory = new Memory({ storage: libsqlstorage, @@ -121,8 +126,8 @@ export const LibsqlMemory = new Memory({ }) log.info('LibSQLStore and Memory initialized with LibSQLVector support', { - storage: process.env.TURSO_DATABASE_URL ?? 'file:./database.db', - vector: process.env.TURSO_DATABASE_URL ?? 'file:./vectors.db', + storage: 'file:./database.db', + vector: 'file:./database.db', // schema: process.env.DB_SCHEMA ?? 'mastra', // maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS ?? '20'), memoryOptions: { diff --git a/tests/test-results/test-results.json b/tests/test-results/test-results.json index bb2ad3f4..c7715727 100644 --- a/tests/test-results/test-results.json +++ b/tests/test-results/test-results.json @@ -1 +1 @@ -{"numTotalTestSuites":2,"numPassedTestSuites":2,"numFailedTestSuites":0,"numPendingTestSuites":0,"numTotalTests":6,"numPassedTests":6,"numFailedTests":0,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1776210199450,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers rewards a synthesized supervisor response that uses delegation without exposing routing chatter","status":"passed","title":"rewards a synthesized supervisor response that uses delegation without exposing routing chatter","duration":9.037199999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers penalizes raw delegation chatter in the final supervisor response","status":"passed","title":"penalizes raw delegation chatter in the final supervisor response","duration":1.4966999999999189,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers rewards evidence grounded supervisor answers and penalizes unsupported summaries","status":"passed","title":"rewards evidence grounded supervisor answers and penalizes unsupported summaries","duration":2.5877000000000407,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers scores request coverage higher when the supervisor answers the key parts of the prompt","status":"passed","title":"scores request coverage higher when the supervisor answers the key parts of the prompt","duration":2.0133999999998196,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers rewards actionable supervisor recommendations and penalizes vague conclusions","status":"passed","title":"rewards actionable supervisor recommendations and penalizes vague conclusions","duration":1.822300000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Supervisor scorers"],"fullName":"Supervisor scorers rewards uncertainty handling when the supervisor states caveats instead of overcommitting","status":"passed","title":"rewards uncertainty handling when the supervisor states caveats instead of overcommitting","duration":2.0205000000000837,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1776210200826,"endTime":1776210200845.0205,"status":"passed","message":"","name":"C:/Users/ssdsk/AgentStack/src/mastra/scorers/supervisor-scorers.test.ts"}]} \ No newline at end of file +{"numTotalTestSuites":2,"numPassedTestSuites":2,"numFailedTestSuites":0,"numPendingTestSuites":0,"numTotalTests":1,"numPassedTests":1,"numFailedTests":0,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1776262021876,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["ForecastView"],"fullName":"ForecastView renders forecast rows","status":"passed","title":"renders forecast rows","duration":77.82680000000028,"failureMessages":[],"location":{"line":9,"column":5},"meta":{},"tags":[]}],"startTime":1776262025182,"endTime":1776262025259.827,"status":"passed","message":"","name":"C:/Users/ssdsk/AgentStack/src/components/ai-elements/tools/__tests__/weather-tool.test.tsx"}]} \ No newline at end of file