Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/dev-playground/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ env:
valueFrom: volume
- name: DATABRICKS_VOLUME_OTHER
valueFrom: other-volume
- name: DATABRICKS_VS_INDEX_NAME
valueFrom: vs-index
8 changes: 8 additions & 0 deletions apps/dev-playground/client/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ function RootComponent() {
Serving
</Button>
</Link>
<Link to="/vector-search" className="no-underline">
<Button
variant="ghost"
className="text-foreground hover:text-secondary-foreground"
>
Vector Search
</Button>
</Link>
<ThemeSelector />
</div>
</nav>
Expand Down
154 changes: 154 additions & 0 deletions apps/dev-playground/client/src/routes/vector-search.route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
Button,
Card,
CardContent,
CardHeader,
CardTitle,
Input,
} from "@databricks/appkit-ui/react";
import { createFileRoute } from "@tanstack/react-router";
import { Search } from "lucide-react";
import { useState } from "react";
import { Header } from "@/components/layout/header";

export const Route = createFileRoute("/vector-search")({
component: VectorSearchRoute,
});

interface SearchResult {
score: number;
data: Record<string, unknown>;
}

interface SearchResponse {
results: SearchResult[];
totalCount: number;
queryTimeMs: number;
queryType: string;
}

function VectorSearchRoute() {
const [query, setQuery] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [response, setResponse] = useState<SearchResponse | null>(null);

const handleSearch = async () => {
if (!query.trim()) return;
setLoading(true);
setError(null);
setResponse(null);

try {
const res = await fetch("/api/vector-search/demo/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ queryText: query }),
});

if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error ?? `HTTP ${res.status}: ${res.statusText}`);
}

const data: SearchResponse = await res.json();
setResponse(data);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
void handleSearch();
}
};

return (
<div className="min-h-screen bg-background">
<main className="max-w-4xl mx-auto px-6 py-12">
<Header
title="Vector Search"
description="Query a Databricks Vector Search index with hybrid search."
tooltip="Uses the Vector Search plugin to query a Databricks Vector Search index via the REST API"
/>

<div className="flex flex-col gap-6">
<div className="flex gap-3">
<Input
placeholder="Enter a search query..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-1"
/>
<Button
onClick={() => void handleSearch()}
disabled={loading || !query.trim()}
>
{loading ? (
"Searching..."
) : (
<>
<Search className="h-4 w-4 mr-2" />
Search
</>
)}
</Button>
</div>

{error && (
<div className="rounded-md border border-destructive bg-destructive/10 px-4 py-3 text-sm text-destructive">
{error}
</div>
)}

{response && (
<div className="flex flex-col gap-4">
<div className="text-sm text-muted-foreground">
{response.totalCount} result
{response.totalCount !== 1 ? "s" : ""} &middot;{" "}
{response.queryTimeMs}ms &middot; {response.queryType}
</div>

{response.results.length === 0 ? (
<p className="text-sm text-muted-foreground">
No results found.
</p>
) : (
response.results.map((result, index) => (
<Card key={`${result.score}-${index}`}>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center justify-between">
<span>Result {index + 1}</span>
<span className="text-muted-foreground font-normal">
score: {result.score.toFixed(4)}
</span>
</CardTitle>
</CardHeader>
<CardContent>
<dl className="grid grid-cols-1 gap-1">
{Object.entries(result.data).map(([key, value]) => (
<div key={key} className="flex gap-2 text-sm">
<dt className="font-medium text-muted-foreground min-w-[80px] shrink-0">
{key}
</dt>
<dd className="text-foreground break-all">
{String(value ?? "")}
</dd>
</div>
))}
</dl>
</CardContent>
</Card>
))
)}
</div>
)}
</div>
</main>
</div>
);
}
11 changes: 11 additions & 0 deletions apps/dev-playground/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
serving,
} from "@databricks/appkit";
import { WorkspaceClient } from "@databricks/sdk-experimental";
import { vectorSearch } from "../../../packages/appkit/src/plugins/vector-search";
import { lakebaseExamples } from "./lakebase-examples-plugin";
import { reconnect } from "./reconnect-plugin";
import { telemetryExamples } from "./telemetry-example-plugin";
Expand All @@ -34,6 +35,16 @@ createApp({
lakebaseExamples(),
files(),
serving(),
vectorSearch({
indexes: {
demo: {
indexName:
process.env.DATABRICKS_VS_INDEX_NAME ?? "catalog.schema.index",
columns: ["id", "text", "title"],
queryType: "hybrid",
},
},
}),
],
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
}).then((appkit) => {
Expand Down
Loading