Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ coverage
internal

.isaac/

# local scratch (not committed)
scratch/
7 changes: 7 additions & 0 deletions apps/dev-playground/client/src/lib/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export const NAV_GROUPS: ReadonlyArray<NavGroup> = [
"Chat agent over Databricks Model Serving with tools auto-discovered from AppKit plugins.",
icon: BotIcon,
},
{
to: "/agent-history",
label: "Agent History",
description:
"Persistent chat history with <ThreadList> + <AgentThread> — resume, rename, delete across restarts.",
icon: LayersIcon,
},
{
to: "/genie",
label: "Genie",
Expand Down
21 changes: 21 additions & 0 deletions apps/dev-playground/client/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Route as ChartInferenceRouteRouteImport } from './routes/chart-inferenc
import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytics.route'
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
import { Route as AiSearchRouteRouteImport } from './routes/ai-search.route'
import { Route as AgentHistoryRouteRouteImport } from './routes/agent-history.route'
import { Route as AgentRouteRouteImport } from './routes/agent.route'
import { Route as IndexRouteImport } from './routes/index'

Expand Down Expand Up @@ -126,6 +127,11 @@ const AiSearchRouteRoute = AiSearchRouteRouteImport.update({
path: '/ai-search',
getParentRoute: () => rootRouteImport,
} as any)
const AgentHistoryRouteRoute = AgentHistoryRouteRouteImport.update({
id: '/agent-history',
path: '/agent-history',
getParentRoute: () => rootRouteImport,
} as any)
const AgentRouteRoute = AgentRouteRouteImport.update({
id: '/agent',
path: '/agent',
Expand All @@ -140,6 +146,7 @@ const IndexRoute = IndexRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/agent-history': typeof AgentHistoryRouteRoute
'/ai-search': typeof AiSearchRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
Expand All @@ -163,6 +170,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/agent-history': typeof AgentHistoryRouteRoute
'/ai-search': typeof AiSearchRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
Expand All @@ -187,6 +195,7 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/agent-history': typeof AgentHistoryRouteRoute
'/ai-search': typeof AiSearchRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
Expand All @@ -212,6 +221,7 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/agent'
| '/agent-history'
| '/ai-search'
| '/analytics'
| '/arrow-analytics'
Expand All @@ -235,6 +245,7 @@ export interface FileRouteTypes {
to:
| '/'
| '/agent'
| '/agent-history'
| '/ai-search'
| '/analytics'
| '/arrow-analytics'
Expand All @@ -258,6 +269,7 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/agent'
| '/agent-history'
| '/ai-search'
| '/analytics'
| '/arrow-analytics'
Expand All @@ -282,6 +294,7 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AgentRouteRoute: typeof AgentRouteRoute
AgentHistoryRouteRoute: typeof AgentHistoryRouteRoute
AiSearchRouteRoute: typeof AiSearchRouteRoute
AnalyticsRouteRoute: typeof AnalyticsRouteRoute
ArrowAnalyticsRouteRoute: typeof ArrowAnalyticsRouteRoute
Expand Down Expand Up @@ -438,6 +451,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AiSearchRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/agent-history': {
id: '/agent-history'
path: '/agent-history'
fullPath: '/agent-history'
preLoaderRoute: typeof AgentHistoryRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/agent': {
id: '/agent'
path: '/agent'
Expand All @@ -458,6 +478,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AgentRouteRoute: AgentRouteRoute,
AgentHistoryRouteRoute: AgentHistoryRouteRoute,
AiSearchRouteRoute: AiSearchRouteRoute,
AnalyticsRouteRoute: AnalyticsRouteRoute,
ArrowAnalyticsRouteRoute: ArrowAnalyticsRouteRoute,
Expand Down
60 changes: 60 additions & 0 deletions apps/dev-playground/client/src/routes/agent-history.route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { AgentThread, ThreadList } from "@databricks/appkit-ui/react/beta";
import { createFileRoute } from "@tanstack/react-router";
import { useCallback, useState } from "react";

export const Route = createFileRoute("/agent-history")({
component: AgentHistoryRoute,
});

/**
* Persistent chat history with the beta appkit-ui components. `<ThreadList>`
* (left) owns the list; `<AgentThread>` (right) owns the active conversation.
* The page holds the active thread id and wires the two together — clicking a
* thread opens it, a new/finished turn refreshes the list. Threads persist when
* the agents plugin uses LakebaseThreadStore (see server/index.ts).
*/
function AgentHistoryRoute() {
// `undefined` = a fresh conversation; a string = an opened thread. Switching
// is driven purely by this prop — no remount — so <AgentThread>'s per-thread
// transcript cache survives and re-opening a thread doesn't refetch.
const [activeThreadId, setActiveThreadId] = useState<string | undefined>();
// Bumping this tells <ThreadList> to refetch (new/updated thread).
const [listSignal, setListSignal] = useState(0);

const newConversation = useCallback(() => setActiveThreadId(undefined), []);
const refreshList = useCallback(() => setListSignal((n) => n + 1), []);

return (
<div className="min-h-screen bg-background">
<div className="mx-auto max-w-7xl px-6 py-12">
<div className="mb-8">
<h1 className="mb-2 text-3xl font-bold">Agent History</h1>
<p className="text-base text-muted-foreground">
<code>&lt;ThreadList&gt;</code> + <code>&lt;AgentThread&gt;</code>{" "}
from <code>@databricks/appkit-ui/react/beta</code>. Threads persist
across restarts when the agent uses <code>LakebaseThreadStore</code>
.
</p>
</div>

<div className="flex h-[700px] gap-6">
<div className="w-72 shrink-0 rounded-lg border bg-card">
<ThreadList
activeThreadId={activeThreadId}
onSelect={setActiveThreadId}
onNewThread={newConversation}
refetchSignal={listSignal}
/>
</div>
<div className="min-w-0 flex-1 rounded-lg border bg-card">
<AgentThread
threadId={activeThreadId}
onThreadCreated={setActiveThreadId}
onTurnComplete={refreshList}
/>
</div>
</div>
</div>
</div>
);
}
9 changes: 8 additions & 1 deletion apps/dev-playground/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
serving,
WRITE_ACTIONS,
} from "@databricks/appkit";
import { agents, aiSearch } from "@databricks/appkit/beta";
import { agents, aiSearch, LakebaseThreadStore } from "@databricks/appkit/beta";

import { lakebaseExamples } from "./lakebase-examples-plugin";
import { reconnect } from "./reconnect-plugin";
Expand Down Expand Up @@ -115,6 +115,13 @@ createApp({
// conversational default for the bare `/agent` route (the markdown agents
// are dispatchers or ephemeral and don't make sense as the landing agent).
defaultAgent: "helper",
// Persist threads across restarts when a Lakebase is bound (same
// LAKEBASE_ENDPOINT signal the lakebase plugin uses above). The store
// self-bootstraps its tables on setup; without Lakebase we fall back to
// the in-memory store, so local dev needs no database.
...(process.env.LAKEBASE_ENDPOINT
? { threadStore: new LakebaseThreadStore() }
: {}),
}),
aiSearch({
indexes: {
Expand Down
Loading
Loading