@dbx-tools/ui-search 0.6.108 → 0.6.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,19 +3,21 @@
3
3
  React search box and results for Databricks AI Search.
4
4
 
5
5
  Import this package when an AppKit UI wants a drop-in, search-as-you-type box
6
- over the [`@dbx-tools/search`](../../node/search) plugin. The components
7
- read the plugin's boot config (indexes, default index, page size, route path)
8
- through AppKit's `usePluginClientConfig`, so search is one component and zero
9
- props.
6
+ over AppKit's beta `aiSearch` plugin. The components delegate single-index
7
+ queries to `useAiSearchQuery`, so native AppKit owns aliases, routes,
8
+ cancellation, and query state. The same UI works with
9
+ `@dbx-tools/search`'s `lakebaseAiSearch` provider because it implements the
10
+ native client-config and response contract.
10
11
 
11
12
  **Key features:**
12
13
 
13
14
  - `SearchBox` - a debounced, cancellable search-as-you-type input with a results
14
15
  dropdown, styled with AppKit tokens. Meilisearch-style instant search.
15
16
  - `SearchResults` - a presentational hit list for a full-page results layout.
16
- - `useSearch` - the hook the components share: `{ query, setQuery, hits,
17
+ - `useSearch` - a debounced presentation adapter over AppKit
18
+ `useAiSearchQuery`: `{ query, setQuery, hits,
17
19
  loading, error, config, submit, clear }`, debounced and abortable, targeting
18
- the single-index or universal (federated) route.
20
+ native single-index queries or the dbx-tools universal route.
19
21
  - Universal search with one flag (`universal`) to search every configured index.
20
22
  - `renderHit` overrides on both components for full control of a row; a sensible
21
23
  default shows a title, id, and score.
@@ -61,6 +63,7 @@ function Results() {
61
63
  - `@dbx-tools/ui-search/styles.css` - AppKit-token styling for the box and
62
64
  results; import once after Tailwind and your AppKit-UI theme.
63
65
 
64
- Runtime search, routes, and the plugin live in
65
- [`@dbx-tools/search`](../../node/search); the wire contract lives in
66
- [`@dbx-tools/shared-search`](../../shared/search).
66
+ Native Vector Search runtime and routes come from `@databricks/appkit`.
67
+ Federated search, agent tools, lifecycle operations, and the Lakebase provider
68
+ live in [`@dbx-tools/search`](../../node/search); its extension wire contract
69
+ lives in [`@dbx-tools/shared-search`](../../shared/search).
package/package.json CHANGED
@@ -24,9 +24,10 @@
24
24
  "typescript": "^5.9.3"
25
25
  },
26
26
  "dependencies": {
27
- "@dbx-tools/shared-core": "0.6.108",
28
- "@dbx-tools/shared-search": "0.6.108",
29
- "@dbx-tools/ui-appkit": "0.6.108",
27
+ "@databricks/appkit-ui": "^0.60.0",
28
+ "@dbx-tools/shared-core": "0.6.112",
29
+ "@dbx-tools/shared-search": "0.6.112",
30
+ "@dbx-tools/ui-appkit": "0.6.112",
30
31
  "lucide-react": "^0.554.0",
31
32
  "react": "^19.2.4",
32
33
  "react-dom": "^19.2.4"
@@ -35,7 +36,7 @@
35
36
  "publishConfig": {
36
37
  "access": "public"
37
38
  },
38
- "version": "0.6.108",
39
+ "version": "0.6.112",
39
40
  "type": "module",
40
41
  "exports": {
41
42
  "./react": "./src/react/index.ts",
@@ -1,8 +1,8 @@
1
1
  // React surface for `@dbx-tools/ui-search`: a drop-in `SearchBox`
2
2
  // (search-as-you-type over Databricks AI Search), a `SearchResults` list for a
3
- // full-page layout, and the `useSearch` hook they share. All three talk to the
4
- // `@dbx-tools/search` plugin's routes and read its boot config through
5
- // AppKit's `usePluginClientConfig`, so search is one component and zero props.
3
+ // full-page layout, and the `useSearch` hook they share. Single-index queries
4
+ // delegate to AppKit's native `useAiSearchQuery`; universal search uses the
5
+ // dbx-tools extension route.
6
6
  // Styled with AppKit tokens (import `@dbx-tools/ui-search/styles.css`).
7
7
 
8
8
  export type {
@@ -1,8 +1,7 @@
1
- // `SearchBox` a drop-in, Meilisearch-style search-as-you-type input for
1
+ // `SearchBox` is a drop-in search-as-you-type input for
2
2
  // Databricks AI Search. It wires an AppKit `Input` to the {@link useSearch}
3
- // hook and renders the hits in a dropdown as the user types, so adding search
4
- // to an app is one component and zero configuration: the plugin's client config
5
- // supplies the index, page size, and route.
3
+ // hook and renders hits in a dropdown as the user types. AppKit's native
4
+ // `aiSearch` client config supplies the index alias and query route.
6
5
  //
7
6
  // It is presentational and unopinionated about what a hit looks like: pass a
8
7
  // `renderHit` to control each row, or rely on the default which shows the first
@@ -1,17 +1,15 @@
1
- // `useSearch` a small React hook that turns the AI Search plugin's HTTP
2
- // routes into a debounced, cancellable search-as-you-type state machine. It
3
- // reads the plugin's boot config (indexes, default index, page size, base path)
4
- // via AppKit's `usePluginClientConfig`, so a search box needs no props to know
5
- // where to POST. Point it at `@dbx-tools/search`'s `POST /api/search`
6
- // (single index) or `POST /api/search/universal` (federated) route.
1
+ // `useSearch` adapts AppKit's native `useAiSearchQuery` to a debounced
2
+ // search-as-you-type state machine. Single-index queries use AppKit's native
3
+ // route and client config; universal queries use the dbx-tools extension route.
7
4
 
8
- import type {
9
- SearchClientConfig,
10
- SearchHit,
11
- SearchMode,
12
- SearchResult,
5
+ import {
6
+ search as sharedSearch,
7
+ type SearchClientConfig,
8
+ type SearchHit,
9
+ type SearchMode,
10
+ type SearchResult,
13
11
  } from "@dbx-tools/shared-search";
14
- import { usePluginClientConfig } from "@dbx-tools/ui-appkit/react";
12
+ import { useAiSearchQuery, type AiSearchRequest } from "@databricks/appkit-ui/react/beta";
15
13
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
16
14
 
17
15
  /** Options for {@link useSearch}. */
@@ -28,8 +26,6 @@ export interface UseSearchOptions {
28
26
  debounceMs?: number;
29
27
  /** Minimum query length before searching. Defaults to 1. */
30
28
  minLength?: number;
31
- /** The plugin name to read config from / route under. Defaults to "search". */
32
- pluginName?: string;
33
29
  }
34
30
 
35
31
  /** The state {@link useSearch} returns. */
@@ -52,25 +48,34 @@ export interface UseSearchState {
52
48
  clear: () => void;
53
49
  }
54
50
 
55
- const DEFAULT_BASE_PATH = "/api/search";
51
+ const UNIVERSAL_SEARCH_PATH = "/api/search/universal";
52
+
53
+ function toHits(
54
+ results: Array<{ score: number; data: Record<string, unknown> }>,
55
+ index: string | null,
56
+ ): SearchHit[] {
57
+ return results.map((result, resultIndex) => ({
58
+ id: String(result.data.id ?? Object.values(result.data)[0] ?? resultIndex),
59
+ score: result.score,
60
+ fields: result.data,
61
+ ...(index ? { index } : {}),
62
+ }));
63
+ }
56
64
 
57
65
  /**
58
- * Search-as-you-type against the AI Search plugin. Debounces input, cancels the
59
- * previous request when a new one starts, and exposes `{ query, setQuery, hits,
60
- * loading, error }` for a search box to render. Reads the plugin's client config
61
- * for the base path, default index, and page size.
66
+ * Search-as-you-type against AppKit AI Search. Debounces input, cancels stale
67
+ * native queries, and exposes `{ query, setQuery, hits, loading, error }` for a
68
+ * search box to render.
62
69
  */
63
70
  export function useSearch(options: UseSearchOptions = {}): UseSearchState {
64
- const pluginName = options.pluginName ?? "search";
65
- const config = usePluginClientConfig<SearchClientConfig>(pluginName);
66
- const basePath = config?.basePath ?? DEFAULT_BASE_PATH;
71
+ const native = useAiSearchQuery({ ...(options.index ? { alias: options.index } : {}) });
67
72
  const debounceMs = options.debounceMs ?? 200;
68
73
  const minLength = options.minLength ?? 1;
69
74
 
70
75
  const [query, setQueryState] = useState("");
71
76
  const [hits, setHits] = useState<SearchHit[]>([]);
72
- const [loading, setLoading] = useState(false);
73
- const [error, setError] = useState<string | null>(null);
77
+ const [universalLoading, setUniversalLoading] = useState(false);
78
+ const [universalError, setUniversalError] = useState<string | null>(null);
74
79
 
75
80
  const abortRef = useRef<AbortController | null>(null);
76
81
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -80,31 +85,33 @@ export function useSearch(options: UseSearchOptions = {}): UseSearchState {
80
85
  abortRef.current?.abort();
81
86
  if (text.trim().length < minLength) {
82
87
  setHits([]);
83
- setLoading(false);
88
+ setUniversalLoading(false);
89
+ return;
90
+ }
91
+ if (!options.universal) {
92
+ const resolvedQueryType = sharedSearch.toAiSearchQueryType(options.mode);
93
+ const request: AiSearchRequest = {
94
+ queryText: text,
95
+ ...(options.limit ? { numResults: options.limit } : {}),
96
+ ...(resolvedQueryType ? { queryType: resolvedQueryType } : {}),
97
+ };
98
+ const result = await native.search(request);
99
+ setHits(result ? toHits(result.results, native.alias) : []);
84
100
  return;
85
101
  }
86
102
  const controller = new AbortController();
87
103
  abortRef.current = controller;
88
- setLoading(true);
89
- setError(null);
104
+ setUniversalLoading(true);
105
+ setUniversalError(null);
90
106
  try {
91
- const path = options.universal ? `${basePath}/universal` : basePath;
92
- const body = options.universal
93
- ? {
94
- query: text,
95
- ...(options.limit ? { limit: options.limit } : {}),
96
- ...(options.mode ? { mode: options.mode } : {}),
97
- }
98
- : {
99
- query: text,
100
- ...(options.index ? { index: options.index } : {}),
101
- ...(options.limit ? { limit: options.limit } : {}),
102
- ...(options.mode ? { mode: options.mode } : {}),
103
- };
104
- const response = await fetch(path, {
107
+ const response = await fetch(UNIVERSAL_SEARCH_PATH, {
105
108
  method: "POST",
106
109
  headers: { "content-type": "application/json" },
107
- body: JSON.stringify(body),
110
+ body: JSON.stringify({
111
+ query: text,
112
+ ...(options.limit ? { limit: options.limit } : {}),
113
+ ...(options.mode ? { mode: options.mode } : {}),
114
+ }),
108
115
  signal: controller.signal,
109
116
  });
110
117
  if (!response.ok) {
@@ -114,13 +121,13 @@ export function useSearch(options: UseSearchOptions = {}): UseSearchState {
114
121
  setHits(result.hits ?? []);
115
122
  } catch (err) {
116
123
  if ((err as Error).name === "AbortError") return;
117
- setError((err as Error).message);
124
+ setUniversalError((err as Error).message);
118
125
  setHits([]);
119
126
  } finally {
120
- if (abortRef.current === controller) setLoading(false);
127
+ if (abortRef.current === controller) setUniversalLoading(false);
121
128
  }
122
129
  },
123
- [basePath, minLength, options.index, options.limit, options.mode, options.universal],
130
+ [minLength, native.alias, native.search, options.limit, options.mode, options.universal],
124
131
  );
125
132
 
126
133
  const setQuery = useCallback(
@@ -142,8 +149,8 @@ export function useSearch(options: UseSearchOptions = {}): UseSearchState {
142
149
  abortRef.current?.abort();
143
150
  setQueryState("");
144
151
  setHits([]);
145
- setError(null);
146
- setLoading(false);
152
+ setUniversalError(null);
153
+ setUniversalLoading(false);
147
154
  }, []);
148
155
 
149
156
  useEffect(
@@ -154,6 +161,21 @@ export function useSearch(options: UseSearchOptions = {}): UseSearchState {
154
161
  [],
155
162
  );
156
163
 
164
+ const config = useMemo<SearchClientConfig>(
165
+ () => ({
166
+ indexes: native.indexes.map((index) => ({
167
+ name: index.alias,
168
+ alias: index.alias,
169
+ isDefault: index.alias === native.alias,
170
+ })),
171
+ ...(native.alias ? { defaultIndex: native.alias } : {}),
172
+ pageSize: options.limit ?? 20,
173
+ basePath: "/api/ai-search",
174
+ }),
175
+ [native.alias, native.indexes, options.limit],
176
+ );
177
+ const loading = options.universal ? universalLoading : native.loading;
178
+ const error = options.universal ? universalError : native.error;
157
179
  return useMemo(
158
180
  () => ({ query, setQuery, hits, loading, error, config, submit, clear }),
159
181
  [query, setQuery, hits, loading, error, config, submit, clear],