@magicx-eng/ai-autocomplete-react 0.1.3
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 +227 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# @magicx-eng/ai-autocomplete-react
|
|
2
|
+
|
|
3
|
+
A React/TypeScript SDK that provides a guided AI-powered autocomplete experience with pill-based input and dropdown suggestions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Two tiers of integration** — use the full `<AIAutocomplete />` component or go headless with `useAIAutocomplete()` + `<AIAutocompleteDropdown />`
|
|
8
|
+
- **Pill-based input** — inline pills for unfilled parameters, bold text for completed ones
|
|
9
|
+
- **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete
|
|
10
|
+
- **Client-side filtering** — instant substring filtering on every keystroke
|
|
11
|
+
- **Option overrides** — inject or dynamically generate client-side options per suggestion type
|
|
12
|
+
- **Accessible** — ARIA combobox pattern with `role="listbox"`, `aria-activedescendant`
|
|
13
|
+
- **Lightweight** — zero dependencies beyond React
|
|
14
|
+
- **TypeScript first** — full type definitions shipped with the package
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @magicx-eng/ai-autocomplete-react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Peer Dependencies
|
|
23
|
+
|
|
24
|
+
React 18 or later:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add react react-dom
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Tier 1: Full Component
|
|
33
|
+
|
|
34
|
+
Drop-in component that owns the input, pills, dropdown, and all state:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-react";
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<AIAutocomplete
|
|
42
|
+
onSubmit={(result) => {
|
|
43
|
+
console.log(result.query); // "Create a email"
|
|
44
|
+
console.log(result.raw_query); // "Create a {{TASK_1}}"
|
|
45
|
+
console.log(result.completed_params); // [{ placeholder: "{{TASK_1}}", type: "task", ... }]
|
|
46
|
+
}}
|
|
47
|
+
placeholder="Create a"
|
|
48
|
+
className="my-autocomplete"
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Tier 2: Headless
|
|
55
|
+
|
|
56
|
+
Use the hook and dropdown separately for full control over the input and rendering:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useAIAutocomplete, AIAutocompleteDropdown } from "@magicx-eng/ai-autocomplete-react";
|
|
60
|
+
|
|
61
|
+
function App() {
|
|
62
|
+
const {
|
|
63
|
+
completedParams,
|
|
64
|
+
suggestionPills,
|
|
65
|
+
segments,
|
|
66
|
+
inputProps,
|
|
67
|
+
dropdownProps,
|
|
68
|
+
isLoading,
|
|
69
|
+
error,
|
|
70
|
+
reset,
|
|
71
|
+
} = useAIAutocomplete({
|
|
72
|
+
onSubmit: () => handleMySubmit(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<textarea {...inputProps} />
|
|
78
|
+
<AIAutocompleteDropdown {...dropdownProps} />
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
### `<AIAutocomplete />`
|
|
87
|
+
|
|
88
|
+
The full component. Owns the input, pills, dropdown, and keyboard navigation.
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Description |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `onSubmit` | `(result: AutocompleteResult) => void` | **required** | Called when the user submits via Enter or the submit button. |
|
|
93
|
+
| `optionOverrides?` | `Record<string, (query: string) => SuggestionOption[]>` | — | Override options per suggestion type. Receives the current filter query. For static options, ignore the query: `() => myOptions`. |
|
|
94
|
+
| `placeholder?` | `string` | — | Fallback placeholder shown when the server doesn't return one. |
|
|
95
|
+
| `className?` | `string` | — | CSS class applied to the container element. |
|
|
96
|
+
|
|
97
|
+
### `AutocompleteResult`
|
|
98
|
+
|
|
99
|
+
The object passed to `onSubmit`:
|
|
100
|
+
|
|
101
|
+
| Field | Type | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `query` | `string` | Plain text as the user sees it (e.g. `"Create a email"`). |
|
|
104
|
+
| `raw_query` | `string` | Text with placeholder tokens (e.g. `"Create a {{TASK_1}}"`). |
|
|
105
|
+
| `completed_params` | `CompletedParam[]` | Array of filled parameter values. |
|
|
106
|
+
|
|
107
|
+
### `CompletedParam`
|
|
108
|
+
|
|
109
|
+
| Field | Type | Description |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `placeholder` | `string` | The placeholder token (e.g. `"{{TASK_1}}"`). |
|
|
112
|
+
| `type` | `string` | Suggestion type (e.g. `"task"`, `"goal"`). |
|
|
113
|
+
| `text?` | `string` | The selected option text. |
|
|
114
|
+
| `kind` | `string \| null` | Category of the option, or `null`. |
|
|
115
|
+
|
|
116
|
+
### `<AIAutocompleteDropdown />`
|
|
117
|
+
|
|
118
|
+
The dropdown component. Used in Tier 2 for headless integration.
|
|
119
|
+
|
|
120
|
+
| Prop | Type | Description |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `suggestions` | `Suggestion[]` | Suggestions to display (from `dropdownProps`). |
|
|
123
|
+
| `activeIndex` | `number` | Currently highlighted option index. |
|
|
124
|
+
| `onSelect` | `(option: SuggestionOption) => void` | Called when an option is selected. |
|
|
125
|
+
| `isOpen` | `boolean` | Whether the dropdown is visible. |
|
|
126
|
+
| `id` | `string` | Listbox ID for ARIA. |
|
|
127
|
+
| `className?` | `string` | CSS class applied to the dropdown. |
|
|
128
|
+
|
|
129
|
+
### `useAIAutocomplete(options)`
|
|
130
|
+
|
|
131
|
+
The core hook. Manages state, API calls, filtering, pills, and keyboard navigation.
|
|
132
|
+
|
|
133
|
+
#### Options
|
|
134
|
+
|
|
135
|
+
| Field | Type | Default | Description |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| `onSubmit?` | `() => void` | — | Called when Enter is pressed with no option highlighted. |
|
|
138
|
+
| `optionOverrides?` | `OptionOverrides` | — | Override options per suggestion type (same as component prop). |
|
|
139
|
+
| `placeholder?` | `string` | — | Fallback placeholder. |
|
|
140
|
+
|
|
141
|
+
#### Return Value
|
|
142
|
+
|
|
143
|
+
| Field | Type | Description |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `completedParams` | `CompletedParamState[]` | All filled parameters with client-side state. |
|
|
146
|
+
| `suggestionPills` | `Suggestion[]` | Unfilled suggestion pills (excludes placeholders). |
|
|
147
|
+
| `activePillIndex` | `number` | Index of the active pill (always `0`). |
|
|
148
|
+
| `setActivePill` | `(index: number) => void` | Reorder pills by moving one to the front. |
|
|
149
|
+
| `removeLastParam` | `() => void` | Remove the last completed parameter and restore its suggestion. |
|
|
150
|
+
| `reEditParam` | `(param: CompletedParamState) => void` | Remove a specific completed parameter, restore its suggestion, and remove its text from the input. |
|
|
151
|
+
| `reset` | `() => void` | Clear all state and re-fetch initial suggestions. |
|
|
152
|
+
| `segments` | `Segment[]` | Text segments for overlay rendering — `"text"` or `"completed"` with the associated param. |
|
|
153
|
+
| `suggestions` | `Suggestion[]` | All suggestions including placeholders. |
|
|
154
|
+
| `activeIndex` | `number` | Currently highlighted dropdown option index (`-1` when none). |
|
|
155
|
+
| `isLoading` | `boolean` | Whether a fetch is in progress. |
|
|
156
|
+
| `error` | `Error \| null` | Last fetch error, or `null`. |
|
|
157
|
+
| `inputProps` | `object` | Spread onto a `<textarea>` — includes `value`, `placeholder`, `onChange`, `onKeyDown`, and ARIA attributes. |
|
|
158
|
+
| `dropdownProps` | `AIAutocompleteDropdownProps` | Spread onto `<AIAutocompleteDropdown />`. |
|
|
159
|
+
|
|
160
|
+
### `SuggestionOption`
|
|
161
|
+
|
|
162
|
+
| Field | Type | Description |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `text` | `string` | Display text for the option. |
|
|
165
|
+
| `icon?` | `string` | Icon shown before the text (e.g. emoji). |
|
|
166
|
+
| `tag?` | `string` | Tag shown after the text in smaller, muted style. |
|
|
167
|
+
| `is_tappable` | `boolean` | Whether the option can be selected. Non-tappable options are always-visible hints. |
|
|
168
|
+
| `kind` | `string \| null` | Category of the option. |
|
|
169
|
+
| `metadata?` | `Record<string, unknown>` | Arbitrary metadata passed through with the selection. |
|
|
170
|
+
|
|
171
|
+
### `Suggestion`
|
|
172
|
+
|
|
173
|
+
| Field | Type | Description |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `type` | `string` | Suggestion type identifier. |
|
|
176
|
+
| `text` | `string` | Display text for the pill. |
|
|
177
|
+
| `required` | `boolean` | Whether this suggestion must be filled. |
|
|
178
|
+
| `options` | `SuggestionOption[]` | Available options for this suggestion. |
|
|
179
|
+
|
|
180
|
+
## Keyboard Behavior
|
|
181
|
+
|
|
182
|
+
| Key | Behavior |
|
|
183
|
+
|---|---|
|
|
184
|
+
| **Arrow Down/Up** | Navigate options. |
|
|
185
|
+
| **Arrow Left/Right** | Jump between columns on the same row. |
|
|
186
|
+
| **Tab** | Select the first tappable option (or highlighted one). Accepts placeholder when input is empty. |
|
|
187
|
+
| **Enter** | Select highlighted option if one is highlighted. Otherwise submits the query. |
|
|
188
|
+
| **Escape** | Clear the highlighted option. |
|
|
189
|
+
|
|
190
|
+
## Option Overrides
|
|
191
|
+
|
|
192
|
+
Use `optionOverrides` to inject client-side options per suggestion type. The function receives the current filter query:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// Static options (ignore query)
|
|
196
|
+
const overrides = {
|
|
197
|
+
account: () => [
|
|
198
|
+
{ text: "Savings", is_tappable: true, kind: null },
|
|
199
|
+
{ text: "Checking", is_tappable: true, kind: null },
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Dynamic options (use query)
|
|
204
|
+
const overrides = {
|
|
205
|
+
value: (query) => {
|
|
206
|
+
const digits = query.replace(/\D/g, "");
|
|
207
|
+
if (!digits) return [{ text: "$100", is_tappable: true, kind: null }];
|
|
208
|
+
return [
|
|
209
|
+
{ text: `$${digits}`, is_tappable: true, kind: null },
|
|
210
|
+
{ text: `$${digits}0`, is_tappable: true, kind: null },
|
|
211
|
+
];
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Non-tappable hints (always shown, not filterable)
|
|
216
|
+
const overrides = {
|
|
217
|
+
value: () => [
|
|
218
|
+
{ text: "Enter an amount", is_tappable: false, kind: null },
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Override options are prepended to server options (deduplicated by `text`). When a filter query is active and an override function exists, it replaces the default client-side filtering entirely.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
Private package. All rights reserved.
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.container{position:relative;font-family:IBM Plex Sans,sans-serif}.checkmark{position:absolute;bottom:-130px;left:50%;transform:translate(-50%) translateY(8px) scale(.8);opacity:0;pointer-events:none;z-index:10;animation:none}.checkmarkVisible{animation:checkmarkFadeInOut 3s ease forwards}@keyframes checkmarkFadeInOut{0%{opacity:0;transform:translate(-50%) translateY(8px) scale(.8)}10%{opacity:1;transform:translate(-50%) translateY(0) scale(1)}80%{opacity:1;transform:translate(-50%) translateY(0) scale(1)}to{opacity:0;transform:translate(-50%) translateY(-8px) scale(.8)}}.checkmarkPath{stroke-dasharray:30;stroke-dashoffset:30}.checkmarkVisible .checkmarkPath{animation:drawCheck .4s ease forwards .1s}@keyframes drawCheck{to{stroke-dashoffset:0}}.inputWrapper{min-height:60px;padding:24px;border:1px solid var(--color-border-default, #9ea5b2);border-radius:23px;background:transparent;overflow:hidden;display:flex;align-items:center;gap:12px}.editorArea{position:relative;flex:1;min-width:0}.sizerContent{position:relative;z-index:1;pointer-events:none;min-height:60px;white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:21px;line-height:38px}.sizerText{color:transparent}.placeholderText{color:var(--color-text-muted, #c1c4cb);opacity:.7}.textarea{position:absolute;top:0;left:0;width:100%;height:100%;padding:0;border:none;background:transparent;color:var(--color-text-default, #fff);caret-color:var(--color-text-default, #fff);font-family:inherit;font-size:21px;line-height:38px;white-space:pre-wrap;word-break:break-word;outline:none;resize:none;overflow:hidden}.textarea::placeholder{color:var(--color-text-muted, #c1c4cb)}.submitButton{flex-shrink:0;width:36px;height:36px;border-radius:50%;border:none;background:var(--color-text-default, #fff);color:var(--color-bg-default, #000);cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:opacity .2s ease}.submitButton:hover{opacity:.85}.dropdown{position:absolute;left:0;right:0;top:100%;margin-top:6px;background:var(--color-background-default, #00002d);border-radius:23px;overflow:hidden;z-index:10;animation:fadeIn .2s ease forwards}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 18px;padding:18px 24px;max-height:192px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}.grid::-webkit-scrollbar{width:6px}.grid::-webkit-scrollbar-track{background:transparent}.grid::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.item{display:flex;align-items:center;font-family:IBM Plex Sans,sans-serif;font-size:21px;line-height:30px;color:var(--color-text-muted, #c1c4cb);white-space:nowrap;opacity:.35;animation:fadeIn .2s ease forwards}.tappable{cursor:pointer}.tappable:hover{color:var(--color-text-default, #fff)}.nonTappable{cursor:default;opacity:.3}.highlighted{color:var(--color-text-default, #fff);opacity:.5}.tag{font-size:13px;margin-left:6px;opacity:.5}.list{position:relative;z-index:1;pointer-events:auto;display:inline-flex;gap:5px;align-items:center;vertical-align:middle;transform:translateY(-3px)}.pill{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:7px 9px;border:none;border-radius:6px;background:var(--color-background-supportive, #313255);color:var(--color-text-muted, #c1c4cb);font-family:IBM Plex Sans,sans-serif;font-size:21px;line-height:30px;cursor:pointer;white-space:nowrap;animation:fadeIn .2s ease forwards}.pill:hover{filter:brightness(1.2)}.active{outline:1px solid #5a5b8a}@keyframes fadeIn{0%{opacity:0}}
|
|
2
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/AIAutocomplete.module.css","../src/AIAutocompleteDropdown.module.css","../src/components/SuggestionGrid.module.css","../src/components/SuggestionItem.module.css","../src/components/PillList.module.css"],"sourcesContent":[".container {\n position: relative;\n font-family: \"IBM Plex Sans\", sans-serif;\n}\n\n.checkmark {\n position: absolute;\n bottom: -130px;\n left: 50%;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n animation: none;\n}\n\n.checkmarkVisible {\n animation: checkmarkFadeInOut 3s ease forwards;\n}\n\n@keyframes checkmarkFadeInOut {\n 0% {\n opacity: 0;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n }\n 10% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 80% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateX(-50%) translateY(-8px) scale(0.8);\n }\n}\n\n.checkmarkPath {\n stroke-dasharray: 30;\n stroke-dashoffset: 30;\n}\n\n.checkmarkVisible .checkmarkPath {\n animation: drawCheck 0.4s ease forwards 0.1s;\n}\n\n@keyframes drawCheck {\n to {\n stroke-dashoffset: 0;\n }\n}\n\n.inputWrapper {\n min-height: 60px;\n padding: 24px;\n border: 1px solid var(--color-border-default, #9ea5b2);\n border-radius: 23px;\n background: transparent;\n overflow: hidden;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.editorArea {\n position: relative;\n flex: 1;\n min-width: 0;\n}\n\n.sizerContent {\n position: relative;\n z-index: 1;\n pointer-events: none;\n min-height: 60px;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n}\n\n.sizerText {\n color: transparent;\n}\n\n.placeholderText {\n color: var(--color-text-muted, #c1c4cb);\n opacity: 0.7;\n}\n\n.textarea {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--color-text-default, #fff);\n caret-color: var(--color-text-default, #fff);\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n white-space: pre-wrap;\n word-break: break-word;\n outline: none;\n resize: none;\n overflow: hidden;\n}\n\n.textarea::placeholder {\n color: var(--color-text-muted, #c1c4cb);\n}\n\n.submitButton {\n flex-shrink: 0;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: var(--color-text-default, #fff);\n color: var(--color-bg-default, #000);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: opacity 0.2s ease;\n}\n\n.submitButton:hover {\n opacity: 0.85;\n}\n",".dropdown {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n margin-top: 6px;\n background: var(--color-background-default, #00002d);\n border-radius: 23px;\n overflow: hidden;\n z-index: 10;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n",".grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n padding: 18px 24px;\n max-height: 192px;\n overflow-y: auto;\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.3) transparent;\n}\n\n.grid::-webkit-scrollbar {\n width: 6px;\n}\n\n.grid::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.grid::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 3px;\n}\n",".item {\n display: flex;\n align-items: center;\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n color: var(--color-text-muted, #c1c4cb);\n white-space: nowrap;\n opacity: 0.35;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n\n.tappable {\n cursor: pointer;\n}\n\n.tappable:hover {\n color: var(--color-text-default, #fff);\n}\n\n.nonTappable {\n cursor: default;\n opacity: 0.3;\n}\n\n.highlighted {\n color: var(--color-text-default, #fff);\n opacity: 0.5;\n}\n\n.tag {\n font-size: 13px;\n margin-left: 6px;\n opacity: 0.5;\n}\n",".list {\n position: relative;\n z-index: 1;\n pointer-events: auto;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n vertical-align: middle;\n transform: translateY(-3px);\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 7px 9px;\n border: none;\n border-radius: 6px;\n background: var(--color-background-supportive, #313255);\n color: var(--color-text-muted, #c1c4cb);\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n cursor: pointer;\n white-space: nowrap;\n animation: fadeIn 200ms ease forwards;\n}\n\n.pill:hover {\n filter: brightness(1.2);\n}\n\n.active {\n outline: 1px solid #5a5b8a;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n"],"mappings":"AAAA,CAAC,UACC,SAAU,SACV,YAAa,aAAe,CAAE,UAChC,CAEA,CAAC,UACC,SAAU,SACV,OAAQ,OACR,KAAM,IACN,UAAW,UAAW,MAAM,WAAW,KAAK,MAAM,IAClD,QAAS,EACT,eAAgB,KAChB,QAAS,GACT,UAAW,IACb,CAEA,CAAC,iBACC,UAAW,mBAAmB,GAAG,KAAK,QACxC,CAEA,WAHa,mBAIX,GACE,QAAS,EACT,UAAW,UAAW,MAAM,WAAW,KAAK,MAAM,GACpD,CACA,IACE,QAAS,EACT,UAAW,UAAW,MAAM,WAAW,GAAG,MAAM,EAClD,CACA,IACE,QAAS,EACT,UAAW,UAAW,MAAM,WAAW,GAAG,MAAM,EAClD,CACA,GACE,QAAS,EACT,UAAW,UAAW,MAAM,WAAW,MAAM,MAAM,GACrD,CACF,CAEA,CAAC,cACC,iBAAkB,GAClB,kBAAmB,EACrB,CAEA,CA5BC,iBA4BiB,CALjB,cAMC,UAAW,UAAU,IAAK,KAAK,SAAS,GAC1C,CAEA,WAHa,UAIX,GACE,kBAAmB,CACrB,CACF,CAEA,CAAC,aACC,WAAY,KAvDd,QAwDW,KACT,OAAQ,IAAI,MAAM,IAAI,sBAAsB,EAAE,SAzDhD,cA0DiB,KACf,WAAY,YACZ,SAAU,OACV,QAAS,KACT,YAAa,OACb,IAAK,IACP,CAEA,CAAC,WACC,SAAU,SACV,KAAM,EACN,UAAW,CACb,CAEA,CAAC,aACC,SAAU,SACV,QAAS,EACT,eAAgB,KAChB,WAAY,KACZ,YAAa,SACb,WAAY,WACZ,YAAa,QACb,UAAW,KACX,YAAa,IACf,CAEA,CAAC,UACC,MAAO,WACT,CAEA,CAAC,gBACC,MAAO,IAAI,kBAAkB,EAAE,SAC/B,QAAS,EACX,CAEA,CAAC,SACC,SAAU,SACV,IAAK,EACL,KAAM,EACN,MAAO,KACP,OAAQ,KAlGV,QAmGW,EACT,OAAQ,KACR,WAAY,YACZ,MAAO,IAAI,oBAAoB,EAAE,MACjC,YAAa,IAAI,oBAAoB,EAAE,MACvC,YAAa,QACb,UAAW,KACX,YAAa,KACb,YAAa,SACb,WAAY,WACZ,QAAS,KACT,OAAQ,KACR,SAAU,MACZ,CAEA,CArBC,QAqBQ,cACP,MAAO,IAAI,kBAAkB,EAAE,QACjC,CAEA,CAAC,aACC,YAAa,EACb,MAAO,KACP,OAAQ,KAzHV,cA0HiB,IACf,OAAQ,KACR,WAAY,IAAI,oBAAoB,EAAE,MACtC,MAAO,IAAI,kBAAkB,EAAE,MAC/B,OAAQ,QACR,QAAS,KACT,YAAa,OACb,gBAAiB,OAjInB,QAkIW,EACT,WAAY,QAAQ,IAAK,IAC3B,CAEA,CAhBC,YAgBY,OACX,QAAS,GACX,CCxIA,CAAC,SACC,SAAU,SACV,KAAM,EACN,MAAO,EACP,IAAK,KACL,WAAY,IACZ,WAAY,IAAI,0BAA0B,EAAE,SAN9C,cAOiB,KACf,SAAU,OACV,QAAS,GACT,UAAW,OAAO,IAAM,KAAK,QAC/B,CAEA,WAHa,OAIX,GACE,QAAS,CACX,CACA,GACE,QAAS,CACX,CACF,CCpBA,CAAC,KACC,QAAS,KACT,sBAAuB,IAAI,IAC3B,IAAK,KAAK,KAHZ,QAIW,KAAK,KACd,WAAY,MACZ,WAAY,KACZ,gBAAiB,KACjB,gBAAiB,KAAK,GAAG,CAAE,GAAG,CAAE,GAAG,CAAE,IAAK,WAC5C,CAEA,CAXC,IAWI,oBACH,MAAO,GACT,CAEA,CAfC,IAeI,0BACH,WAAY,WACd,CAEA,CAnBC,IAmBI,0BACH,WAAY,UApBd,cAqBiB,GACjB,CCtBA,CAAC,KACC,QAAS,KACT,YAAa,OACb,YAAa,aAAe,CAAE,WAC9B,UAAW,KACX,YAAa,KACb,MAAO,IAAI,kBAAkB,EAAE,SAC/B,YAAa,OACb,QAAS,IACT,UAAW,OAAO,IAAM,KAAK,QAC/B,CAQA,CAAC,SACC,OAAQ,OACV,CAEA,CAJC,QAIQ,OACP,MAAO,IAAI,oBAAoB,EAAE,KACnC,CAEA,CAAC,YACC,OAAQ,QACR,QAAS,EACX,CAEA,CAAC,YACC,MAAO,IAAI,oBAAoB,EAAE,MACjC,QAAS,EACX,CAEA,CAAC,IACC,UAAW,KACX,YAAa,IACb,QAAS,EACX,CCxCA,CAAC,KACC,SAAU,SACV,QAAS,EACT,eAAgB,KAChB,QAAS,YACT,IAAK,IACL,YAAa,OACb,eAAgB,OAChB,UAAW,WAAW,KACxB,CAEA,CAAC,KACC,QAAS,YACT,YAAa,OACb,gBAAiB,OACjB,OAAQ,KAfV,QAgBW,IAAI,IACb,OAAQ,KAjBV,cAkBiB,IACf,WAAY,IAAI,6BAA6B,EAAE,SAC/C,MAAO,IAAI,kBAAkB,EAAE,SAC/B,YAAa,aAAe,CAAE,WAC9B,UAAW,KACX,YAAa,KACb,OAAQ,QACR,YAAa,OACb,UAAW,OAAO,IAAM,KAAK,QAC/B,CAEA,CAlBC,IAkBI,OACH,OAAQ,WAAW,IACrB,CAEA,CAAC,OACC,QAAS,IAAI,MAAM,OACrB,CAEA,WAXa,OAYX,GACE,QAAS,CACX,CACF","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ChangeEvent, KeyboardEvent } from 'react';
|
|
3
|
+
|
|
4
|
+
type TaskKind = "automation" | "email" | "insight";
|
|
5
|
+
interface CompletedParam {
|
|
6
|
+
placeholder: string;
|
|
7
|
+
type: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
kind: TaskKind | null;
|
|
10
|
+
}
|
|
11
|
+
interface SuggestionOption {
|
|
12
|
+
text: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
is_tappable: boolean;
|
|
16
|
+
kind: TaskKind | null;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
interface Suggestion {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
options: SuggestionOption[];
|
|
24
|
+
}
|
|
25
|
+
interface InputItem {
|
|
26
|
+
type: string;
|
|
27
|
+
text: string;
|
|
28
|
+
state: "completed" | "in_progress";
|
|
29
|
+
}
|
|
30
|
+
interface AutocompleteRequest {
|
|
31
|
+
data: {
|
|
32
|
+
raw_query: string;
|
|
33
|
+
completed_params: CompletedParam[];
|
|
34
|
+
contact_account_count?: number;
|
|
35
|
+
};
|
|
36
|
+
meta: {
|
|
37
|
+
request_id: string;
|
|
38
|
+
request_at: string;
|
|
39
|
+
language: string;
|
|
40
|
+
client_version: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
interface AutocompleteResponse {
|
|
44
|
+
data: {
|
|
45
|
+
raw_query: string;
|
|
46
|
+
input: InputItem[];
|
|
47
|
+
suggestions: Suggestion[];
|
|
48
|
+
is_ready?: boolean;
|
|
49
|
+
};
|
|
50
|
+
meta: {
|
|
51
|
+
request_id: string;
|
|
52
|
+
request_at: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
interface CompletedParamState extends CompletedParam {
|
|
56
|
+
text: string;
|
|
57
|
+
suggestionType: string;
|
|
58
|
+
suggestionPlaceholder: string;
|
|
59
|
+
options: SuggestionOption[];
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
type Segment = {
|
|
63
|
+
type: "text";
|
|
64
|
+
value: string;
|
|
65
|
+
} | {
|
|
66
|
+
type: "completed";
|
|
67
|
+
value: string;
|
|
68
|
+
param: CompletedParamState;
|
|
69
|
+
};
|
|
70
|
+
type OptionOverrides = Record<string, (query: string) => SuggestionOption[]>;
|
|
71
|
+
interface AIAutocompleteProps {
|
|
72
|
+
onSubmit: (result: AutocompleteResult) => void;
|
|
73
|
+
optionOverrides?: OptionOverrides;
|
|
74
|
+
maskCompletedText?: boolean;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
className?: string;
|
|
77
|
+
}
|
|
78
|
+
interface AutocompleteResult {
|
|
79
|
+
query: string;
|
|
80
|
+
raw_query: string;
|
|
81
|
+
completed_params: CompletedParam[];
|
|
82
|
+
}
|
|
83
|
+
interface UseAIAutocompleteOptions {
|
|
84
|
+
onSubmit?: () => void;
|
|
85
|
+
optionOverrides?: OptionOverrides;
|
|
86
|
+
maskCompletedText?: boolean;
|
|
87
|
+
placeholder?: string;
|
|
88
|
+
}
|
|
89
|
+
interface UseAIAutocompleteReturn {
|
|
90
|
+
completedParams: CompletedParamState[];
|
|
91
|
+
suggestionPills: Suggestion[];
|
|
92
|
+
activePillIndex: number;
|
|
93
|
+
setActivePill: (index: number) => void;
|
|
94
|
+
removeLastParam: () => void;
|
|
95
|
+
reEditParam: (param: CompletedParamState) => void;
|
|
96
|
+
reset: () => void;
|
|
97
|
+
segments: Segment[];
|
|
98
|
+
suggestions: Suggestion[];
|
|
99
|
+
activeIndex: number;
|
|
100
|
+
isReady: boolean;
|
|
101
|
+
isLoading: boolean;
|
|
102
|
+
error: Error | null;
|
|
103
|
+
inputProps: {
|
|
104
|
+
value: string;
|
|
105
|
+
placeholder: string | undefined;
|
|
106
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
107
|
+
onKeyDown: (e: KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
108
|
+
role: "combobox";
|
|
109
|
+
"aria-expanded": boolean;
|
|
110
|
+
"aria-activedescendant": string | undefined;
|
|
111
|
+
"aria-autocomplete": "list";
|
|
112
|
+
"aria-controls": string;
|
|
113
|
+
};
|
|
114
|
+
dropdownProps: AIAutocompleteDropdownProps;
|
|
115
|
+
}
|
|
116
|
+
interface AIAutocompleteDropdownProps {
|
|
117
|
+
suggestions: Suggestion[];
|
|
118
|
+
activeIndex: number;
|
|
119
|
+
onSelect: (option: SuggestionOption) => void;
|
|
120
|
+
isOpen: boolean;
|
|
121
|
+
id: string;
|
|
122
|
+
className?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
declare function AIAutocomplete({ onSubmit, optionOverrides, maskCompletedText, placeholder, className, }: AIAutocompleteProps): react_jsx_runtime.JSX.Element;
|
|
126
|
+
|
|
127
|
+
declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, isOpen, id, className, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element | null;
|
|
128
|
+
|
|
129
|
+
declare function useAIAutocomplete({ onSubmit, optionOverrides, maskCompletedText, placeholder: customPlaceholder, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
|
|
130
|
+
|
|
131
|
+
export { AIAutocomplete, AIAutocompleteDropdown, type AIAutocompleteDropdownProps, type AIAutocompleteProps, type AutocompleteRequest, type AutocompleteResponse, type AutocompleteResult, type CompletedParam, type CompletedParamState, type InputItem, type OptionOverrides, type Segment, type Suggestion, type SuggestionOption, type TaskKind, type UseAIAutocompleteOptions, type UseAIAutocompleteReturn, useAIAutocomplete };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ChangeEvent, KeyboardEvent } from 'react';
|
|
3
|
+
|
|
4
|
+
type TaskKind = "automation" | "email" | "insight";
|
|
5
|
+
interface CompletedParam {
|
|
6
|
+
placeholder: string;
|
|
7
|
+
type: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
kind: TaskKind | null;
|
|
10
|
+
}
|
|
11
|
+
interface SuggestionOption {
|
|
12
|
+
text: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
is_tappable: boolean;
|
|
16
|
+
kind: TaskKind | null;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
interface Suggestion {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
options: SuggestionOption[];
|
|
24
|
+
}
|
|
25
|
+
interface InputItem {
|
|
26
|
+
type: string;
|
|
27
|
+
text: string;
|
|
28
|
+
state: "completed" | "in_progress";
|
|
29
|
+
}
|
|
30
|
+
interface AutocompleteRequest {
|
|
31
|
+
data: {
|
|
32
|
+
raw_query: string;
|
|
33
|
+
completed_params: CompletedParam[];
|
|
34
|
+
contact_account_count?: number;
|
|
35
|
+
};
|
|
36
|
+
meta: {
|
|
37
|
+
request_id: string;
|
|
38
|
+
request_at: string;
|
|
39
|
+
language: string;
|
|
40
|
+
client_version: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
interface AutocompleteResponse {
|
|
44
|
+
data: {
|
|
45
|
+
raw_query: string;
|
|
46
|
+
input: InputItem[];
|
|
47
|
+
suggestions: Suggestion[];
|
|
48
|
+
is_ready?: boolean;
|
|
49
|
+
};
|
|
50
|
+
meta: {
|
|
51
|
+
request_id: string;
|
|
52
|
+
request_at: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
interface CompletedParamState extends CompletedParam {
|
|
56
|
+
text: string;
|
|
57
|
+
suggestionType: string;
|
|
58
|
+
suggestionPlaceholder: string;
|
|
59
|
+
options: SuggestionOption[];
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
type Segment = {
|
|
63
|
+
type: "text";
|
|
64
|
+
value: string;
|
|
65
|
+
} | {
|
|
66
|
+
type: "completed";
|
|
67
|
+
value: string;
|
|
68
|
+
param: CompletedParamState;
|
|
69
|
+
};
|
|
70
|
+
type OptionOverrides = Record<string, (query: string) => SuggestionOption[]>;
|
|
71
|
+
interface AIAutocompleteProps {
|
|
72
|
+
onSubmit: (result: AutocompleteResult) => void;
|
|
73
|
+
optionOverrides?: OptionOverrides;
|
|
74
|
+
maskCompletedText?: boolean;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
className?: string;
|
|
77
|
+
}
|
|
78
|
+
interface AutocompleteResult {
|
|
79
|
+
query: string;
|
|
80
|
+
raw_query: string;
|
|
81
|
+
completed_params: CompletedParam[];
|
|
82
|
+
}
|
|
83
|
+
interface UseAIAutocompleteOptions {
|
|
84
|
+
onSubmit?: () => void;
|
|
85
|
+
optionOverrides?: OptionOverrides;
|
|
86
|
+
maskCompletedText?: boolean;
|
|
87
|
+
placeholder?: string;
|
|
88
|
+
}
|
|
89
|
+
interface UseAIAutocompleteReturn {
|
|
90
|
+
completedParams: CompletedParamState[];
|
|
91
|
+
suggestionPills: Suggestion[];
|
|
92
|
+
activePillIndex: number;
|
|
93
|
+
setActivePill: (index: number) => void;
|
|
94
|
+
removeLastParam: () => void;
|
|
95
|
+
reEditParam: (param: CompletedParamState) => void;
|
|
96
|
+
reset: () => void;
|
|
97
|
+
segments: Segment[];
|
|
98
|
+
suggestions: Suggestion[];
|
|
99
|
+
activeIndex: number;
|
|
100
|
+
isReady: boolean;
|
|
101
|
+
isLoading: boolean;
|
|
102
|
+
error: Error | null;
|
|
103
|
+
inputProps: {
|
|
104
|
+
value: string;
|
|
105
|
+
placeholder: string | undefined;
|
|
106
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
107
|
+
onKeyDown: (e: KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
108
|
+
role: "combobox";
|
|
109
|
+
"aria-expanded": boolean;
|
|
110
|
+
"aria-activedescendant": string | undefined;
|
|
111
|
+
"aria-autocomplete": "list";
|
|
112
|
+
"aria-controls": string;
|
|
113
|
+
};
|
|
114
|
+
dropdownProps: AIAutocompleteDropdownProps;
|
|
115
|
+
}
|
|
116
|
+
interface AIAutocompleteDropdownProps {
|
|
117
|
+
suggestions: Suggestion[];
|
|
118
|
+
activeIndex: number;
|
|
119
|
+
onSelect: (option: SuggestionOption) => void;
|
|
120
|
+
isOpen: boolean;
|
|
121
|
+
id: string;
|
|
122
|
+
className?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
declare function AIAutocomplete({ onSubmit, optionOverrides, maskCompletedText, placeholder, className, }: AIAutocompleteProps): react_jsx_runtime.JSX.Element;
|
|
126
|
+
|
|
127
|
+
declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, isOpen, id, className, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element | null;
|
|
128
|
+
|
|
129
|
+
declare function useAIAutocomplete({ onSubmit, optionOverrides, maskCompletedText, placeholder: customPlaceholder, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
|
|
130
|
+
|
|
131
|
+
export { AIAutocomplete, AIAutocompleteDropdown, type AIAutocompleteDropdownProps, type AIAutocompleteProps, type AutocompleteRequest, type AutocompleteResponse, type AutocompleteResult, type CompletedParam, type CompletedParamState, type InputItem, type OptionOverrides, type Segment, type Suggestion, type SuggestionOption, type TaskKind, type UseAIAutocompleteOptions, type UseAIAutocompleteReturn, useAIAutocomplete };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var de=Object.defineProperty;var $e=Object.getOwnPropertyDescriptor;var Be=Object.getOwnPropertyNames;var Ue=Object.prototype.hasOwnProperty;var qe=(e,i)=>{for(var o in i)de(e,o,{get:i[o],enumerable:!0})},ze=(e,i,o,p)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of Be(i))!Ue.call(e,t)&&t!==o&&de(e,t,{get:()=>i[t],enumerable:!(p=$e(i,t))||p.enumerable});return e};var Fe=e=>ze(de({},"__esModule",{value:!0}),e);var it={};qe(it,{AIAutocomplete:()=>Te,AIAutocompleteDropdown:()=>se,useAIAutocomplete:()=>le});module.exports=Fe(it);var T=require("react");var A={};var ve={};var Se={};var Q={};var re=require("react/jsx-runtime");function Pe({option:e,isHighlighted:i,onSelect:o,onHighlight:p,id:t}){let l=[Q.item,i?Q.highlighted:"",e.is_tappable?Q.tappable:Q.nonTappable].filter(Boolean).join(" ");return(0,re.jsxs)("div",{id:t,role:"option","aria-selected":i,className:l,tabIndex:e.is_tappable?0:-1,onClick:()=>e.is_tappable&&o(e),onKeyDown:r=>{e.is_tappable&&(r.key==="Enter"||r.key===" ")&&(r.preventDefault(),o(e))},onMouseEnter:p,children:[e.icon?`${e.icon} ${e.text}`:e.text,e.tag&&(0,re.jsx)("span",{className:Q.tag,children:e.tag})]})}var fe=require("react/jsx-runtime");function ke({options:e,activeIndex:i,onSelect:o,onHighlight:p,listboxId:t}){return(0,fe.jsx)("div",{className:Se.grid,children:e.map((l,r)=>(0,fe.jsx)(Pe,{option:l,isHighlighted:r===i,onSelect:o,onHighlight:()=>p(r),id:`${t}-option-${r}`},l.text))})}var me=require("react/jsx-runtime");function se({suggestions:e,activeIndex:i,onSelect:o,isOpen:p,id:t,className:l}){if(!p||e.length===0)return null;let r=e[0];return r.options.length===0?null:(0,me.jsx)("div",{id:t,role:"listbox",className:`${ve.dropdown} ${l??""}`,onMouseDown:b=>b.preventDefault(),children:(0,me.jsx)(ke,{options:r.options,activeIndex:i,onSelect:o,onHighlight:()=>{},listboxId:t})})}var ae={};var ge=require("react/jsx-runtime");function Ge(e){return e===0?.4:e===1?.3:.15}function we({pills:e,activePillIndex:i,onSelectPill:o}){return(0,ge.jsx)("span",{className:ae.list,children:e.map((p,t)=>(0,ge.jsx)("button",{type:"button",className:`${ae.pill} ${t===i?ae.active:""}`,style:{opacity:Ge(t)},onClick:()=>o(t),children:p.text},`${p.type}-${p.text}`))})}var c=require("react");var Ye="0.1.0",Ve=process?.env.MAGICX_API_ENDPOINT||"/api/suggest",Ae=!1;function je(){let e=process?.env.MAGICX_AI_AUTOCOMPLETE_API_KEY||"";return!e&&!Ae&&(Ae=!0,console.warn("[AIAutocomplete] No API key set (MAGICX_AI_AUTOCOMPLETE_API_KEY). Requests will be sent without an Authorization header.")),e}function Je(){return process?.env.MAGICX_AUTH_SCHEME==="Basic"?"Basic":"Bearer"}function Ze(){return crypto.randomUUID()}function et(e,i){return{placeholder:e.placeholder,type:e.type,...i&&{text:e.text},kind:e.kind}}async function Ie(e,i,o){let p=je(),t=Je(),l=!o?.maskCompletedText,r=i.find(x=>x.type==="contact"&&x.metadata?.contact_account_count)?.metadata?.contact_account_count,b=typeof r=="number"?r:void 0,v={data:{raw_query:e,completed_params:i.map(x=>et(x,l)),...b!=null&&{contact_account_count:b}},meta:{request_id:Ze(),request_at:new Date().toISOString(),language:typeof navigator<"u"?navigator.language:"en-US",client_version:Ye}},h={"Content-Type":"application/json","X-App-Identifier":process?.env.MAGICX_APP_IDENTIFIER||"active-campaign-demo"};p&&(h.Authorization=t==="Basic"?`Basic ${btoa(p)}`:`Bearer ${p}`);let f=await fetch(Ve,{method:"POST",headers:h,body:JSON.stringify(v),signal:o?.signal});if(!f.ok)throw new Error(`API error: ${f.status} ${f.statusText}`);return f.json()}function ie(e,i){let o=e,p={},t=[];for(let l of i){let r=(p[l.type]??0)+1;p[l.type]=r;let v=`{{${l.type.toUpperCase().replace(/\s+/g,"_")}_${r}}}`,h=o.indexOf(l.text);h!==-1&&(o=o.slice(0,h)+v+o.slice(h+l.text.length)),t.push({...l,placeholder:v})}return{rawQuery:o,completedParams:t}}var tt=100,ot=300,nt=2;function Ce(e,i){let o=i.trimStart();if(!o)return e;let p=o.toLowerCase();return e.filter(t=>!t.is_tappable||t.text.toLowerCase().includes(p))}function he(e,i){let o=i.trim();if(!o)return null;let p=o.toLowerCase();return e.find(t=>t.is_tappable&&t.text.toLowerCase()===p)??null}function rt(e,i){let o=[],p=0;for(let l of i){let r=e.indexOf(l.text,p);r!==-1&&(r>p&&o.push({type:"text",value:e.slice(p,r)}),o.push({type:"completed",value:l.text,param:l}),p=r+l.text.length)}let t=e.slice(p);return t&&o.push({type:"text",value:t}),o}function st(e,i){let o=[],p=[],t=0;for(let l of i){let r=e.indexOf(l.text,t);r===-1?p.push(l):(o.push(l),t=r+l.text.length)}return{valid:o,invalid:p}}function at(e,i){return i?e.map(o=>{let p=i[o.type];if(!p)return o;let t=p("");if(t.length===0)return o;let l=new Set(t.map(b=>b.text)),r=(o.options??[]).filter(b=>!l.has(b.text));return{...o,options:[...t,...r]}}):e}function le({onSubmit:e,optionOverrides:i,maskCompletedText:o,placeholder:p}){let[t,l]=(0,c.useState)([]),[r,b]=(0,c.useState)(""),[v,h]=(0,c.useState)([]),[f,x]=(0,c.useState)(-1),[G,$]=(0,c.useState)(!1),[_,Y]=(0,c.useState)(null),[V,j]=(0,c.useState)(!1),E=(0,c.useRef)(0),B=(0,c.useRef)(null),U=(0,c.useRef)(""),q=(0,c.useRef)(e);q.current=e;let J=(0,c.useRef)(i);J.current=i;let Z=(0,c.useRef)(o);Z.current=o;let I=(0,c.useRef)(r);I.current=r;let D=(0,c.useRef)(v);D.current=v;let P=(0,c.useRef)(0),H=(0,c.useRef)(!1),ce=(0,c.useRef)(!1),pe=(0,c.useId)(),N=(0,c.useCallback)(async(s,d)=>{B.current?.abort();let n=new AbortController;B.current=n;let a=++E.current;D.current.some(u=>u.type!=="placeholder")||$(!0),Y(null);try{let u=await Ie(s,d,{maskCompletedText:Z.current,signal:n.signal});if(a!==E.current)return;let S=at(u.data.suggestions??[],J.current);j(u.data.is_ready??!1),U.current=s;let k=u.data.input??[],O=k[k.length-1],F=I.current;if(O?.state==="in_progress"){let R=F.lastIndexOf(O.text);R!==-1?P.current=R:P.current=F.length}else P.current=F.length;let C=S.filter(R=>R.type!=="placeholder")[0];if(C){let R=F.slice(P.current),K=he(C.options,R);K&&(l(L=>[...L,{placeholder:"",type:C.type,text:K.text,kind:K.kind,suggestionType:C.type,suggestionPlaceholder:C.text,options:C.options,metadata:K.metadata}]),S=S.filter(L=>L!==C))}h(S),$(!1),x(-1)}catch(u){a===E.current&&(Y(u instanceof Error?u:new Error(String(u))),$(!1))}},[]);(0,c.useEffect)(()=>(N("",[]),()=>{B.current?.abort()}),[N]);let _e=(0,c.useMemo)(()=>rt(r,t),[r,t]);P.current=Math.min(P.current,r.length);let ee=r.slice(P.current);console.log(`[filter] base=${P.current} query="${ee}" text="${r}"`);let z=v.filter(s=>s.type==="placeholder").map(s=>s.text).join(" ")||p||"",te=v.filter(s=>s.type!=="placeholder"),m=te[0],xe=m?i?.[m.type]:void 0,w=m?xe&&ee.trim()?xe(ee.trim()):Ce(m.options??[],ee):[],ue=z.length>0,oe=!G&&w.length>0&&(!!r||H.current||!ue),X=(0,c.useCallback)(s=>{if(!m)return;let d={placeholder:"",type:m.type,text:s.text,kind:s.kind,suggestionType:m.type,suggestionPlaceholder:m.text,options:m.options,metadata:s.metadata},n=P.current,a=r.slice(0,n);if(a.length>0&&!a.endsWith(" ")){let k=a.split(/\s+/).pop()??"";k&&s.text.toLowerCase().startsWith(k.toLowerCase())&&(a=a.slice(0,a.length-k.length))}let g=a.length>0&&a[a.length-1]!==" ",u=a+(g?" ":"")+s.text+" ";b(u),P.current=u.length,l(k=>[...k,d]),h(k=>k.filter(O=>O!==m)),H.current=!1,x(-1),te.length-1>0&&(ce.current=!0)},[m,te,r]),Oe=(0,c.useCallback)(s=>{let d=s.target.value,n=d.length>0?d[0].toUpperCase()+d.slice(1):d;b(n),H.current=!1,x(-1);let{valid:a,invalid:g}=st(n,t);if(g.length>0){l(a);for(let u of g)h(S=>[{type:u.suggestionType,text:u.suggestionPlaceholder,required:!0,options:u.options},...S])}if(m&&g.length===0){let u=n.slice(P.current),S=he(m.options,u);S&&(l(k=>[...k,{placeholder:"",type:m.type,text:S.text,kind:S.kind,suggestionType:m.type,suggestionPlaceholder:m.text,options:m.options,metadata:S.metadata}]),h(k=>k.filter(O=>O!==m)))}},[t,m]),W=(0,c.useRef)(null),M=(0,c.useRef)(null),ye=(0,c.useRef)(!0);(0,c.useEffect)(()=>{W.current&&clearTimeout(W.current),M.current&&clearTimeout(M.current);let s=d=>{if(ce.current)return ce.current=!1,!1;if(!r&&t.length===0)return ye.current?(N("",[]),!0):(ye.current=!0,!1);let n=r.slice(P.current),u=D.current.filter(L=>L.type!=="placeholder")[0],k=(u?Ce(u.options,n):[]).filter(L=>L.is_tappable),O=u?he(u.options,n)!==null:!1,F=n.trim().length>0;if(k.length>0&&!O&&F)return!1;let{rawQuery:ne,completedParams:C}=ie(r,t),R=ne.length<U.current.length,K=Math.abs(ne.length-U.current.length);return R||K>=d?(N(ne,C),!0):!1};return W.current=setTimeout(()=>{s(nt)&&M.current&&clearTimeout(M.current)},tt),M.current=setTimeout(()=>s(1),ot),()=>{W.current&&clearTimeout(W.current),M.current&&clearTimeout(M.current)}},[r,t,N]);let be=(0,c.useCallback)(()=>{let d=w.map((g,u)=>g.is_tappable?u:-1).filter(g=>g!==-1),n=d.filter(g=>g%2===0),a=d.filter(g=>g%2===1);return[...n,...a]},[w]),Re=(0,c.useCallback)(s=>{let d=be();switch(s.key){case"ArrowDown":{if(s.preventDefault(),d.length===0)return;let n=d.indexOf(f),a=n<d.length-1?n+1:0;x(d[a]);break}case"ArrowUp":{if(s.preventDefault(),d.length===0)return;let n=d.indexOf(f),a=n>0?n-1:d.length-1;x(d[a]);break}case"ArrowRight":{if(f<0)break;if(f%2===0){let a=f+1;a<w.length&&w[a]?.is_tappable&&(s.preventDefault(),x(a))}break}case"ArrowLeft":{if(f<0)break;if(f%2===1){let a=f-1;a>=0&&w[a]?.is_tappable&&(s.preventDefault(),x(a))}break}case"Enter":{s.preventDefault(),f>=0&&w[f]?.is_tappable?X(w[f]):q.current&&q.current();break}case"Tab":{if(f>=0&&w[f]?.is_tappable)s.preventDefault(),X(w[f]);else if(oe){let n=w.find(a=>a.is_tappable);n&&(s.preventDefault(),X(n))}else if(!r&&ue){s.preventDefault();let n=v.find(a=>a.type==="placeholder");b(z),P.current=z.length,n&&(l(a=>[...a,{placeholder:"",type:n.type,text:z,kind:null,suggestionType:n.type,suggestionPlaceholder:n.text,options:n.options}]),h(a=>a.filter(g=>g!==n)))}break}case"Escape":x(-1);break}},[f,w,ue,oe,z,X,v,be,r]),Ee=(0,c.useCallback)(s=>{let d=v.filter(u=>u.type!=="placeholder");if(s<0||s>=d.length)return;let n=d[s],a=d.filter((u,S)=>S!==s),g=v.filter(u=>u.type==="placeholder");h([...g,n,...a]),H.current=!0,x(-1)},[v]),De=(0,c.useCallback)(()=>{if(t.length===0)return;let s=t[t.length-1],d={type:s.suggestionType,text:s.suggestionPlaceholder,required:!0,options:s.options};l(n=>n.slice(0,-1)),h(n=>[d,...n]),x(-1)},[t]),Ne=(0,c.useCallback)(s=>{let d={type:s.suggestionType,text:s.suggestionPlaceholder,required:!0,options:s.options};b(n=>{let a=n.indexOf(s.text);if(a===-1)return n;let g=n.slice(0,a),u=n.slice(a+s.text.length),S=(g+u).replace(/ {2,}/g," ");return P.current=Math.min(P.current,S.length),S}),l(n=>n.filter(a=>a!==s)),h(n=>[d,...n]),x(-1),H.current=!0},[]),Me=(0,c.useCallback)(()=>{b(""),l([]),h([]),x(-1),j(!1),P.current=0,U.current="",N("",[])},[N]),Le=f>=0?`${pe}-option-${f}`:void 0;return{completedParams:t,suggestionPills:te,activePillIndex:0,setActivePill:Ee,removeLastParam:De,reEditParam:Ne,segments:_e,suggestions:v,activeIndex:f,isReady:V,isLoading:G,error:_,inputProps:{value:r,placeholder:z||void 0,onChange:Oe,onKeyDown:Re,role:"combobox","aria-expanded":oe,"aria-activedescendant":Le,"aria-autocomplete":"list","aria-controls":pe},reset:Me,dropdownProps:{suggestions:m?[{...m,options:w}]:[],activeIndex:f,onSelect:X,isOpen:oe,id:pe}}}var y=require("react/jsx-runtime");function Te({onSubmit:e,optionOverrides:i,maskCompletedText:o,placeholder:p,className:t}){let l=(0,T.useRef)(null),[r,b]=(0,T.useState)(!1),v=(0,T.useRef)(()=>{});(0,T.useEffect)(()=>{l.current?.focus()},[]);let{completedParams:h,suggestionPills:f,activePillIndex:x,setActivePill:G,segments:$,inputProps:_,dropdownProps:Y,reset:V}=le({onSubmit:()=>v.current(),optionOverrides:i,maskCompletedText:o,placeholder:p}),j=()=>{l.current?.focus()},E=!!_.value||h.length>0,B=(0,T.useCallback)(()=>{if(!E)return;let{rawQuery:I,completedParams:D}=ie(_.value,h);e({query:_.value.trim(),raw_query:I,completed_params:D}),V(),b(!0),setTimeout(()=>b(!1),3e3)},[E,_.value,h,e,V]);v.current=B;let{onChange:U,placeholder:q,...J}=_,Z=!_.value;return(0,y.jsxs)("div",{className:`${A.container} ${t??""}`,children:[(0,y.jsx)("div",{className:`${A.checkmark} ${r?A.checkmarkVisible:""}`,children:(0,y.jsxs)("svg",{width:"72",height:"72",viewBox:"0 0 24 24",fill:"none",role:"img","aria-label":"Success",children:[(0,y.jsx)("circle",{cx:"12",cy:"12",r:"12",fill:"#34C759"}),(0,y.jsx)("path",{d:"M7 12.5l3.5 3.5L17 9",stroke:"#000",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",className:A.checkmarkPath})]})}),(0,y.jsx)(se,{...Y}),(0,y.jsxs)("div",{className:A.inputWrapper,onClick:j,children:[(0,y.jsxs)("div",{className:A.editorArea,children:[(0,y.jsxs)("div",{className:A.sizerContent,"aria-hidden":"true",children:[Z&&q?(0,y.jsxs)("span",{className:A.placeholderText,children:[q," "]}):(0,y.jsxs)("span",{className:A.sizerText,children:[$.map((I,D)=>(0,y.jsx)("span",{children:I.value},`${D}-${I.type}`)),$.length===0&&"\xA0"]})," ",(0,y.jsx)(we,{pills:f,activePillIndex:x,onSelectPill:G})]}),(0,y.jsx)("textarea",{ref:l,className:A.textarea,rows:1,onChange:U,...J})]}),(0,y.jsx)("button",{type:"button",className:A.submitButton,disabled:!E,onClick:I=>{I.stopPropagation(),B()},"aria-label":"Submit",children:(0,y.jsx)("svg",{width:"18",height:"18",viewBox:"0 0 18 18",fill:"none",role:"img","aria-label":"Submit",children:(0,y.jsx)("path",{d:"M9 14V4M9 4L4 9M9 4L14 9",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})})]})]})}0&&(module.exports={AIAutocomplete,AIAutocompleteDropdown,useAIAutocomplete});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/AIAutocomplete.tsx","../src/AIAutocomplete.module.css","../src/AIAutocompleteDropdown.module.css","../src/components/SuggestionGrid.module.css","../src/components/SuggestionItem.module.css","../src/components/SuggestionItem.tsx","../src/components/SuggestionGrid.tsx","../src/AIAutocompleteDropdown.tsx","../src/components/PillList.module.css","../src/components/PillList.tsx","../src/hooks/useAIAutocomplete.ts","../src/utils/api.ts","../src/utils/buildQuery.ts"],"sourcesContent":["export { AIAutocomplete } from \"./AIAutocomplete\";\nexport { AIAutocompleteDropdown } from \"./AIAutocompleteDropdown\";\nexport { useAIAutocomplete } from \"./hooks/useAIAutocomplete\";\nexport type {\n AIAutocompleteDropdownProps,\n AIAutocompleteProps,\n AutocompleteRequest,\n AutocompleteResponse,\n AutocompleteResult,\n CompletedParam,\n CompletedParamState,\n InputItem,\n OptionOverrides,\n Segment,\n Suggestion,\n SuggestionOption,\n TaskKind,\n UseAIAutocompleteOptions,\n UseAIAutocompleteReturn,\n} from \"./types\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport styles from \"./AIAutocomplete.module.css\";\nimport { AIAutocompleteDropdown } from \"./AIAutocompleteDropdown\";\nimport { PillList } from \"./components/PillList\";\nimport { useAIAutocomplete } from \"./hooks/useAIAutocomplete\";\nimport type { AIAutocompleteProps } from \"./types\";\nimport { buildQuery } from \"./utils/buildQuery\";\n\nexport function AIAutocomplete({\n onSubmit,\n optionOverrides,\n maskCompletedText,\n placeholder,\n className,\n}: AIAutocompleteProps) {\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [showCheckmark, setShowCheckmark] = useState(false);\n const handleSubmitRef = useRef<() => void>(() => {});\n\n useEffect(() => {\n textareaRef.current?.focus();\n }, []);\n\n const {\n completedParams,\n suggestionPills,\n activePillIndex,\n setActivePill,\n segments,\n inputProps,\n dropdownProps,\n reset,\n } = useAIAutocomplete({\n onSubmit: () => handleSubmitRef.current(),\n optionOverrides,\n maskCompletedText,\n placeholder,\n });\n\n const handleContainerClick = () => {\n textareaRef.current?.focus();\n };\n\n const canSubmit = !!inputProps.value || completedParams.length > 0;\n\n const handleSubmit = useCallback(() => {\n if (!canSubmit) return;\n const { rawQuery, completedParams: finalParams } = buildQuery(\n inputProps.value,\n completedParams,\n );\n onSubmit({\n query: inputProps.value.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n reset();\n setShowCheckmark(true);\n setTimeout(() => setShowCheckmark(false), 3000);\n }, [canSubmit, inputProps.value, completedParams, onSubmit, reset]);\n\n handleSubmitRef.current = handleSubmit;\n\n const { onChange, placeholder: inputPlaceholder, ...restProps } = inputProps;\n const isEmpty = !inputProps.value;\n\n return (\n <div className={`${styles.container} ${className ?? \"\"}`}>\n <div className={`${styles.checkmark} ${showCheckmark ? styles.checkmarkVisible : \"\"}`}>\n <svg width=\"72\" height=\"72\" viewBox=\"0 0 24 24\" fill=\"none\" role=\"img\" aria-label=\"Success\">\n <circle cx=\"12\" cy=\"12\" r=\"12\" fill=\"#34C759\" />\n <path\n d=\"M7 12.5l3.5 3.5L17 9\"\n stroke=\"#000\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={styles.checkmarkPath}\n />\n </svg>\n </div>\n <AIAutocompleteDropdown {...dropdownProps} />\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: container click delegates to textarea focus */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: wrapper delegates focus to textarea */}\n <div className={styles.inputWrapper} onClick={handleContainerClick}>\n <div className={styles.editorArea}>\n <div className={styles.sizerContent} aria-hidden=\"true\">\n {isEmpty && inputPlaceholder ? (\n <span className={styles.placeholderText}>{inputPlaceholder} </span>\n ) : (\n <span className={styles.sizerText}>\n {segments.map((seg, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: segments are positional and don't reorder\n <span key={`${i}-${seg.type}`}>{seg.value}</span>\n ))}\n {segments.length === 0 && \"\\u00A0\"}\n </span>\n )}\n {\" \"}\n <PillList\n pills={suggestionPills}\n activePillIndex={activePillIndex}\n onSelectPill={setActivePill}\n />\n </div>\n <textarea\n ref={textareaRef}\n className={styles.textarea}\n rows={1}\n onChange={onChange}\n {...restProps}\n />\n </div>\n <button\n type=\"button\"\n className={styles.submitButton}\n disabled={!canSubmit}\n onClick={(e) => {\n e.stopPropagation();\n handleSubmit();\n }}\n aria-label=\"Submit\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n );\n}\n",".container {\n position: relative;\n font-family: \"IBM Plex Sans\", sans-serif;\n}\n\n.checkmark {\n position: absolute;\n bottom: -130px;\n left: 50%;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n animation: none;\n}\n\n.checkmarkVisible {\n animation: checkmarkFadeInOut 3s ease forwards;\n}\n\n@keyframes checkmarkFadeInOut {\n 0% {\n opacity: 0;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n }\n 10% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 80% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateX(-50%) translateY(-8px) scale(0.8);\n }\n}\n\n.checkmarkPath {\n stroke-dasharray: 30;\n stroke-dashoffset: 30;\n}\n\n.checkmarkVisible .checkmarkPath {\n animation: drawCheck 0.4s ease forwards 0.1s;\n}\n\n@keyframes drawCheck {\n to {\n stroke-dashoffset: 0;\n }\n}\n\n.inputWrapper {\n min-height: 60px;\n padding: 24px;\n border: 1px solid var(--color-border-default, #9ea5b2);\n border-radius: 23px;\n background: transparent;\n overflow: hidden;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.editorArea {\n position: relative;\n flex: 1;\n min-width: 0;\n}\n\n.sizerContent {\n position: relative;\n z-index: 1;\n pointer-events: none;\n min-height: 60px;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n}\n\n.sizerText {\n color: transparent;\n}\n\n.placeholderText {\n color: var(--color-text-muted, #c1c4cb);\n opacity: 0.7;\n}\n\n.textarea {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--color-text-default, #fff);\n caret-color: var(--color-text-default, #fff);\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n white-space: pre-wrap;\n word-break: break-word;\n outline: none;\n resize: none;\n overflow: hidden;\n}\n\n.textarea::placeholder {\n color: var(--color-text-muted, #c1c4cb);\n}\n\n.submitButton {\n flex-shrink: 0;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: var(--color-text-default, #fff);\n color: var(--color-bg-default, #000);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: opacity 0.2s ease;\n}\n\n.submitButton:hover {\n opacity: 0.85;\n}\n",".dropdown {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n margin-top: 6px;\n background: var(--color-background-default, #00002d);\n border-radius: 23px;\n overflow: hidden;\n z-index: 10;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n",".grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n padding: 18px 24px;\n max-height: 192px;\n overflow-y: auto;\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.3) transparent;\n}\n\n.grid::-webkit-scrollbar {\n width: 6px;\n}\n\n.grid::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.grid::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 3px;\n}\n",".item {\n display: flex;\n align-items: center;\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n color: var(--color-text-muted, #c1c4cb);\n white-space: nowrap;\n opacity: 0.35;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n\n.tappable {\n cursor: pointer;\n}\n\n.tappable:hover {\n color: var(--color-text-default, #fff);\n}\n\n.nonTappable {\n cursor: default;\n opacity: 0.3;\n}\n\n.highlighted {\n color: var(--color-text-default, #fff);\n opacity: 0.5;\n}\n\n.tag {\n font-size: 13px;\n margin-left: 6px;\n opacity: 0.5;\n}\n","import type { SuggestionOption } from \"../types\";\nimport styles from \"./SuggestionItem.module.css\";\n\ninterface SuggestionItemProps {\n option: SuggestionOption;\n isHighlighted: boolean;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: () => void;\n id: string;\n}\n\nexport function SuggestionItem({\n option,\n isHighlighted,\n onSelect,\n onHighlight,\n id,\n}: SuggestionItemProps) {\n const className = [\n styles.item,\n isHighlighted ? styles.highlighted : \"\",\n option.is_tappable ? styles.tappable : styles.nonTappable,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <div\n id={id}\n role=\"option\"\n aria-selected={isHighlighted}\n className={className}\n tabIndex={option.is_tappable ? 0 : -1}\n onClick={() => option.is_tappable && onSelect(option)}\n onKeyDown={(e) => {\n if (option.is_tappable && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onSelect(option);\n }\n }}\n onMouseEnter={onHighlight}\n >\n {option.icon ? `${option.icon} ${option.text}` : option.text}\n {option.tag && <span className={styles.tag}>{option.tag}</span>}\n </div>\n );\n}\n","import type { SuggestionOption } from \"../types\";\nimport styles from \"./SuggestionGrid.module.css\";\nimport { SuggestionItem } from \"./SuggestionItem\";\n\ninterface SuggestionGridProps {\n options: SuggestionOption[];\n activeIndex: number;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n listboxId: string;\n}\n\nexport function SuggestionGrid({\n options,\n activeIndex,\n onSelect,\n onHighlight,\n listboxId,\n}: SuggestionGridProps) {\n return (\n <div className={styles.grid}>\n {options.map((option, i) => (\n <SuggestionItem\n key={option.text}\n option={option}\n isHighlighted={i === activeIndex}\n onSelect={onSelect}\n onHighlight={() => onHighlight(i)}\n id={`${listboxId}-option-${i}`}\n />\n ))}\n </div>\n );\n}\n","import styles from \"./AIAutocompleteDropdown.module.css\";\nimport { SuggestionGrid } from \"./components/SuggestionGrid\";\nimport type { AIAutocompleteDropdownProps } from \"./types\";\n\nexport function AIAutocompleteDropdown({\n suggestions,\n activeIndex,\n onSelect,\n isOpen,\n id,\n className,\n}: AIAutocompleteDropdownProps) {\n if (!isOpen || suggestions.length === 0) return null;\n\n const activeSuggestion = suggestions[0];\n if (activeSuggestion.options.length === 0) return null;\n\n return (\n // biome-ignore lint/a11y/noStaticElementInteractions: preventDefault keeps focus on textarea\n <div\n id={id}\n role=\"listbox\"\n className={`${styles.dropdown} ${className ?? \"\"}`}\n onMouseDown={(e) => e.preventDefault()}\n >\n <SuggestionGrid\n options={activeSuggestion.options}\n activeIndex={activeIndex}\n onSelect={onSelect}\n onHighlight={() => {}}\n listboxId={id}\n />\n </div>\n );\n}\n",".list {\n position: relative;\n z-index: 1;\n pointer-events: auto;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n vertical-align: middle;\n transform: translateY(-3px);\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 7px 9px;\n border: none;\n border-radius: 6px;\n background: var(--color-background-supportive, #313255);\n color: var(--color-text-muted, #c1c4cb);\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n cursor: pointer;\n white-space: nowrap;\n animation: fadeIn 200ms ease forwards;\n}\n\n.pill:hover {\n filter: brightness(1.2);\n}\n\n.active {\n outline: 1px solid #5a5b8a;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n","import type { Suggestion } from \"../types\";\nimport styles from \"./PillList.module.css\";\n\ninterface PillListProps {\n pills: Suggestion[];\n activePillIndex: number;\n onSelectPill: (index: number) => void;\n}\n\nfunction getPillOpacity(index: number): number {\n if (index === 0) return 0.4;\n if (index === 1) return 0.3;\n return 0.15;\n}\n\nexport function PillList({ pills, activePillIndex, onSelectPill }: PillListProps) {\n return (\n <span className={styles.list}>\n {pills.map((pill, i) => (\n <button\n key={`${pill.type}-${pill.text}`}\n type=\"button\"\n className={`${styles.pill} ${i === activePillIndex ? styles.active : \"\"}`}\n style={{ opacity: getPillOpacity(i) }}\n onClick={() => onSelectPill(i)}\n >\n {pill.text}\n </button>\n ))}\n </span>\n );\n}\n","import {\n type ChangeEvent,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n CompletedParamState,\n Segment,\n Suggestion,\n SuggestionOption,\n UseAIAutocompleteOptions,\n UseAIAutocompleteReturn,\n} from \"../types\";\nimport { fetchSuggestions } from \"../utils/api\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nconst DEBOUNCE_MS = 100;\nconst SLOW_DEBOUNCE_MS = 300;\nconst MIN_CHARS_DIFF = 2;\n\n/**\n * Filters options using partial substring match on the text after the last completed param.\n */\nfunction filterOptions(options: SuggestionOption[], query: string): SuggestionOption[] {\n const trimmed = query.trimStart();\n if (!trimmed) return options;\n const lower = trimmed.toLowerCase();\n return options.filter((o) => !o.is_tappable || o.text.toLowerCase().includes(lower));\n}\n\n/**\n * Finds an exact match for the trimmed filter query against options.\n */\nfunction findExactMatch(options: SuggestionOption[], query: string): SuggestionOption | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n const lower = trimmed.toLowerCase();\n return options.find((o) => o.is_tappable && o.text.toLowerCase() === lower) ?? null;\n}\n\n/**\n * Derives segments for overlay rendering by matching completed params in text.\n */\nfunction deriveSegments(text: string, completedParams: CompletedParamState[]): Segment[] {\n const result: Segment[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n if (idx > pos) {\n result.push({ type: \"text\", value: text.slice(pos, idx) });\n }\n result.push({ type: \"completed\", value: param.text, param });\n pos = idx + param.text.length;\n }\n\n const remaining = text.slice(pos);\n if (remaining) {\n result.push({ type: \"text\", value: remaining });\n }\n\n return result;\n}\n\n/**\n * Checks which completed params still exist in the new text.\n */\nfunction reconcileParams(\n text: string,\n completedParams: CompletedParamState[],\n): { valid: CompletedParamState[]; invalid: CompletedParamState[] } {\n const valid: CompletedParamState[] = [];\n const invalid: CompletedParamState[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n invalid.push(param);\n } else {\n valid.push(param);\n pos = idx + param.text.length;\n }\n }\n\n return { valid, invalid };\n}\n\n/**\n * Applies option overrides by calling the override function with an empty query,\n * then prepending results to suggestions that match by type, deduplicating by text.\n */\nfunction applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: Record<string, (query: string) => SuggestionOption[]>,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => {\n const fn = overrides[s.type];\n if (!fn) return s;\n const extra = fn(\"\");\n if (extra.length === 0) return s;\n const existingTexts = new Set(extra.map((o) => o.text));\n const deduped = (s.options ?? []).filter((o) => !existingTexts.has(o.text));\n return { ...s, options: [...extra, ...deduped] };\n });\n}\n\nexport function useAIAutocomplete({\n onSubmit,\n optionOverrides,\n maskCompletedText,\n placeholder: customPlaceholder,\n}: UseAIAutocompleteOptions): UseAIAutocompleteReturn {\n // === Core state ===\n const [completedParams, setCompletedParams] = useState<CompletedParamState[]>([]);\n const [text, setText] = useState(\"\");\n const [suggestions, setSuggestions] = useState<Suggestion[]>([]);\n const [activeDropdownIndex, setActiveDropdownIndex] = useState(-1);\n\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [isReady, setIsReady] = useState(false);\n const fetchVersionRef = useRef(0);\n const abortRef = useRef<AbortController | null>(null);\n const lastRawQueryRef = useRef(\"\");\n const onSubmitRef = useRef(onSubmit);\n onSubmitRef.current = onSubmit;\n const optionOverridesRef = useRef(optionOverrides);\n optionOverridesRef.current = optionOverrides;\n const maskCompletedTextRef = useRef(maskCompletedText);\n maskCompletedTextRef.current = maskCompletedText;\n const textRef = useRef(text);\n textRef.current = text;\n const suggestionsRef = useRef(suggestions);\n suggestionsRef.current = suggestions;\n const filterBaseRef = useRef(0);\n const pillTappedRef = useRef(false);\n const skipNextFetchRef = useRef(false);\n const listboxId = useId();\n\n // === Fetch ===\n const doFetch = useCallback(async (rawQuery: string, completed: CompletedParamState[]) => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const version = ++fetchVersionRef.current;\n\n // Only show loading if no suggestions are currently displayed,\n // to avoid flickering the dropdown closed and reopened.\n const hasSuggestions = suggestionsRef.current.some((s) => s.type !== \"placeholder\");\n if (!hasSuggestions) setIsLoading(true);\n setError(null);\n try {\n const res = await fetchSuggestions(rawQuery, completed, {\n maskCompletedText: maskCompletedTextRef.current,\n signal: controller.signal,\n });\n\n // Only process the rest if this is still the latest request\n if (version !== fetchVersionRef.current) return;\n\n let newSuggestions = applyOptionOverrides(\n res.data.suggestions ?? [],\n optionOverridesRef.current,\n );\n setIsReady(res.data.is_ready ?? false);\n lastRawQueryRef.current = rawQuery;\n\n // If the last input segment is in_progress, the filter should include\n // everything the user typed that hasn't been completed — set filterBase\n // to the end of all completed segments in the current text.\n const input = res.data.input ?? [];\n const lastInput = input[input.length - 1];\n const currentText = textRef.current;\n if (lastInput?.state === \"in_progress\") {\n const inProgressIdx = currentText.lastIndexOf(lastInput.text);\n if (inProgressIdx !== -1) {\n filterBaseRef.current = inProgressIdx;\n } else {\n filterBaseRef.current = currentText.length;\n }\n } else {\n filterBaseRef.current = currentText.length;\n }\n\n // Check if the user already typed an exact match while waiting for the response\n const actionable = newSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n if (active) {\n const query = currentText.slice(filterBaseRef.current);\n const match = findExactMatch(active.options, query);\n if (match) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: active.type,\n text: match.text,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options,\n metadata: match.metadata,\n },\n ]);\n newSuggestions = newSuggestions.filter((s) => s !== active);\n }\n }\n\n setSuggestions(newSuggestions);\n\n setIsLoading(false);\n setActiveDropdownIndex(-1);\n } catch (err) {\n if (version === fetchVersionRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n }\n // biome-ignore lint/correctness/useExhaustiveDependencies: optionOverrides read via ref to keep doFetch stable\n }, []);\n\n // === Mount fetch ===\n useEffect(() => {\n doFetch(\"\", []);\n return () => {\n abortRef.current?.abort();\n };\n }, [doFetch]);\n\n // === Derived ===\n const segments = useMemo(() => deriveSegments(text, completedParams), [text, completedParams]);\n\n // Clamp filterBase if user backspaced\n filterBaseRef.current = Math.min(filterBaseRef.current, text.length);\n const filterQuery = text.slice(filterBaseRef.current);\n // biome-ignore lint/suspicious/noConsole: debug logging\n console.log(`[filter] base=${filterBaseRef.current} query=\"${filterQuery}\" text=\"${text}\"`);\n\n const serverPlaceholder = suggestions\n .filter((s) => s.type === \"placeholder\")\n .map((s) => s.text)\n .join(\" \");\n // Server placeholder takes priority, custom placeholder is the fallback\n const placeholderText = serverPlaceholder || customPlaceholder || \"\";\n const actionableSuggestions = suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion: Suggestion | undefined = actionableSuggestions[0];\n const overrideFn = activeSuggestion ? optionOverrides?.[activeSuggestion.type] : undefined;\n const filteredOptions = activeSuggestion\n ? overrideFn && filterQuery.trim()\n ? overrideFn(filterQuery.trim())\n : filterOptions(activeSuggestion.options ?? [], filterQuery)\n : [];\n const hasPlaceholder = placeholderText.length > 0;\n const isDropdownOpen =\n !isLoading &&\n filteredOptions.length > 0 &&\n (!!text || pillTappedRef.current || !hasPlaceholder);\n\n // === Handlers ===\n const selectOption = useCallback(\n (option: SuggestionOption) => {\n if (!activeSuggestion) return;\n\n const completed: CompletedParamState = {\n placeholder: \"\",\n type: activeSuggestion.type,\n text: option.text,\n kind: option.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options,\n metadata: option.metadata,\n };\n\n // Replace filter text + any partial word before it that the option completes.\n // Step 1: Always strip the filter text (everything after filterBase)\n const base = filterBaseRef.current;\n let prefix = text.slice(0, base);\n // Step 2: If the last word before filterBase is a partial start of the option,\n // trim it too (e.g. \"Create a campa|i\" → \"campa\" starts \"campaign\")\n if (prefix.length > 0 && !prefix.endsWith(\" \")) {\n const lastWord = prefix.split(/\\s+/).pop() ?? \"\";\n if (lastWord && option.text.toLowerCase().startsWith(lastWord.toLowerCase())) {\n prefix = prefix.slice(0, prefix.length - lastWord.length);\n }\n }\n\n const needsSpace = prefix.length > 0 && prefix[prefix.length - 1] !== \" \";\n const newText = prefix + (needsSpace ? \" \" : \"\") + option.text + \" \";\n setText(newText);\n filterBaseRef.current = newText.length;\n setCompletedParams((prev) => [...prev, completed]);\n setSuggestions((prev) => prev.filter((s) => s !== activeSuggestion));\n pillTappedRef.current = false;\n setActiveDropdownIndex(-1);\n // If there are other actionable suggestions remaining, the client already\n // knows what to show next — skip the network call the debounce would trigger.\n const remainingActionable = actionableSuggestions.length - 1;\n if (remainingActionable > 0) {\n skipNextFetchRef.current = true;\n }\n },\n [activeSuggestion, actionableSuggestions, text],\n );\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => {\n const raw = e.target.value;\n const newValue = raw.length > 0 ? raw[0].toUpperCase() + raw.slice(1) : raw;\n setText(newValue);\n pillTappedRef.current = false;\n setActiveDropdownIndex(-1);\n\n // Reconcile — check if any completed params were modified\n const { valid, invalid } = reconcileParams(newValue, completedParams);\n if (invalid.length > 0) {\n setCompletedParams(valid);\n for (const param of invalid) {\n setSuggestions((prev) => [\n {\n type: param.suggestionType,\n text: param.suggestionPlaceholder,\n required: true,\n options: param.options,\n },\n ...prev,\n ]);\n }\n }\n\n // Check exact match on the new filter query (only if no params were broken)\n if (activeSuggestion && invalid.length === 0) {\n const newFilterQuery = newValue.slice(filterBaseRef.current);\n const match = findExactMatch(activeSuggestion.options, newFilterQuery);\n if (match) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: activeSuggestion.type,\n text: match.text,\n kind: match.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options,\n metadata: match.metadata,\n },\n ]);\n setSuggestions((prev) => prev.filter((s) => s !== activeSuggestion));\n }\n }\n },\n [completedParams, activeSuggestion],\n );\n\n // === Debounced fetch on typing ===\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const slowDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hasFetchedRef = useRef(true);\n\n useEffect(() => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n\n /** Returns true if a fetch was triggered. */\n const attemptFetch = (minDiff: number): boolean => {\n // After selecting an option when other suggestions remain, skip the fetch\n if (skipNextFetchRef.current) {\n skipNextFetchRef.current = false;\n return false;\n }\n\n // Reset: if input is cleared, fetch fresh suggestions\n if (!text && completedParams.length === 0) {\n if (!hasFetchedRef.current) {\n hasFetchedRef.current = true;\n return false;\n }\n doFetch(\"\", []);\n return true;\n }\n\n // Check if current tappable options can still handle the filter\n const currentQuery = text.slice(filterBaseRef.current);\n const currentSuggestions = suggestionsRef.current;\n const actionable = currentSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n const currentFiltered = active ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o) => o.is_tappable);\n const hasExactMatch = active ? findExactMatch(active.options, currentQuery) !== null : false;\n\n // Only skip fetch if there are tappable filtered options and no exact match.\n // Non-tappable options (hints) don't count — we still need server results.\n // Skip this gate if filterQuery is empty — options are showing vacuously.\n const isInFilterZone = currentQuery.trim().length > 0;\n if (tappableFiltered.length > 0 && !hasExactMatch && isInFilterZone) return false;\n\n const { rawQuery, completedParams: updatedParams } = buildQuery(text, completedParams);\n const isDeleting = rawQuery.length < lastRawQueryRef.current.length;\n const charDiff = Math.abs(rawQuery.length - lastRawQueryRef.current.length);\n if (isDeleting || charDiff >= minDiff) {\n doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\n };\n\n // Fast debounce: fires at 100ms, requires >= 2 char diff\n // If it fetches, cancel the slow timer since the request is already made.\n debounceRef.current = setTimeout(() => {\n if (attemptFetch(MIN_CHARS_DIFF)) {\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n }\n }, DEBOUNCE_MS);\n\n // Slow debounce: fires at 300ms, requires >= 1 char diff (catches single-char pauses)\n slowDebounceRef.current = setTimeout(() => attemptFetch(1), SLOW_DEBOUNCE_MS);\n\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n };\n }, [text, completedParams, doFetch]);\n\n const getTappableIndices = useCallback(() => {\n const cols = 2;\n const tappable = filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n // Reorder to column-first: all left column items, then all right column items\n const left = tappable.filter((i) => i % cols === 0);\n const right = tappable.filter((i) => i % cols === 1);\n return [...left, ...right];\n }, [filteredOptions]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n const tappableIndices = getTappableIndices();\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n const currentPos = tappableIndices.indexOf(activeDropdownIndex);\n const nextPos = currentPos < tappableIndices.length - 1 ? currentPos + 1 : 0;\n setActiveDropdownIndex(tappableIndices[nextPos]);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n const currentPos = tappableIndices.indexOf(activeDropdownIndex);\n const prevPos = currentPos > 0 ? currentPos - 1 : tappableIndices.length - 1;\n setActiveDropdownIndex(tappableIndices[prevPos]);\n break;\n }\n case \"ArrowRight\": {\n if (activeDropdownIndex < 0) break;\n const cols = 2;\n if (activeDropdownIndex % cols === 0) {\n const rightNeighbor = activeDropdownIndex + 1;\n if (\n rightNeighbor < filteredOptions.length &&\n filteredOptions[rightNeighbor]?.is_tappable\n ) {\n e.preventDefault();\n setActiveDropdownIndex(rightNeighbor);\n }\n }\n break;\n }\n case \"ArrowLeft\": {\n if (activeDropdownIndex < 0) break;\n const cols = 2;\n if (activeDropdownIndex % cols === 1) {\n const leftNeighbor = activeDropdownIndex - 1;\n if (leftNeighbor >= 0 && filteredOptions[leftNeighbor]?.is_tappable) {\n e.preventDefault();\n setActiveDropdownIndex(leftNeighbor);\n }\n }\n break;\n }\n case \"Enter\": {\n e.preventDefault();\n if (activeDropdownIndex >= 0 && filteredOptions[activeDropdownIndex]?.is_tappable) {\n selectOption(filteredOptions[activeDropdownIndex]);\n } else if (onSubmitRef.current) {\n onSubmitRef.current();\n }\n break;\n }\n case \"Tab\": {\n if (activeDropdownIndex >= 0 && filteredOptions[activeDropdownIndex]?.is_tappable) {\n e.preventDefault();\n selectOption(filteredOptions[activeDropdownIndex]);\n } else if (isDropdownOpen) {\n const firstTappable = filteredOptions.find((o) => o.is_tappable);\n if (firstTappable) {\n e.preventDefault();\n selectOption(firstTappable);\n }\n } else if (!text && hasPlaceholder) {\n e.preventDefault();\n const placeholderSuggestion = suggestions.find((s) => s.type === \"placeholder\");\n setText(placeholderText);\n filterBaseRef.current = placeholderText.length;\n if (placeholderSuggestion) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: placeholderSuggestion.type,\n text: placeholderText,\n kind: null,\n suggestionType: placeholderSuggestion.type,\n suggestionPlaceholder: placeholderSuggestion.text,\n options: placeholderSuggestion.options,\n },\n ]);\n setSuggestions((prev) => prev.filter((s) => s !== placeholderSuggestion));\n }\n }\n break;\n }\n case \"Escape\":\n setActiveDropdownIndex(-1);\n break;\n }\n },\n [\n activeDropdownIndex,\n filteredOptions,\n hasPlaceholder,\n isDropdownOpen,\n placeholderText,\n selectOption,\n suggestions,\n getTappableIndices,\n text,\n ],\n );\n\n const setActivePill = useCallback(\n (index: number) => {\n const actionable = suggestions.filter((s) => s.type !== \"placeholder\");\n if (index < 0 || index >= actionable.length) return;\n const moved = actionable[index];\n const rest = actionable.filter((_, i) => i !== index);\n const placeholders = suggestions.filter((s) => s.type === \"placeholder\");\n setSuggestions([...placeholders, moved, ...rest]);\n pillTappedRef.current = true;\n setActiveDropdownIndex(-1);\n },\n [suggestions],\n );\n\n const removeLastParam = useCallback(() => {\n if (completedParams.length === 0) return;\n const lastParam = completedParams[completedParams.length - 1];\n const restoredSuggestion: Suggestion = {\n type: lastParam.suggestionType,\n text: lastParam.suggestionPlaceholder,\n required: true,\n options: lastParam.options,\n };\n setCompletedParams((prev) => prev.slice(0, -1));\n setSuggestions((prev) => [restoredSuggestion, ...prev]);\n setActiveDropdownIndex(-1);\n }, [completedParams]);\n\n const reEditParam = useCallback((param: CompletedParamState) => {\n const restoredSuggestion: Suggestion = {\n type: param.suggestionType,\n text: param.suggestionPlaceholder,\n required: true,\n options: param.options,\n };\n setText((prev) => {\n const idx = prev.indexOf(param.text);\n if (idx === -1) return prev;\n const before = prev.slice(0, idx);\n const after = prev.slice(idx + param.text.length);\n const cleaned = (before + after).replace(/ {2,}/g, \" \");\n filterBaseRef.current = Math.min(filterBaseRef.current, cleaned.length);\n return cleaned;\n });\n setCompletedParams((prev) => prev.filter((p) => p !== param));\n setSuggestions((prev) => [restoredSuggestion, ...prev]);\n setActiveDropdownIndex(-1);\n pillTappedRef.current = true;\n }, []);\n\n const reset = useCallback(() => {\n setText(\"\");\n setCompletedParams([]);\n setSuggestions([]);\n setActiveDropdownIndex(-1);\n setIsReady(false);\n filterBaseRef.current = 0;\n lastRawQueryRef.current = \"\";\n doFetch(\"\", []);\n }, [doFetch]);\n\n const activeDescendantId =\n activeDropdownIndex >= 0 ? `${listboxId}-option-${activeDropdownIndex}` : undefined;\n\n return {\n completedParams,\n suggestionPills: actionableSuggestions,\n activePillIndex: 0,\n setActivePill,\n removeLastParam,\n reEditParam,\n segments,\n suggestions,\n activeIndex: activeDropdownIndex,\n isReady,\n isLoading,\n error,\n inputProps: {\n value: text,\n placeholder: placeholderText || undefined,\n onChange: handleChange,\n onKeyDown: handleKeyDown,\n role: \"combobox\" as const,\n \"aria-expanded\": isDropdownOpen,\n \"aria-activedescendant\": activeDescendantId,\n \"aria-autocomplete\": \"list\" as const,\n \"aria-controls\": listboxId,\n },\n reset,\n dropdownProps: {\n suggestions: activeSuggestion ? [{ ...activeSuggestion, options: filteredOptions }] : [],\n activeIndex: activeDropdownIndex,\n onSelect: selectOption,\n isOpen: isDropdownOpen,\n id: listboxId,\n },\n };\n}\n","import type {\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n} from \"../types\";\n\nconst SDK_VERSION = \"0.1.0\";\n\n// process.env.* values are replaced at build time by the bundler's `define` config.\n// Use optional chaining (process?.env), NOT `typeof process !== \"undefined\"` guards —\n// the typeof guard prevents the replacement from taking effect in browsers.\ndeclare const process: { env: Record<string, string | undefined> } | undefined;\nconst API_ENDPOINT = process?.env.MAGICX_API_ENDPOINT || \"/api/suggest\";\n\nlet hasWarnedMissingKey = false;\n\nfunction getApiKey(): string {\n const key = process?.env.MAGICX_AI_AUTOCOMPLETE_API_KEY || \"\";\n if (!key && !hasWarnedMissingKey) {\n hasWarnedMissingKey = true;\n // biome-ignore lint/suspicious/noConsole: intentional dev warning\n console.warn(\n \"[AIAutocomplete] No API key set (MAGICX_AI_AUTOCOMPLETE_API_KEY). \" +\n \"Requests will be sent without an Authorization header.\",\n );\n }\n return key;\n}\n\nfunction getAuthScheme(): \"Bearer\" | \"Basic\" {\n const scheme = process?.env.MAGICX_AUTH_SCHEME;\n return scheme === \"Basic\" ? \"Basic\" : \"Bearer\";\n}\n\nfunction generateRequestId(): string {\n return crypto.randomUUID();\n}\n\nfunction toWireParam(param: CompletedParamState, includeText: boolean): CompletedParam {\n return {\n placeholder: param.placeholder,\n type: param.type,\n ...(includeText && { text: param.text }),\n kind: param.kind,\n };\n}\n\nexport async function fetchSuggestions(\n rawQuery: string,\n completedParams: CompletedParamState[],\n options?: { maskCompletedText?: boolean; signal?: AbortSignal },\n): Promise<AutocompleteResponse> {\n const apiKey = getApiKey();\n const authScheme = getAuthScheme();\n const includeText = !options?.maskCompletedText;\n\n const rawCount = completedParams.find(\n (p) => p.type === \"contact\" && p.metadata?.contact_account_count,\n )?.metadata?.contact_account_count;\n const contactAccountCount = typeof rawCount === \"number\" ? rawCount : undefined;\n\n const body: AutocompleteRequest = {\n data: {\n raw_query: rawQuery,\n completed_params: completedParams.map((p) => toWireParam(p, includeText)),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n },\n meta: {\n request_id: generateRequestId(),\n request_at: new Date().toISOString(),\n language: typeof navigator !== \"undefined\" ? navigator.language : \"en-US\",\n client_version: SDK_VERSION,\n },\n };\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-App-Identifier\": process?.env.MAGICX_APP_IDENTIFIER || \"active-campaign-demo\",\n };\n if (apiKey) {\n headers.Authorization = authScheme === \"Basic\" ? `Basic ${btoa(apiKey)}` : `Bearer ${apiKey}`;\n }\n\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: options?.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n}\n","import type { CompletedParamState } from \"../types\";\n\ninterface BuildQueryResult {\n rawQuery: string;\n completedParams: CompletedParamState[];\n}\n\n/**\n * Takes the raw input text and completed params (without placeholders),\n * replaces each completed param's text in the string with a {{TYPE_N}} token,\n * and returns the transformed query + params with placeholders filled in.\n *\n * Replacements happen left-to-right, first occurrence only per param.\n * Counter is per-type (e.g. {{TASK_1}}, {{GOAL_1}}, {{GOAL_2}}).\n */\nexport function buildQuery(text: string, completedParams: CompletedParamState[]): BuildQueryResult {\n let result = text;\n const typeCounts: Record<string, number> = {};\n const updatedParams: CompletedParamState[] = [];\n\n for (const param of completedParams) {\n const count = (typeCounts[param.type] ?? 0) + 1;\n typeCounts[param.type] = count;\n\n const typeKey = param.type.toUpperCase().replace(/\\s+/g, \"_\");\n const placeholder = `{{${typeKey}_${count}}}`;\n\n // Replace first occurrence of the param text in the string\n const index = result.indexOf(param.text);\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n }\n\n updatedParams.push({ ...param, placeholder });\n }\n\n return { rawQuery: result, completedParams: updatedParams };\n}\n"],"mappings":"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","names":["index_exports","__export","AIAutocomplete","AIAutocompleteDropdown","useAIAutocomplete","__toCommonJS","import_react","AIAutocomplete_default","AIAutocompleteDropdown_default","SuggestionGrid_default","SuggestionItem_default","import_jsx_runtime","SuggestionItem","option","isHighlighted","onSelect","onHighlight","id","className","SuggestionItem_default","e","import_jsx_runtime","SuggestionGrid","options","activeIndex","onSelect","onHighlight","listboxId","SuggestionGrid_default","option","i","SuggestionItem","import_jsx_runtime","AIAutocompleteDropdown","suggestions","activeIndex","onSelect","isOpen","id","className","activeSuggestion","AIAutocompleteDropdown_default","e","SuggestionGrid","PillList_default","import_jsx_runtime","getPillOpacity","index","PillList","pills","activePillIndex","onSelectPill","PillList_default","pill","i","import_react","SDK_VERSION","API_ENDPOINT","hasWarnedMissingKey","getApiKey","key","getAuthScheme","generateRequestId","toWireParam","param","includeText","fetchSuggestions","rawQuery","completedParams","options","apiKey","authScheme","rawCount","p","contactAccountCount","body","headers","response","buildQuery","text","completedParams","result","typeCounts","updatedParams","param","count","placeholder","index","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","filterOptions","options","query","trimmed","lower","o","findExactMatch","deriveSegments","text","completedParams","result","pos","param","idx","remaining","reconcileParams","valid","invalid","applyOptionOverrides","suggestions","overrides","s","fn","extra","existingTexts","deduped","useAIAutocomplete","onSubmit","optionOverrides","maskCompletedText","customPlaceholder","setCompletedParams","setText","setSuggestions","activeDropdownIndex","setActiveDropdownIndex","isLoading","setIsLoading","error","setError","isReady","setIsReady","fetchVersionRef","abortRef","lastRawQueryRef","onSubmitRef","optionOverridesRef","maskCompletedTextRef","textRef","suggestionsRef","filterBaseRef","pillTappedRef","skipNextFetchRef","listboxId","doFetch","rawQuery","completed","controller","version","res","fetchSuggestions","newSuggestions","input","lastInput","currentText","inProgressIdx","active","match","prev","err","segments","filterQuery","placeholderText","actionableSuggestions","activeSuggestion","overrideFn","filteredOptions","hasPlaceholder","isDropdownOpen","selectOption","option","base","prefix","lastWord","needsSpace","newText","handleChange","e","raw","newValue","newFilterQuery","debounceRef","slowDebounceRef","hasFetchedRef","attemptFetch","minDiff","currentQuery","tappableFiltered","hasExactMatch","isInFilterZone","updatedParams","buildQuery","isDeleting","charDiff","getTappableIndices","tappable","i","left","right","handleKeyDown","tappableIndices","currentPos","nextPos","prevPos","rightNeighbor","leftNeighbor","firstTappable","placeholderSuggestion","setActivePill","index","actionable","moved","rest","_","placeholders","removeLastParam","lastParam","restoredSuggestion","reEditParam","before","after","cleaned","p","reset","activeDescendantId","import_jsx_runtime","AIAutocomplete","onSubmit","optionOverrides","maskCompletedText","placeholder","className","textareaRef","showCheckmark","setShowCheckmark","handleSubmitRef","completedParams","suggestionPills","activePillIndex","setActivePill","segments","inputProps","dropdownProps","reset","useAIAutocomplete","handleContainerClick","canSubmit","handleSubmit","rawQuery","finalParams","buildQuery","onChange","inputPlaceholder","restProps","isEmpty","AIAutocomplete_default","AIAutocompleteDropdown","seg","i","PillList","e"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useCallback as at,useEffect as it,useRef as _e,useState as lt}from"react";var w={};var xe={};var ye={};var X={};import{jsx as Fe,jsxs as Ke}from"react/jsx-runtime";function be({option:e,isHighlighted:c,onSelect:a,onHighlight:l,id:o}){let i=[X.item,c?X.highlighted:"",e.is_tappable?X.tappable:X.nonTappable].filter(Boolean).join(" ");return Ke("div",{id:o,role:"option","aria-selected":c,className:i,tabIndex:e.is_tappable?0:-1,onClick:()=>e.is_tappable&&a(e),onKeyDown:n=>{e.is_tappable&&(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),a(e))},onMouseEnter:l,children:[e.icon?`${e.icon} ${e.text}`:e.text,e.tag&&Fe("span",{className:X.tag,children:e.tag})]})}import{jsx as ve}from"react/jsx-runtime";function Se({options:e,activeIndex:c,onSelect:a,onHighlight:l,listboxId:o}){return ve("div",{className:ye.grid,children:e.map((i,n)=>ve(be,{option:i,isHighlighted:n===c,onSelect:a,onHighlight:()=>l(n),id:`${o}-option-${n}`},i.text))})}import{jsx as Pe}from"react/jsx-runtime";function ue({suggestions:e,activeIndex:c,onSelect:a,isOpen:l,id:o,className:i}){if(!l||e.length===0)return null;let n=e[0];return n.options.length===0?null:Pe("div",{id:o,role:"listbox",className:`${xe.dropdown} ${i??""}`,onMouseDown:x=>x.preventDefault(),children:Pe(Se,{options:n.options,activeIndex:c,onSelect:a,onHighlight:()=>{},listboxId:o})})}var ae={};import{jsx as ke}from"react/jsx-runtime";function He(e){return e===0?.4:e===1?.3:.15}function we({pills:e,activePillIndex:c,onSelectPill:a}){return ke("span",{className:ae.list,children:e.map((l,o)=>ke("button",{type:"button",className:`${ae.pill} ${o===c?ae.active:""}`,style:{opacity:He(o)},onClick:()=>a(o),children:l.text},`${l.type}-${l.text}`))})}import{useCallback as T,useEffect as Ce,useId as Je,useMemo as Ze,useRef as P,useState as $}from"react";var Xe="0.1.0",We=process?.env.MAGICX_API_ENDPOINT||"/api/suggest",Ae=!1;function Ge(){let e=process?.env.MAGICX_AI_AUTOCOMPLETE_API_KEY||"";return!e&&!Ae&&(Ae=!0,console.warn("[AIAutocomplete] No API key set (MAGICX_AI_AUTOCOMPLETE_API_KEY). Requests will be sent without an Authorization header.")),e}function Ye(){return process?.env.MAGICX_AUTH_SCHEME==="Basic"?"Basic":"Bearer"}function Ve(){return crypto.randomUUID()}function je(e,c){return{placeholder:e.placeholder,type:e.type,...c&&{text:e.text},kind:e.kind}}async function Ie(e,c,a){let l=Ge(),o=Ye(),i=!a?.maskCompletedText,n=c.find(h=>h.type==="contact"&&h.metadata?.contact_account_count)?.metadata?.contact_account_count,x=typeof n=="number"?n:void 0,y={data:{raw_query:e,completed_params:c.map(h=>je(h,i)),...x!=null&&{contact_account_count:x}},meta:{request_id:Ve(),request_at:new Date().toISOString(),language:typeof navigator<"u"?navigator.language:"en-US",client_version:Xe}},g={"Content-Type":"application/json","X-App-Identifier":process?.env.MAGICX_APP_IDENTIFIER||"active-campaign-demo"};l&&(g.Authorization=o==="Basic"?`Basic ${btoa(l)}`:`Bearer ${l}`);let d=await fetch(We,{method:"POST",headers:g,body:JSON.stringify(y),signal:a?.signal});if(!d.ok)throw new Error(`API error: ${d.status} ${d.statusText}`);return d.json()}function ie(e,c){let a=e,l={},o=[];for(let i of c){let n=(l[i.type]??0)+1;l[i.type]=n;let y=`{{${i.type.toUpperCase().replace(/\s+/g,"_")}_${n}}}`,g=a.indexOf(i.text);g!==-1&&(a=a.slice(0,g)+y+a.slice(g+i.text.length)),o.push({...i,placeholder:y})}return{rawQuery:a,completedParams:o}}var et=100,tt=300,ot=2;function Te(e,c){let a=c.trimStart();if(!a)return e;let l=a.toLowerCase();return e.filter(o=>!o.is_tappable||o.text.toLowerCase().includes(l))}function de(e,c){let a=c.trim();if(!a)return null;let l=a.toLowerCase();return e.find(o=>o.is_tappable&&o.text.toLowerCase()===l)??null}function nt(e,c){let a=[],l=0;for(let i of c){let n=e.indexOf(i.text,l);n!==-1&&(n>l&&a.push({type:"text",value:e.slice(l,n)}),a.push({type:"completed",value:i.text,param:i}),l=n+i.text.length)}let o=e.slice(l);return o&&a.push({type:"text",value:o}),a}function rt(e,c){let a=[],l=[],o=0;for(let i of c){let n=e.indexOf(i.text,o);n===-1?l.push(i):(a.push(i),o=n+i.text.length)}return{valid:a,invalid:l}}function st(e,c){return c?e.map(a=>{let l=c[a.type];if(!l)return a;let o=l("");if(o.length===0)return a;let i=new Set(o.map(x=>x.text)),n=(a.options??[]).filter(x=>!i.has(x.text));return{...a,options:[...o,...n]}}):e}function fe({onSubmit:e,optionOverrides:c,maskCompletedText:a,placeholder:l}){let[o,i]=$([]),[n,x]=$(""),[y,g]=$([]),[d,h]=$(-1),[V,U]=$(!1),[_,j]=$(null),[J,Z]=$(!1),E=P(0),q=P(null),z=P(""),F=P(e);F.current=e;let ee=P(c);ee.current=c;let te=P(a);te.current=a;let I=P(n);I.current=n;let D=P(y);D.current=y;let v=P(0),W=P(!1),le=P(!1),ce=Je(),N=T(async(r,u)=>{q.current?.abort();let t=new AbortController;q.current=t;let s=++E.current;D.current.some(p=>p.type!=="placeholder")||U(!0),j(null);try{let p=await Ie(r,u,{maskCompletedText:te.current,signal:t.signal});if(s!==E.current)return;let b=st(p.data.suggestions??[],ee.current);Z(p.data.is_ready??!1),z.current=r;let S=p.data.input??[],O=S[S.length-1],Q=I.current;if(O?.state==="in_progress"){let R=Q.lastIndexOf(O.text);R!==-1?v.current=R:v.current=Q.length}else v.current=Q.length;let C=b.filter(R=>R.type!=="placeholder")[0];if(C){let R=Q.slice(v.current),H=de(C.options,R);H&&(i(L=>[...L,{placeholder:"",type:C.type,text:H.text,kind:H.kind,suggestionType:C.type,suggestionPlaceholder:C.text,options:C.options,metadata:H.metadata}]),b=b.filter(L=>L!==C))}g(b),U(!1),h(-1)}catch(p){s===E.current&&(j(p instanceof Error?p:new Error(String(p))),U(!1))}},[]);Ce(()=>(N("",[]),()=>{q.current?.abort()}),[N]);let Oe=Ze(()=>nt(n,o),[n,o]);v.current=Math.min(v.current,n.length);let oe=n.slice(v.current);console.log(`[filter] base=${v.current} query="${oe}" text="${n}"`);let K=y.filter(r=>r.type==="placeholder").map(r=>r.text).join(" ")||l||"",ne=y.filter(r=>r.type!=="placeholder"),f=ne[0],me=f?c?.[f.type]:void 0,k=f?me&&oe.trim()?me(oe.trim()):Te(f.options??[],oe):[],pe=K.length>0,re=!V&&k.length>0&&(!!n||W.current||!pe),G=T(r=>{if(!f)return;let u={placeholder:"",type:f.type,text:r.text,kind:r.kind,suggestionType:f.type,suggestionPlaceholder:f.text,options:f.options,metadata:r.metadata},t=v.current,s=n.slice(0,t);if(s.length>0&&!s.endsWith(" ")){let S=s.split(/\s+/).pop()??"";S&&r.text.toLowerCase().startsWith(S.toLowerCase())&&(s=s.slice(0,s.length-S.length))}let m=s.length>0&&s[s.length-1]!==" ",p=s+(m?" ":"")+r.text+" ";x(p),v.current=p.length,i(S=>[...S,u]),g(S=>S.filter(O=>O!==f)),W.current=!1,h(-1),ne.length-1>0&&(le.current=!0)},[f,ne,n]),Re=T(r=>{let u=r.target.value,t=u.length>0?u[0].toUpperCase()+u.slice(1):u;x(t),W.current=!1,h(-1);let{valid:s,invalid:m}=rt(t,o);if(m.length>0){i(s);for(let p of m)g(b=>[{type:p.suggestionType,text:p.suggestionPlaceholder,required:!0,options:p.options},...b])}if(f&&m.length===0){let p=t.slice(v.current),b=de(f.options,p);b&&(i(S=>[...S,{placeholder:"",type:f.type,text:b.text,kind:b.kind,suggestionType:f.type,suggestionPlaceholder:f.text,options:f.options,metadata:b.metadata}]),g(S=>S.filter(O=>O!==f)))}},[o,f]),Y=P(null),M=P(null),ge=P(!0);Ce(()=>{Y.current&&clearTimeout(Y.current),M.current&&clearTimeout(M.current);let r=u=>{if(le.current)return le.current=!1,!1;if(!n&&o.length===0)return ge.current?(N("",[]),!0):(ge.current=!0,!1);let t=n.slice(v.current),p=D.current.filter(L=>L.type!=="placeholder")[0],S=(p?Te(p.options,t):[]).filter(L=>L.is_tappable),O=p?de(p.options,t)!==null:!1,Q=t.trim().length>0;if(S.length>0&&!O&&Q)return!1;let{rawQuery:se,completedParams:C}=ie(n,o),R=se.length<z.current.length,H=Math.abs(se.length-z.current.length);return R||H>=u?(N(se,C),!0):!1};return Y.current=setTimeout(()=>{r(ot)&&M.current&&clearTimeout(M.current)},et),M.current=setTimeout(()=>r(1),tt),()=>{Y.current&&clearTimeout(Y.current),M.current&&clearTimeout(M.current)}},[n,o,N]);let he=T(()=>{let u=k.map((m,p)=>m.is_tappable?p:-1).filter(m=>m!==-1),t=u.filter(m=>m%2===0),s=u.filter(m=>m%2===1);return[...t,...s]},[k]),Ee=T(r=>{let u=he();switch(r.key){case"ArrowDown":{if(r.preventDefault(),u.length===0)return;let t=u.indexOf(d),s=t<u.length-1?t+1:0;h(u[s]);break}case"ArrowUp":{if(r.preventDefault(),u.length===0)return;let t=u.indexOf(d),s=t>0?t-1:u.length-1;h(u[s]);break}case"ArrowRight":{if(d<0)break;if(d%2===0){let s=d+1;s<k.length&&k[s]?.is_tappable&&(r.preventDefault(),h(s))}break}case"ArrowLeft":{if(d<0)break;if(d%2===1){let s=d-1;s>=0&&k[s]?.is_tappable&&(r.preventDefault(),h(s))}break}case"Enter":{r.preventDefault(),d>=0&&k[d]?.is_tappable?G(k[d]):F.current&&F.current();break}case"Tab":{if(d>=0&&k[d]?.is_tappable)r.preventDefault(),G(k[d]);else if(re){let t=k.find(s=>s.is_tappable);t&&(r.preventDefault(),G(t))}else if(!n&&pe){r.preventDefault();let t=y.find(s=>s.type==="placeholder");x(K),v.current=K.length,t&&(i(s=>[...s,{placeholder:"",type:t.type,text:K,kind:null,suggestionType:t.type,suggestionPlaceholder:t.text,options:t.options}]),g(s=>s.filter(m=>m!==t)))}break}case"Escape":h(-1);break}},[d,k,pe,re,K,G,y,he,n]),De=T(r=>{let u=y.filter(p=>p.type!=="placeholder");if(r<0||r>=u.length)return;let t=u[r],s=u.filter((p,b)=>b!==r),m=y.filter(p=>p.type==="placeholder");g([...m,t,...s]),W.current=!0,h(-1)},[y]),Ne=T(()=>{if(o.length===0)return;let r=o[o.length-1],u={type:r.suggestionType,text:r.suggestionPlaceholder,required:!0,options:r.options};i(t=>t.slice(0,-1)),g(t=>[u,...t]),h(-1)},[o]),Me=T(r=>{let u={type:r.suggestionType,text:r.suggestionPlaceholder,required:!0,options:r.options};x(t=>{let s=t.indexOf(r.text);if(s===-1)return t;let m=t.slice(0,s),p=t.slice(s+r.text.length),b=(m+p).replace(/ {2,}/g," ");return v.current=Math.min(v.current,b.length),b}),i(t=>t.filter(s=>s!==r)),g(t=>[u,...t]),h(-1),W.current=!0},[]),Le=T(()=>{x(""),i([]),g([]),h(-1),Z(!1),v.current=0,z.current="",N("",[])},[N]),$e=d>=0?`${ce}-option-${d}`:void 0;return{completedParams:o,suggestionPills:ne,activePillIndex:0,setActivePill:De,removeLastParam:Ne,reEditParam:Me,segments:Oe,suggestions:y,activeIndex:d,isReady:J,isLoading:V,error:_,inputProps:{value:n,placeholder:K||void 0,onChange:Re,onKeyDown:Ee,role:"combobox","aria-expanded":re,"aria-activedescendant":$e,"aria-autocomplete":"list","aria-controls":ce},reset:Le,dropdownProps:{suggestions:f?[{...f,options:k}]:[],activeIndex:d,onSelect:G,isOpen:re,id:ce}}}import{jsx as A,jsxs as B}from"react/jsx-runtime";function ct({onSubmit:e,optionOverrides:c,maskCompletedText:a,placeholder:l,className:o}){let i=_e(null),[n,x]=lt(!1),y=_e(()=>{});it(()=>{i.current?.focus()},[]);let{completedParams:g,suggestionPills:d,activePillIndex:h,setActivePill:V,segments:U,inputProps:_,dropdownProps:j,reset:J}=fe({onSubmit:()=>y.current(),optionOverrides:c,maskCompletedText:a,placeholder:l}),Z=()=>{i.current?.focus()},E=!!_.value||g.length>0,q=at(()=>{if(!E)return;let{rawQuery:I,completedParams:D}=ie(_.value,g);e({query:_.value.trim(),raw_query:I,completed_params:D}),J(),x(!0),setTimeout(()=>x(!1),3e3)},[E,_.value,g,e,J]);y.current=q;let{onChange:z,placeholder:F,...ee}=_,te=!_.value;return B("div",{className:`${w.container} ${o??""}`,children:[A("div",{className:`${w.checkmark} ${n?w.checkmarkVisible:""}`,children:B("svg",{width:"72",height:"72",viewBox:"0 0 24 24",fill:"none",role:"img","aria-label":"Success",children:[A("circle",{cx:"12",cy:"12",r:"12",fill:"#34C759"}),A("path",{d:"M7 12.5l3.5 3.5L17 9",stroke:"#000",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",className:w.checkmarkPath})]})}),A(ue,{...j}),B("div",{className:w.inputWrapper,onClick:Z,children:[B("div",{className:w.editorArea,children:[B("div",{className:w.sizerContent,"aria-hidden":"true",children:[te&&F?B("span",{className:w.placeholderText,children:[F," "]}):B("span",{className:w.sizerText,children:[U.map((I,D)=>A("span",{children:I.value},`${D}-${I.type}`)),U.length===0&&"\xA0"]})," ",A(we,{pills:d,activePillIndex:h,onSelectPill:V})]}),A("textarea",{ref:i,className:w.textarea,rows:1,onChange:z,...ee})]}),A("button",{type:"button",className:w.submitButton,disabled:!E,onClick:I=>{I.stopPropagation(),q()},"aria-label":"Submit",children:A("svg",{width:"18",height:"18",viewBox:"0 0 18 18",fill:"none",role:"img","aria-label":"Submit",children:A("path",{d:"M9 14V4M9 4L4 9M9 4L14 9",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})})]})]})}export{ct as AIAutocomplete,ue as AIAutocompleteDropdown,fe as useAIAutocomplete};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/AIAutocomplete.tsx","../src/AIAutocomplete.module.css","../src/AIAutocompleteDropdown.module.css","../src/components/SuggestionGrid.module.css","../src/components/SuggestionItem.module.css","../src/components/SuggestionItem.tsx","../src/components/SuggestionGrid.tsx","../src/AIAutocompleteDropdown.tsx","../src/components/PillList.module.css","../src/components/PillList.tsx","../src/hooks/useAIAutocomplete.ts","../src/utils/api.ts","../src/utils/buildQuery.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport styles from \"./AIAutocomplete.module.css\";\nimport { AIAutocompleteDropdown } from \"./AIAutocompleteDropdown\";\nimport { PillList } from \"./components/PillList\";\nimport { useAIAutocomplete } from \"./hooks/useAIAutocomplete\";\nimport type { AIAutocompleteProps } from \"./types\";\nimport { buildQuery } from \"./utils/buildQuery\";\n\nexport function AIAutocomplete({\n onSubmit,\n optionOverrides,\n maskCompletedText,\n placeholder,\n className,\n}: AIAutocompleteProps) {\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [showCheckmark, setShowCheckmark] = useState(false);\n const handleSubmitRef = useRef<() => void>(() => {});\n\n useEffect(() => {\n textareaRef.current?.focus();\n }, []);\n\n const {\n completedParams,\n suggestionPills,\n activePillIndex,\n setActivePill,\n segments,\n inputProps,\n dropdownProps,\n reset,\n } = useAIAutocomplete({\n onSubmit: () => handleSubmitRef.current(),\n optionOverrides,\n maskCompletedText,\n placeholder,\n });\n\n const handleContainerClick = () => {\n textareaRef.current?.focus();\n };\n\n const canSubmit = !!inputProps.value || completedParams.length > 0;\n\n const handleSubmit = useCallback(() => {\n if (!canSubmit) return;\n const { rawQuery, completedParams: finalParams } = buildQuery(\n inputProps.value,\n completedParams,\n );\n onSubmit({\n query: inputProps.value.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n reset();\n setShowCheckmark(true);\n setTimeout(() => setShowCheckmark(false), 3000);\n }, [canSubmit, inputProps.value, completedParams, onSubmit, reset]);\n\n handleSubmitRef.current = handleSubmit;\n\n const { onChange, placeholder: inputPlaceholder, ...restProps } = inputProps;\n const isEmpty = !inputProps.value;\n\n return (\n <div className={`${styles.container} ${className ?? \"\"}`}>\n <div className={`${styles.checkmark} ${showCheckmark ? styles.checkmarkVisible : \"\"}`}>\n <svg width=\"72\" height=\"72\" viewBox=\"0 0 24 24\" fill=\"none\" role=\"img\" aria-label=\"Success\">\n <circle cx=\"12\" cy=\"12\" r=\"12\" fill=\"#34C759\" />\n <path\n d=\"M7 12.5l3.5 3.5L17 9\"\n stroke=\"#000\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={styles.checkmarkPath}\n />\n </svg>\n </div>\n <AIAutocompleteDropdown {...dropdownProps} />\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: container click delegates to textarea focus */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: wrapper delegates focus to textarea */}\n <div className={styles.inputWrapper} onClick={handleContainerClick}>\n <div className={styles.editorArea}>\n <div className={styles.sizerContent} aria-hidden=\"true\">\n {isEmpty && inputPlaceholder ? (\n <span className={styles.placeholderText}>{inputPlaceholder} </span>\n ) : (\n <span className={styles.sizerText}>\n {segments.map((seg, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: segments are positional and don't reorder\n <span key={`${i}-${seg.type}`}>{seg.value}</span>\n ))}\n {segments.length === 0 && \"\\u00A0\"}\n </span>\n )}\n {\" \"}\n <PillList\n pills={suggestionPills}\n activePillIndex={activePillIndex}\n onSelectPill={setActivePill}\n />\n </div>\n <textarea\n ref={textareaRef}\n className={styles.textarea}\n rows={1}\n onChange={onChange}\n {...restProps}\n />\n </div>\n <button\n type=\"button\"\n className={styles.submitButton}\n disabled={!canSubmit}\n onClick={(e) => {\n e.stopPropagation();\n handleSubmit();\n }}\n aria-label=\"Submit\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n role=\"img\"\n aria-label=\"Submit\"\n >\n <path\n d=\"M9 14V4M9 4L4 9M9 4L14 9\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n );\n}\n",".container {\n position: relative;\n font-family: \"IBM Plex Sans\", sans-serif;\n}\n\n.checkmark {\n position: absolute;\n bottom: -130px;\n left: 50%;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n opacity: 0;\n pointer-events: none;\n z-index: 10;\n animation: none;\n}\n\n.checkmarkVisible {\n animation: checkmarkFadeInOut 3s ease forwards;\n}\n\n@keyframes checkmarkFadeInOut {\n 0% {\n opacity: 0;\n transform: translateX(-50%) translateY(8px) scale(0.8);\n }\n 10% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 80% {\n opacity: 1;\n transform: translateX(-50%) translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateX(-50%) translateY(-8px) scale(0.8);\n }\n}\n\n.checkmarkPath {\n stroke-dasharray: 30;\n stroke-dashoffset: 30;\n}\n\n.checkmarkVisible .checkmarkPath {\n animation: drawCheck 0.4s ease forwards 0.1s;\n}\n\n@keyframes drawCheck {\n to {\n stroke-dashoffset: 0;\n }\n}\n\n.inputWrapper {\n min-height: 60px;\n padding: 24px;\n border: 1px solid var(--color-border-default, #9ea5b2);\n border-radius: 23px;\n background: transparent;\n overflow: hidden;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.editorArea {\n position: relative;\n flex: 1;\n min-width: 0;\n}\n\n.sizerContent {\n position: relative;\n z-index: 1;\n pointer-events: none;\n min-height: 60px;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n}\n\n.sizerText {\n color: transparent;\n}\n\n.placeholderText {\n color: var(--color-text-muted, #c1c4cb);\n opacity: 0.7;\n}\n\n.textarea {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--color-text-default, #fff);\n caret-color: var(--color-text-default, #fff);\n font-family: inherit;\n font-size: 21px;\n line-height: 38px;\n white-space: pre-wrap;\n word-break: break-word;\n outline: none;\n resize: none;\n overflow: hidden;\n}\n\n.textarea::placeholder {\n color: var(--color-text-muted, #c1c4cb);\n}\n\n.submitButton {\n flex-shrink: 0;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: var(--color-text-default, #fff);\n color: var(--color-bg-default, #000);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: opacity 0.2s ease;\n}\n\n.submitButton:hover {\n opacity: 0.85;\n}\n",".dropdown {\n position: absolute;\n left: 0;\n right: 0;\n top: 100%;\n margin-top: 6px;\n background: var(--color-background-default, #00002d);\n border-radius: 23px;\n overflow: hidden;\n z-index: 10;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n",".grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 18px;\n padding: 18px 24px;\n max-height: 192px;\n overflow-y: auto;\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.3) transparent;\n}\n\n.grid::-webkit-scrollbar {\n width: 6px;\n}\n\n.grid::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.grid::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 3px;\n}\n",".item {\n display: flex;\n align-items: center;\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n color: var(--color-text-muted, #c1c4cb);\n white-space: nowrap;\n opacity: 0.35;\n animation: fadeIn 200ms ease forwards;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n\n.tappable {\n cursor: pointer;\n}\n\n.tappable:hover {\n color: var(--color-text-default, #fff);\n}\n\n.nonTappable {\n cursor: default;\n opacity: 0.3;\n}\n\n.highlighted {\n color: var(--color-text-default, #fff);\n opacity: 0.5;\n}\n\n.tag {\n font-size: 13px;\n margin-left: 6px;\n opacity: 0.5;\n}\n","import type { SuggestionOption } from \"../types\";\nimport styles from \"./SuggestionItem.module.css\";\n\ninterface SuggestionItemProps {\n option: SuggestionOption;\n isHighlighted: boolean;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: () => void;\n id: string;\n}\n\nexport function SuggestionItem({\n option,\n isHighlighted,\n onSelect,\n onHighlight,\n id,\n}: SuggestionItemProps) {\n const className = [\n styles.item,\n isHighlighted ? styles.highlighted : \"\",\n option.is_tappable ? styles.tappable : styles.nonTappable,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <div\n id={id}\n role=\"option\"\n aria-selected={isHighlighted}\n className={className}\n tabIndex={option.is_tappable ? 0 : -1}\n onClick={() => option.is_tappable && onSelect(option)}\n onKeyDown={(e) => {\n if (option.is_tappable && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onSelect(option);\n }\n }}\n onMouseEnter={onHighlight}\n >\n {option.icon ? `${option.icon} ${option.text}` : option.text}\n {option.tag && <span className={styles.tag}>{option.tag}</span>}\n </div>\n );\n}\n","import type { SuggestionOption } from \"../types\";\nimport styles from \"./SuggestionGrid.module.css\";\nimport { SuggestionItem } from \"./SuggestionItem\";\n\ninterface SuggestionGridProps {\n options: SuggestionOption[];\n activeIndex: number;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n listboxId: string;\n}\n\nexport function SuggestionGrid({\n options,\n activeIndex,\n onSelect,\n onHighlight,\n listboxId,\n}: SuggestionGridProps) {\n return (\n <div className={styles.grid}>\n {options.map((option, i) => (\n <SuggestionItem\n key={option.text}\n option={option}\n isHighlighted={i === activeIndex}\n onSelect={onSelect}\n onHighlight={() => onHighlight(i)}\n id={`${listboxId}-option-${i}`}\n />\n ))}\n </div>\n );\n}\n","import styles from \"./AIAutocompleteDropdown.module.css\";\nimport { SuggestionGrid } from \"./components/SuggestionGrid\";\nimport type { AIAutocompleteDropdownProps } from \"./types\";\n\nexport function AIAutocompleteDropdown({\n suggestions,\n activeIndex,\n onSelect,\n isOpen,\n id,\n className,\n}: AIAutocompleteDropdownProps) {\n if (!isOpen || suggestions.length === 0) return null;\n\n const activeSuggestion = suggestions[0];\n if (activeSuggestion.options.length === 0) return null;\n\n return (\n // biome-ignore lint/a11y/noStaticElementInteractions: preventDefault keeps focus on textarea\n <div\n id={id}\n role=\"listbox\"\n className={`${styles.dropdown} ${className ?? \"\"}`}\n onMouseDown={(e) => e.preventDefault()}\n >\n <SuggestionGrid\n options={activeSuggestion.options}\n activeIndex={activeIndex}\n onSelect={onSelect}\n onHighlight={() => {}}\n listboxId={id}\n />\n </div>\n );\n}\n",".list {\n position: relative;\n z-index: 1;\n pointer-events: auto;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n vertical-align: middle;\n transform: translateY(-3px);\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 7px 9px;\n border: none;\n border-radius: 6px;\n background: var(--color-background-supportive, #313255);\n color: var(--color-text-muted, #c1c4cb);\n font-family: \"IBM Plex Sans\", sans-serif;\n font-size: 21px;\n line-height: 30px;\n cursor: pointer;\n white-space: nowrap;\n animation: fadeIn 200ms ease forwards;\n}\n\n.pill:hover {\n filter: brightness(1.2);\n}\n\n.active {\n outline: 1px solid #5a5b8a;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n}\n","import type { Suggestion } from \"../types\";\nimport styles from \"./PillList.module.css\";\n\ninterface PillListProps {\n pills: Suggestion[];\n activePillIndex: number;\n onSelectPill: (index: number) => void;\n}\n\nfunction getPillOpacity(index: number): number {\n if (index === 0) return 0.4;\n if (index === 1) return 0.3;\n return 0.15;\n}\n\nexport function PillList({ pills, activePillIndex, onSelectPill }: PillListProps) {\n return (\n <span className={styles.list}>\n {pills.map((pill, i) => (\n <button\n key={`${pill.type}-${pill.text}`}\n type=\"button\"\n className={`${styles.pill} ${i === activePillIndex ? styles.active : \"\"}`}\n style={{ opacity: getPillOpacity(i) }}\n onClick={() => onSelectPill(i)}\n >\n {pill.text}\n </button>\n ))}\n </span>\n );\n}\n","import {\n type ChangeEvent,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n CompletedParamState,\n Segment,\n Suggestion,\n SuggestionOption,\n UseAIAutocompleteOptions,\n UseAIAutocompleteReturn,\n} from \"../types\";\nimport { fetchSuggestions } from \"../utils/api\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nconst DEBOUNCE_MS = 100;\nconst SLOW_DEBOUNCE_MS = 300;\nconst MIN_CHARS_DIFF = 2;\n\n/**\n * Filters options using partial substring match on the text after the last completed param.\n */\nfunction filterOptions(options: SuggestionOption[], query: string): SuggestionOption[] {\n const trimmed = query.trimStart();\n if (!trimmed) return options;\n const lower = trimmed.toLowerCase();\n return options.filter((o) => !o.is_tappable || o.text.toLowerCase().includes(lower));\n}\n\n/**\n * Finds an exact match for the trimmed filter query against options.\n */\nfunction findExactMatch(options: SuggestionOption[], query: string): SuggestionOption | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n const lower = trimmed.toLowerCase();\n return options.find((o) => o.is_tappable && o.text.toLowerCase() === lower) ?? null;\n}\n\n/**\n * Derives segments for overlay rendering by matching completed params in text.\n */\nfunction deriveSegments(text: string, completedParams: CompletedParamState[]): Segment[] {\n const result: Segment[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n if (idx > pos) {\n result.push({ type: \"text\", value: text.slice(pos, idx) });\n }\n result.push({ type: \"completed\", value: param.text, param });\n pos = idx + param.text.length;\n }\n\n const remaining = text.slice(pos);\n if (remaining) {\n result.push({ type: \"text\", value: remaining });\n }\n\n return result;\n}\n\n/**\n * Checks which completed params still exist in the new text.\n */\nfunction reconcileParams(\n text: string,\n completedParams: CompletedParamState[],\n): { valid: CompletedParamState[]; invalid: CompletedParamState[] } {\n const valid: CompletedParamState[] = [];\n const invalid: CompletedParamState[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n invalid.push(param);\n } else {\n valid.push(param);\n pos = idx + param.text.length;\n }\n }\n\n return { valid, invalid };\n}\n\n/**\n * Applies option overrides by calling the override function with an empty query,\n * then prepending results to suggestions that match by type, deduplicating by text.\n */\nfunction applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: Record<string, (query: string) => SuggestionOption[]>,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => {\n const fn = overrides[s.type];\n if (!fn) return s;\n const extra = fn(\"\");\n if (extra.length === 0) return s;\n const existingTexts = new Set(extra.map((o) => o.text));\n const deduped = (s.options ?? []).filter((o) => !existingTexts.has(o.text));\n return { ...s, options: [...extra, ...deduped] };\n });\n}\n\nexport function useAIAutocomplete({\n onSubmit,\n optionOverrides,\n maskCompletedText,\n placeholder: customPlaceholder,\n}: UseAIAutocompleteOptions): UseAIAutocompleteReturn {\n // === Core state ===\n const [completedParams, setCompletedParams] = useState<CompletedParamState[]>([]);\n const [text, setText] = useState(\"\");\n const [suggestions, setSuggestions] = useState<Suggestion[]>([]);\n const [activeDropdownIndex, setActiveDropdownIndex] = useState(-1);\n\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [isReady, setIsReady] = useState(false);\n const fetchVersionRef = useRef(0);\n const abortRef = useRef<AbortController | null>(null);\n const lastRawQueryRef = useRef(\"\");\n const onSubmitRef = useRef(onSubmit);\n onSubmitRef.current = onSubmit;\n const optionOverridesRef = useRef(optionOverrides);\n optionOverridesRef.current = optionOverrides;\n const maskCompletedTextRef = useRef(maskCompletedText);\n maskCompletedTextRef.current = maskCompletedText;\n const textRef = useRef(text);\n textRef.current = text;\n const suggestionsRef = useRef(suggestions);\n suggestionsRef.current = suggestions;\n const filterBaseRef = useRef(0);\n const pillTappedRef = useRef(false);\n const skipNextFetchRef = useRef(false);\n const listboxId = useId();\n\n // === Fetch ===\n const doFetch = useCallback(async (rawQuery: string, completed: CompletedParamState[]) => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const version = ++fetchVersionRef.current;\n\n // Only show loading if no suggestions are currently displayed,\n // to avoid flickering the dropdown closed and reopened.\n const hasSuggestions = suggestionsRef.current.some((s) => s.type !== \"placeholder\");\n if (!hasSuggestions) setIsLoading(true);\n setError(null);\n try {\n const res = await fetchSuggestions(rawQuery, completed, {\n maskCompletedText: maskCompletedTextRef.current,\n signal: controller.signal,\n });\n\n // Only process the rest if this is still the latest request\n if (version !== fetchVersionRef.current) return;\n\n let newSuggestions = applyOptionOverrides(\n res.data.suggestions ?? [],\n optionOverridesRef.current,\n );\n setIsReady(res.data.is_ready ?? false);\n lastRawQueryRef.current = rawQuery;\n\n // If the last input segment is in_progress, the filter should include\n // everything the user typed that hasn't been completed — set filterBase\n // to the end of all completed segments in the current text.\n const input = res.data.input ?? [];\n const lastInput = input[input.length - 1];\n const currentText = textRef.current;\n if (lastInput?.state === \"in_progress\") {\n const inProgressIdx = currentText.lastIndexOf(lastInput.text);\n if (inProgressIdx !== -1) {\n filterBaseRef.current = inProgressIdx;\n } else {\n filterBaseRef.current = currentText.length;\n }\n } else {\n filterBaseRef.current = currentText.length;\n }\n\n // Check if the user already typed an exact match while waiting for the response\n const actionable = newSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n if (active) {\n const query = currentText.slice(filterBaseRef.current);\n const match = findExactMatch(active.options, query);\n if (match) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: active.type,\n text: match.text,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options,\n metadata: match.metadata,\n },\n ]);\n newSuggestions = newSuggestions.filter((s) => s !== active);\n }\n }\n\n setSuggestions(newSuggestions);\n\n setIsLoading(false);\n setActiveDropdownIndex(-1);\n } catch (err) {\n if (version === fetchVersionRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n }\n // biome-ignore lint/correctness/useExhaustiveDependencies: optionOverrides read via ref to keep doFetch stable\n }, []);\n\n // === Mount fetch ===\n useEffect(() => {\n doFetch(\"\", []);\n return () => {\n abortRef.current?.abort();\n };\n }, [doFetch]);\n\n // === Derived ===\n const segments = useMemo(() => deriveSegments(text, completedParams), [text, completedParams]);\n\n // Clamp filterBase if user backspaced\n filterBaseRef.current = Math.min(filterBaseRef.current, text.length);\n const filterQuery = text.slice(filterBaseRef.current);\n // biome-ignore lint/suspicious/noConsole: debug logging\n console.log(`[filter] base=${filterBaseRef.current} query=\"${filterQuery}\" text=\"${text}\"`);\n\n const serverPlaceholder = suggestions\n .filter((s) => s.type === \"placeholder\")\n .map((s) => s.text)\n .join(\" \");\n // Server placeholder takes priority, custom placeholder is the fallback\n const placeholderText = serverPlaceholder || customPlaceholder || \"\";\n const actionableSuggestions = suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion: Suggestion | undefined = actionableSuggestions[0];\n const overrideFn = activeSuggestion ? optionOverrides?.[activeSuggestion.type] : undefined;\n const filteredOptions = activeSuggestion\n ? overrideFn && filterQuery.trim()\n ? overrideFn(filterQuery.trim())\n : filterOptions(activeSuggestion.options ?? [], filterQuery)\n : [];\n const hasPlaceholder = placeholderText.length > 0;\n const isDropdownOpen =\n !isLoading &&\n filteredOptions.length > 0 &&\n (!!text || pillTappedRef.current || !hasPlaceholder);\n\n // === Handlers ===\n const selectOption = useCallback(\n (option: SuggestionOption) => {\n if (!activeSuggestion) return;\n\n const completed: CompletedParamState = {\n placeholder: \"\",\n type: activeSuggestion.type,\n text: option.text,\n kind: option.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options,\n metadata: option.metadata,\n };\n\n // Replace filter text + any partial word before it that the option completes.\n // Step 1: Always strip the filter text (everything after filterBase)\n const base = filterBaseRef.current;\n let prefix = text.slice(0, base);\n // Step 2: If the last word before filterBase is a partial start of the option,\n // trim it too (e.g. \"Create a campa|i\" → \"campa\" starts \"campaign\")\n if (prefix.length > 0 && !prefix.endsWith(\" \")) {\n const lastWord = prefix.split(/\\s+/).pop() ?? \"\";\n if (lastWord && option.text.toLowerCase().startsWith(lastWord.toLowerCase())) {\n prefix = prefix.slice(0, prefix.length - lastWord.length);\n }\n }\n\n const needsSpace = prefix.length > 0 && prefix[prefix.length - 1] !== \" \";\n const newText = prefix + (needsSpace ? \" \" : \"\") + option.text + \" \";\n setText(newText);\n filterBaseRef.current = newText.length;\n setCompletedParams((prev) => [...prev, completed]);\n setSuggestions((prev) => prev.filter((s) => s !== activeSuggestion));\n pillTappedRef.current = false;\n setActiveDropdownIndex(-1);\n // If there are other actionable suggestions remaining, the client already\n // knows what to show next — skip the network call the debounce would trigger.\n const remainingActionable = actionableSuggestions.length - 1;\n if (remainingActionable > 0) {\n skipNextFetchRef.current = true;\n }\n },\n [activeSuggestion, actionableSuggestions, text],\n );\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => {\n const raw = e.target.value;\n const newValue = raw.length > 0 ? raw[0].toUpperCase() + raw.slice(1) : raw;\n setText(newValue);\n pillTappedRef.current = false;\n setActiveDropdownIndex(-1);\n\n // Reconcile — check if any completed params were modified\n const { valid, invalid } = reconcileParams(newValue, completedParams);\n if (invalid.length > 0) {\n setCompletedParams(valid);\n for (const param of invalid) {\n setSuggestions((prev) => [\n {\n type: param.suggestionType,\n text: param.suggestionPlaceholder,\n required: true,\n options: param.options,\n },\n ...prev,\n ]);\n }\n }\n\n // Check exact match on the new filter query (only if no params were broken)\n if (activeSuggestion && invalid.length === 0) {\n const newFilterQuery = newValue.slice(filterBaseRef.current);\n const match = findExactMatch(activeSuggestion.options, newFilterQuery);\n if (match) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: activeSuggestion.type,\n text: match.text,\n kind: match.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options,\n metadata: match.metadata,\n },\n ]);\n setSuggestions((prev) => prev.filter((s) => s !== activeSuggestion));\n }\n }\n },\n [completedParams, activeSuggestion],\n );\n\n // === Debounced fetch on typing ===\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const slowDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hasFetchedRef = useRef(true);\n\n useEffect(() => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n\n /** Returns true if a fetch was triggered. */\n const attemptFetch = (minDiff: number): boolean => {\n // After selecting an option when other suggestions remain, skip the fetch\n if (skipNextFetchRef.current) {\n skipNextFetchRef.current = false;\n return false;\n }\n\n // Reset: if input is cleared, fetch fresh suggestions\n if (!text && completedParams.length === 0) {\n if (!hasFetchedRef.current) {\n hasFetchedRef.current = true;\n return false;\n }\n doFetch(\"\", []);\n return true;\n }\n\n // Check if current tappable options can still handle the filter\n const currentQuery = text.slice(filterBaseRef.current);\n const currentSuggestions = suggestionsRef.current;\n const actionable = currentSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n const currentFiltered = active ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o) => o.is_tappable);\n const hasExactMatch = active ? findExactMatch(active.options, currentQuery) !== null : false;\n\n // Only skip fetch if there are tappable filtered options and no exact match.\n // Non-tappable options (hints) don't count — we still need server results.\n // Skip this gate if filterQuery is empty — options are showing vacuously.\n const isInFilterZone = currentQuery.trim().length > 0;\n if (tappableFiltered.length > 0 && !hasExactMatch && isInFilterZone) return false;\n\n const { rawQuery, completedParams: updatedParams } = buildQuery(text, completedParams);\n const isDeleting = rawQuery.length < lastRawQueryRef.current.length;\n const charDiff = Math.abs(rawQuery.length - lastRawQueryRef.current.length);\n if (isDeleting || charDiff >= minDiff) {\n doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\n };\n\n // Fast debounce: fires at 100ms, requires >= 2 char diff\n // If it fetches, cancel the slow timer since the request is already made.\n debounceRef.current = setTimeout(() => {\n if (attemptFetch(MIN_CHARS_DIFF)) {\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n }\n }, DEBOUNCE_MS);\n\n // Slow debounce: fires at 300ms, requires >= 1 char diff (catches single-char pauses)\n slowDebounceRef.current = setTimeout(() => attemptFetch(1), SLOW_DEBOUNCE_MS);\n\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (slowDebounceRef.current) clearTimeout(slowDebounceRef.current);\n };\n }, [text, completedParams, doFetch]);\n\n const getTappableIndices = useCallback(() => {\n const cols = 2;\n const tappable = filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n // Reorder to column-first: all left column items, then all right column items\n const left = tappable.filter((i) => i % cols === 0);\n const right = tappable.filter((i) => i % cols === 1);\n return [...left, ...right];\n }, [filteredOptions]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n const tappableIndices = getTappableIndices();\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n const currentPos = tappableIndices.indexOf(activeDropdownIndex);\n const nextPos = currentPos < tappableIndices.length - 1 ? currentPos + 1 : 0;\n setActiveDropdownIndex(tappableIndices[nextPos]);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n const currentPos = tappableIndices.indexOf(activeDropdownIndex);\n const prevPos = currentPos > 0 ? currentPos - 1 : tappableIndices.length - 1;\n setActiveDropdownIndex(tappableIndices[prevPos]);\n break;\n }\n case \"ArrowRight\": {\n if (activeDropdownIndex < 0) break;\n const cols = 2;\n if (activeDropdownIndex % cols === 0) {\n const rightNeighbor = activeDropdownIndex + 1;\n if (\n rightNeighbor < filteredOptions.length &&\n filteredOptions[rightNeighbor]?.is_tappable\n ) {\n e.preventDefault();\n setActiveDropdownIndex(rightNeighbor);\n }\n }\n break;\n }\n case \"ArrowLeft\": {\n if (activeDropdownIndex < 0) break;\n const cols = 2;\n if (activeDropdownIndex % cols === 1) {\n const leftNeighbor = activeDropdownIndex - 1;\n if (leftNeighbor >= 0 && filteredOptions[leftNeighbor]?.is_tappable) {\n e.preventDefault();\n setActiveDropdownIndex(leftNeighbor);\n }\n }\n break;\n }\n case \"Enter\": {\n e.preventDefault();\n if (activeDropdownIndex >= 0 && filteredOptions[activeDropdownIndex]?.is_tappable) {\n selectOption(filteredOptions[activeDropdownIndex]);\n } else if (onSubmitRef.current) {\n onSubmitRef.current();\n }\n break;\n }\n case \"Tab\": {\n if (activeDropdownIndex >= 0 && filteredOptions[activeDropdownIndex]?.is_tappable) {\n e.preventDefault();\n selectOption(filteredOptions[activeDropdownIndex]);\n } else if (isDropdownOpen) {\n const firstTappable = filteredOptions.find((o) => o.is_tappable);\n if (firstTappable) {\n e.preventDefault();\n selectOption(firstTappable);\n }\n } else if (!text && hasPlaceholder) {\n e.preventDefault();\n const placeholderSuggestion = suggestions.find((s) => s.type === \"placeholder\");\n setText(placeholderText);\n filterBaseRef.current = placeholderText.length;\n if (placeholderSuggestion) {\n setCompletedParams((prev) => [\n ...prev,\n {\n placeholder: \"\",\n type: placeholderSuggestion.type,\n text: placeholderText,\n kind: null,\n suggestionType: placeholderSuggestion.type,\n suggestionPlaceholder: placeholderSuggestion.text,\n options: placeholderSuggestion.options,\n },\n ]);\n setSuggestions((prev) => prev.filter((s) => s !== placeholderSuggestion));\n }\n }\n break;\n }\n case \"Escape\":\n setActiveDropdownIndex(-1);\n break;\n }\n },\n [\n activeDropdownIndex,\n filteredOptions,\n hasPlaceholder,\n isDropdownOpen,\n placeholderText,\n selectOption,\n suggestions,\n getTappableIndices,\n text,\n ],\n );\n\n const setActivePill = useCallback(\n (index: number) => {\n const actionable = suggestions.filter((s) => s.type !== \"placeholder\");\n if (index < 0 || index >= actionable.length) return;\n const moved = actionable[index];\n const rest = actionable.filter((_, i) => i !== index);\n const placeholders = suggestions.filter((s) => s.type === \"placeholder\");\n setSuggestions([...placeholders, moved, ...rest]);\n pillTappedRef.current = true;\n setActiveDropdownIndex(-1);\n },\n [suggestions],\n );\n\n const removeLastParam = useCallback(() => {\n if (completedParams.length === 0) return;\n const lastParam = completedParams[completedParams.length - 1];\n const restoredSuggestion: Suggestion = {\n type: lastParam.suggestionType,\n text: lastParam.suggestionPlaceholder,\n required: true,\n options: lastParam.options,\n };\n setCompletedParams((prev) => prev.slice(0, -1));\n setSuggestions((prev) => [restoredSuggestion, ...prev]);\n setActiveDropdownIndex(-1);\n }, [completedParams]);\n\n const reEditParam = useCallback((param: CompletedParamState) => {\n const restoredSuggestion: Suggestion = {\n type: param.suggestionType,\n text: param.suggestionPlaceholder,\n required: true,\n options: param.options,\n };\n setText((prev) => {\n const idx = prev.indexOf(param.text);\n if (idx === -1) return prev;\n const before = prev.slice(0, idx);\n const after = prev.slice(idx + param.text.length);\n const cleaned = (before + after).replace(/ {2,}/g, \" \");\n filterBaseRef.current = Math.min(filterBaseRef.current, cleaned.length);\n return cleaned;\n });\n setCompletedParams((prev) => prev.filter((p) => p !== param));\n setSuggestions((prev) => [restoredSuggestion, ...prev]);\n setActiveDropdownIndex(-1);\n pillTappedRef.current = true;\n }, []);\n\n const reset = useCallback(() => {\n setText(\"\");\n setCompletedParams([]);\n setSuggestions([]);\n setActiveDropdownIndex(-1);\n setIsReady(false);\n filterBaseRef.current = 0;\n lastRawQueryRef.current = \"\";\n doFetch(\"\", []);\n }, [doFetch]);\n\n const activeDescendantId =\n activeDropdownIndex >= 0 ? `${listboxId}-option-${activeDropdownIndex}` : undefined;\n\n return {\n completedParams,\n suggestionPills: actionableSuggestions,\n activePillIndex: 0,\n setActivePill,\n removeLastParam,\n reEditParam,\n segments,\n suggestions,\n activeIndex: activeDropdownIndex,\n isReady,\n isLoading,\n error,\n inputProps: {\n value: text,\n placeholder: placeholderText || undefined,\n onChange: handleChange,\n onKeyDown: handleKeyDown,\n role: \"combobox\" as const,\n \"aria-expanded\": isDropdownOpen,\n \"aria-activedescendant\": activeDescendantId,\n \"aria-autocomplete\": \"list\" as const,\n \"aria-controls\": listboxId,\n },\n reset,\n dropdownProps: {\n suggestions: activeSuggestion ? [{ ...activeSuggestion, options: filteredOptions }] : [],\n activeIndex: activeDropdownIndex,\n onSelect: selectOption,\n isOpen: isDropdownOpen,\n id: listboxId,\n },\n };\n}\n","import type {\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n} from \"../types\";\n\nconst SDK_VERSION = \"0.1.0\";\n\n// process.env.* values are replaced at build time by the bundler's `define` config.\n// Use optional chaining (process?.env), NOT `typeof process !== \"undefined\"` guards —\n// the typeof guard prevents the replacement from taking effect in browsers.\ndeclare const process: { env: Record<string, string | undefined> } | undefined;\nconst API_ENDPOINT = process?.env.MAGICX_API_ENDPOINT || \"/api/suggest\";\n\nlet hasWarnedMissingKey = false;\n\nfunction getApiKey(): string {\n const key = process?.env.MAGICX_AI_AUTOCOMPLETE_API_KEY || \"\";\n if (!key && !hasWarnedMissingKey) {\n hasWarnedMissingKey = true;\n // biome-ignore lint/suspicious/noConsole: intentional dev warning\n console.warn(\n \"[AIAutocomplete] No API key set (MAGICX_AI_AUTOCOMPLETE_API_KEY). \" +\n \"Requests will be sent without an Authorization header.\",\n );\n }\n return key;\n}\n\nfunction getAuthScheme(): \"Bearer\" | \"Basic\" {\n const scheme = process?.env.MAGICX_AUTH_SCHEME;\n return scheme === \"Basic\" ? \"Basic\" : \"Bearer\";\n}\n\nfunction generateRequestId(): string {\n return crypto.randomUUID();\n}\n\nfunction toWireParam(param: CompletedParamState, includeText: boolean): CompletedParam {\n return {\n placeholder: param.placeholder,\n type: param.type,\n ...(includeText && { text: param.text }),\n kind: param.kind,\n };\n}\n\nexport async function fetchSuggestions(\n rawQuery: string,\n completedParams: CompletedParamState[],\n options?: { maskCompletedText?: boolean; signal?: AbortSignal },\n): Promise<AutocompleteResponse> {\n const apiKey = getApiKey();\n const authScheme = getAuthScheme();\n const includeText = !options?.maskCompletedText;\n\n const rawCount = completedParams.find(\n (p) => p.type === \"contact\" && p.metadata?.contact_account_count,\n )?.metadata?.contact_account_count;\n const contactAccountCount = typeof rawCount === \"number\" ? rawCount : undefined;\n\n const body: AutocompleteRequest = {\n data: {\n raw_query: rawQuery,\n completed_params: completedParams.map((p) => toWireParam(p, includeText)),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n },\n meta: {\n request_id: generateRequestId(),\n request_at: new Date().toISOString(),\n language: typeof navigator !== \"undefined\" ? navigator.language : \"en-US\",\n client_version: SDK_VERSION,\n },\n };\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-App-Identifier\": process?.env.MAGICX_APP_IDENTIFIER || \"active-campaign-demo\",\n };\n if (apiKey) {\n headers.Authorization = authScheme === \"Basic\" ? `Basic ${btoa(apiKey)}` : `Bearer ${apiKey}`;\n }\n\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: options?.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n}\n","import type { CompletedParamState } from \"../types\";\n\ninterface BuildQueryResult {\n rawQuery: string;\n completedParams: CompletedParamState[];\n}\n\n/**\n * Takes the raw input text and completed params (without placeholders),\n * replaces each completed param's text in the string with a {{TYPE_N}} token,\n * and returns the transformed query + params with placeholders filled in.\n *\n * Replacements happen left-to-right, first occurrence only per param.\n * Counter is per-type (e.g. {{TASK_1}}, {{GOAL_1}}, {{GOAL_2}}).\n */\nexport function buildQuery(text: string, completedParams: CompletedParamState[]): BuildQueryResult {\n let result = text;\n const typeCounts: Record<string, number> = {};\n const updatedParams: CompletedParamState[] = [];\n\n for (const param of completedParams) {\n const count = (typeCounts[param.type] ?? 0) + 1;\n typeCounts[param.type] = count;\n\n const typeKey = param.type.toUpperCase().replace(/\\s+/g, \"_\");\n const placeholder = `{{${typeKey}_${count}}}`;\n\n // Replace first occurrence of the param text in the string\n const index = result.indexOf(param.text);\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n }\n\n updatedParams.push({ ...param, placeholder });\n }\n\n return { rawQuery: result, completedParams: updatedParams };\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","AIAutocomplete_default","AIAutocompleteDropdown_default","SuggestionGrid_default","SuggestionItem_default","jsx","jsxs","SuggestionItem","option","isHighlighted","onSelect","onHighlight","id","className","SuggestionItem_default","e","jsx","SuggestionGrid","options","activeIndex","onSelect","onHighlight","listboxId","SuggestionGrid_default","option","i","SuggestionItem","jsx","AIAutocompleteDropdown","suggestions","activeIndex","onSelect","isOpen","id","className","activeSuggestion","AIAutocompleteDropdown_default","e","SuggestionGrid","PillList_default","jsx","getPillOpacity","index","PillList","pills","activePillIndex","onSelectPill","PillList_default","pill","i","useCallback","useEffect","useId","useMemo","useRef","useState","SDK_VERSION","API_ENDPOINT","hasWarnedMissingKey","getApiKey","key","getAuthScheme","generateRequestId","toWireParam","param","includeText","fetchSuggestions","rawQuery","completedParams","options","apiKey","authScheme","rawCount","p","contactAccountCount","body","headers","response","buildQuery","text","completedParams","result","typeCounts","updatedParams","param","count","placeholder","index","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","filterOptions","options","query","trimmed","lower","findExactMatch","deriveSegments","text","completedParams","result","pos","param","idx","remaining","reconcileParams","valid","invalid","applyOptionOverrides","suggestions","overrides","s","fn","extra","existingTexts","o","deduped","useAIAutocomplete","onSubmit","optionOverrides","maskCompletedText","customPlaceholder","setCompletedParams","useState","setText","setSuggestions","activeDropdownIndex","setActiveDropdownIndex","isLoading","setIsLoading","error","setError","isReady","setIsReady","fetchVersionRef","useRef","abortRef","lastRawQueryRef","onSubmitRef","optionOverridesRef","maskCompletedTextRef","textRef","suggestionsRef","filterBaseRef","pillTappedRef","skipNextFetchRef","listboxId","useId","doFetch","useCallback","rawQuery","completed","controller","version","res","fetchSuggestions","newSuggestions","input","lastInput","currentText","inProgressIdx","active","match","prev","err","useEffect","segments","useMemo","filterQuery","placeholderText","actionableSuggestions","activeSuggestion","overrideFn","filteredOptions","hasPlaceholder","isDropdownOpen","selectOption","option","base","prefix","lastWord","needsSpace","newText","handleChange","e","raw","newValue","newFilterQuery","debounceRef","slowDebounceRef","hasFetchedRef","attemptFetch","minDiff","currentQuery","tappableFiltered","hasExactMatch","isInFilterZone","updatedParams","buildQuery","isDeleting","charDiff","getTappableIndices","tappable","i","left","right","handleKeyDown","tappableIndices","currentPos","nextPos","prevPos","rightNeighbor","leftNeighbor","firstTappable","placeholderSuggestion","setActivePill","index","actionable","moved","rest","_","placeholders","removeLastParam","lastParam","restoredSuggestion","reEditParam","before","after","cleaned","p","reset","activeDescendantId","jsx","jsxs","AIAutocomplete","onSubmit","optionOverrides","maskCompletedText","placeholder","className","textareaRef","useRef","showCheckmark","setShowCheckmark","useState","handleSubmitRef","useEffect","completedParams","suggestionPills","activePillIndex","setActivePill","segments","inputProps","dropdownProps","reset","useAIAutocomplete","handleContainerClick","canSubmit","handleSubmit","useCallback","rawQuery","finalParams","buildQuery","onChange","inputPlaceholder","restProps","isEmpty","AIAutocomplete_default","AIAutocompleteDropdown","seg","i","PillList","e"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magicx-eng/ai-autocomplete-react",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "AI Autocomplete React SDK — guided autocomplete with pill-based input and dropdown suggestions",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "vite playground",
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"build:playground": "vite build playground --outDir dist --emptyOutDir",
|
|
23
|
+
"lint": "biome check .",
|
|
24
|
+
"lint:fix": "biome check --fix .",
|
|
25
|
+
"format": "biome format --fix .",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"test:coverage": "vitest run --coverage",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepack": "mv README.md repo-readme.md && cp npm-readme.md README.md",
|
|
31
|
+
"postpack": "mv repo-readme.md README.md",
|
|
32
|
+
"prepublishOnly": "pnpm lint && pnpm test && pnpm build"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": ">=18.0.0",
|
|
36
|
+
"react-dom": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "^2.4.5",
|
|
40
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
41
|
+
"@testing-library/react": "^16.3.2",
|
|
42
|
+
"@types/react": "^19.2.14",
|
|
43
|
+
"@types/react-dom": "^19.2.3",
|
|
44
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
45
|
+
"jsdom": "^28.1.0",
|
|
46
|
+
"msw": "^2.12.10",
|
|
47
|
+
"react": "^19.2.4",
|
|
48
|
+
"react-dom": "^19.2.4",
|
|
49
|
+
"tsup": "^8.5.1",
|
|
50
|
+
"typescript": "^5.9.3",
|
|
51
|
+
"vite": "^7.3.1",
|
|
52
|
+
"vitest": "^4.0.18"
|
|
53
|
+
},
|
|
54
|
+
"packageManager": "pnpm@10.30.3",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"react",
|
|
57
|
+
"autocomplete",
|
|
58
|
+
"ai",
|
|
59
|
+
"typescript"
|
|
60
|
+
],
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"msw": {
|
|
63
|
+
"workerDirectory": [
|
|
64
|
+
"playground/public"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|