@seed-ship/mcp-ui-solid 2.0.0 → 2.1.0
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 +50 -1
- package/dist/components/AutocompleteDropdown.cjs +201 -0
- package/dist/components/AutocompleteDropdown.cjs.map +1 -0
- package/dist/components/AutocompleteDropdown.d.ts +71 -0
- package/dist/components/AutocompleteDropdown.d.ts.map +1 -0
- package/dist/components/AutocompleteDropdown.js +201 -0
- package/dist/components/AutocompleteDropdown.js.map +1 -0
- package/dist/components/AutocompleteFormField.cjs +289 -0
- package/dist/components/AutocompleteFormField.cjs.map +1 -0
- package/dist/components/AutocompleteFormField.d.ts +52 -0
- package/dist/components/AutocompleteFormField.d.ts.map +1 -0
- package/dist/components/AutocompleteFormField.js +289 -0
- package/dist/components/AutocompleteFormField.js.map +1 -0
- package/dist/components/DraggableGridItem.cjs +133 -0
- package/dist/components/DraggableGridItem.cjs.map +1 -0
- package/dist/components/DraggableGridItem.d.ts +95 -0
- package/dist/components/DraggableGridItem.d.ts.map +1 -0
- package/dist/components/DraggableGridItem.js +133 -0
- package/dist/components/DraggableGridItem.js.map +1 -0
- package/dist/components/EditableUIResourceRenderer.cjs +203 -0
- package/dist/components/EditableUIResourceRenderer.cjs.map +1 -0
- package/dist/components/EditableUIResourceRenderer.d.ts +43 -0
- package/dist/components/EditableUIResourceRenderer.d.ts.map +1 -0
- package/dist/components/EditableUIResourceRenderer.js +203 -0
- package/dist/components/EditableUIResourceRenderer.js.map +1 -0
- package/dist/components/GhostText.cjs +105 -0
- package/dist/components/GhostText.cjs.map +1 -0
- package/dist/components/GhostText.d.ts +113 -0
- package/dist/components/GhostText.d.ts.map +1 -0
- package/dist/components/GhostText.js +105 -0
- package/dist/components/GhostText.js.map +1 -0
- package/dist/components/ResizeHandle.cjs +173 -0
- package/dist/components/ResizeHandle.cjs.map +1 -0
- package/dist/components/ResizeHandle.d.ts +50 -0
- package/dist/components/ResizeHandle.d.ts.map +1 -0
- package/dist/components/ResizeHandle.js +173 -0
- package/dist/components/ResizeHandle.js.map +1 -0
- package/dist/context/AutocompleteContext.cjs +158 -0
- package/dist/context/AutocompleteContext.cjs.map +1 -0
- package/dist/context/AutocompleteContext.d.ts +77 -0
- package/dist/context/AutocompleteContext.d.ts.map +1 -0
- package/dist/context/AutocompleteContext.js +158 -0
- package/dist/context/AutocompleteContext.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useAutocomplete.cjs +234 -0
- package/dist/hooks/useAutocomplete.cjs.map +1 -0
- package/dist/hooks/useAutocomplete.d.ts +119 -0
- package/dist/hooks/useAutocomplete.d.ts.map +1 -0
- package/dist/hooks/useAutocomplete.js +234 -0
- package/dist/hooks/useAutocomplete.js.map +1 -0
- package/dist/hooks/useDragDrop.cjs +170 -0
- package/dist/hooks/useDragDrop.cjs.map +1 -0
- package/dist/hooks/useDragDrop.d.ts +100 -0
- package/dist/hooks/useDragDrop.d.ts.map +1 -0
- package/dist/hooks/useDragDrop.js +170 -0
- package/dist/hooks/useDragDrop.js.map +1 -0
- package/dist/hooks/useResize.cjs +209 -0
- package/dist/hooks/useResize.cjs.map +1 -0
- package/dist/hooks/useResize.d.ts +87 -0
- package/dist/hooks/useResize.d.ts.map +1 -0
- package/dist/hooks/useResize.js +209 -0
- package/dist/hooks/useResize.js.map +1 -0
- package/dist/hooks.cjs +6 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +6 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.js +6 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +29 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/duckdb.cjs +192 -0
- package/dist/plugins/duckdb.cjs.map +1 -0
- package/dist/plugins/duckdb.d.ts +20 -0
- package/dist/plugins/duckdb.d.ts.map +1 -0
- package/dist/plugins/duckdb.js +170 -0
- package/dist/plugins/duckdb.js.map +1 -0
- package/dist/plugins/groq.cjs +97 -0
- package/dist/plugins/groq.cjs.map +1 -0
- package/dist/plugins/groq.d.ts +13 -0
- package/dist/plugins/groq.d.ts.map +1 -0
- package/dist/plugins/groq.js +97 -0
- package/dist/plugins/groq.js.map +1 -0
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/rest.cjs +92 -0
- package/dist/plugins/rest.cjs.map +1 -0
- package/dist/plugins/rest.d.ts +13 -0
- package/dist/plugins/rest.d.ts.map +1 -0
- package/dist/plugins/rest.js +92 -0
- package/dist/plugins/rest.js.map +1 -0
- package/dist/plugins/supabase.cjs +79 -0
- package/dist/plugins/supabase.cjs.map +1 -0
- package/dist/plugins/supabase.d.ts +13 -0
- package/dist/plugins/supabase.d.ts.map +1 -0
- package/dist/plugins/supabase.js +79 -0
- package/dist/plugins/supabase.js.map +1 -0
- package/dist/services/validation.cjs +40 -1
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +40 -1
- package/dist/services/validation.js.map +1 -1
- package/dist/types/index.d.ts +430 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +430 -0
- package/dist/types.d.ts +430 -0
- package/package.json +16 -1
- package/src/components/AutocompleteDropdown.tsx +329 -0
- package/src/components/AutocompleteFormField.tsx +288 -0
- package/src/components/DraggableGridItem.tsx +274 -0
- package/src/components/EditableUIResourceRenderer.tsx +273 -0
- package/src/components/GhostText.tsx +262 -0
- package/src/components/ResizeHandle.tsx +262 -0
- package/src/context/AutocompleteContext.tsx +317 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/useAutocomplete.test.ts +334 -0
- package/src/hooks/useAutocomplete.ts +466 -0
- package/src/hooks/useDragDrop.test.ts +355 -0
- package/src/hooks/useDragDrop.ts +379 -0
- package/src/hooks/useResize.test.ts +313 -0
- package/src/hooks/useResize.ts +372 -0
- package/src/index.ts +71 -0
- package/src/plugins/duckdb.ts +269 -0
- package/src/plugins/groq.ts +137 -0
- package/src/plugins/index.ts +14 -0
- package/src/plugins/rest.ts +147 -0
- package/src/plugins/supabase.ts +120 -0
- package/src/services/validation.ts +46 -0
- package/src/styles/autocomplete.css +356 -0
- package/src/styles/drag-drop.css +297 -0
- package/src/styles/index.css +7 -0
- package/src/types/index.ts +529 -0
- package/src/vite-env.d.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +2 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB WASM Autocomplete Plugin
|
|
3
|
+
* Provides SQL-based suggestions using DuckDB WASM
|
|
4
|
+
*
|
|
5
|
+
* Sprint Autocomplete Feature
|
|
6
|
+
*
|
|
7
|
+
* Note: DuckDB WASM is ~2MB and is lazy-loaded on first use.
|
|
8
|
+
* This plugin requires @duckdb/duckdb-wasm as an optional peer dependency.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
AutocompletePlugin,
|
|
13
|
+
AutocompleteResult,
|
|
14
|
+
AutocompleteContext,
|
|
15
|
+
AutocompleteOption,
|
|
16
|
+
DuckDBPluginConfig
|
|
17
|
+
} from '../types'
|
|
18
|
+
|
|
19
|
+
// Type for DuckDB connection (lazy loaded)
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
type DuckDBConnection = any
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
type DuckDBInstance = any
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
type DuckDBModule = any
|
|
26
|
+
|
|
27
|
+
// Module state for DuckDB (singleton)
|
|
28
|
+
let duckdbPromise: Promise<{ db: DuckDBInstance; conn: DuckDBConnection }> | null = null
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if we're in a test environment
|
|
32
|
+
*/
|
|
33
|
+
function isTestEnvironment(): boolean {
|
|
34
|
+
return typeof process !== 'undefined' && (
|
|
35
|
+
process.env.NODE_ENV === 'test' ||
|
|
36
|
+
process.env.VITEST === 'true' ||
|
|
37
|
+
typeof (globalThis as any).vitest !== 'undefined'
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lazy load DuckDB WASM
|
|
43
|
+
*/
|
|
44
|
+
async function loadDuckDB(): Promise<{ db: DuckDBInstance; conn: DuckDBConnection }> {
|
|
45
|
+
// In test environment, throw early to avoid import issues
|
|
46
|
+
if (isTestEnvironment()) {
|
|
47
|
+
throw new Error('[DuckDB Plugin] DuckDB WASM is not available in test environment')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (duckdbPromise) {
|
|
51
|
+
return duckdbPromise
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
duckdbPromise = (async () => {
|
|
55
|
+
try {
|
|
56
|
+
// Dynamic import to avoid bundling if not used
|
|
57
|
+
// The import is wrapped to handle missing module gracefully
|
|
58
|
+
let duckdb: DuckDBModule
|
|
59
|
+
try {
|
|
60
|
+
duckdb = await import(/* @vite-ignore */ '@duckdb/duckdb-wasm')
|
|
61
|
+
} catch (importError) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'[DuckDB Plugin] @duckdb/duckdb-wasm is not installed. ' +
|
|
64
|
+
'Install it with: npm install @duckdb/duckdb-wasm'
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get WASM bundles
|
|
69
|
+
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles()
|
|
70
|
+
|
|
71
|
+
// Select best bundle for the browser
|
|
72
|
+
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES)
|
|
73
|
+
|
|
74
|
+
// Instantiate worker and database
|
|
75
|
+
const worker = new Worker(bundle.mainWorker!)
|
|
76
|
+
const logger = new duckdb.ConsoleLogger()
|
|
77
|
+
const db = new duckdb.AsyncDuckDB(logger, worker)
|
|
78
|
+
|
|
79
|
+
await db.instantiate(bundle.mainModule, bundle.pthreadWorker)
|
|
80
|
+
|
|
81
|
+
// Create connection
|
|
82
|
+
const conn = await db.connect()
|
|
83
|
+
|
|
84
|
+
return { db, conn }
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('[DuckDB Plugin] Failed to load DuckDB WASM:', error)
|
|
87
|
+
duckdbPromise = null
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
})()
|
|
91
|
+
|
|
92
|
+
return duckdbPromise
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a DuckDB WASM autocomplete plugin
|
|
97
|
+
*/
|
|
98
|
+
export function createDuckDBPlugin(config: DuckDBPluginConfig): AutocompletePlugin {
|
|
99
|
+
const { query, data } = config
|
|
100
|
+
|
|
101
|
+
let isReady = false
|
|
102
|
+
let initPromise: Promise<void> | null = null
|
|
103
|
+
let connection: DuckDBConnection | null = null
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Initialize DuckDB and load data if provided
|
|
107
|
+
*/
|
|
108
|
+
const initialize = async (): Promise<void> => {
|
|
109
|
+
if (initPromise) return initPromise
|
|
110
|
+
|
|
111
|
+
initPromise = (async () => {
|
|
112
|
+
try {
|
|
113
|
+
const { conn } = await loadDuckDB()
|
|
114
|
+
connection = conn
|
|
115
|
+
|
|
116
|
+
// Load data if provided
|
|
117
|
+
if (data) {
|
|
118
|
+
await loadData(conn, data)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
isReady = true
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('[DuckDB Plugin] Initialization error:', error)
|
|
124
|
+
throw error
|
|
125
|
+
}
|
|
126
|
+
})()
|
|
127
|
+
|
|
128
|
+
return initPromise
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load data into DuckDB
|
|
133
|
+
*/
|
|
134
|
+
const loadData = async (
|
|
135
|
+
conn: DuckDBConnection,
|
|
136
|
+
dataConfig: NonNullable<DuckDBPluginConfig['data']>
|
|
137
|
+
): Promise<void> => {
|
|
138
|
+
const { tableName, source, format = 'csv' } = dataConfig
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
if (typeof source === 'string') {
|
|
142
|
+
// URL - fetch and load
|
|
143
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
144
|
+
if (format === 'csv') {
|
|
145
|
+
await conn.query(`
|
|
146
|
+
CREATE TABLE IF NOT EXISTS ${tableName} AS
|
|
147
|
+
SELECT * FROM read_csv_auto('${source}')
|
|
148
|
+
`)
|
|
149
|
+
} else if (format === 'json') {
|
|
150
|
+
await conn.query(`
|
|
151
|
+
CREATE TABLE IF NOT EXISTS ${tableName} AS
|
|
152
|
+
SELECT * FROM read_json_auto('${source}')
|
|
153
|
+
`)
|
|
154
|
+
} else if (format === 'parquet') {
|
|
155
|
+
await conn.query(`
|
|
156
|
+
CREATE TABLE IF NOT EXISTS ${tableName} AS
|
|
157
|
+
SELECT * FROM read_parquet('${source}')
|
|
158
|
+
`)
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
// Inline data (CSV/JSON string)
|
|
162
|
+
// For inline data, we'd need to use DuckDB's data registration
|
|
163
|
+
console.warn('[DuckDB Plugin] Inline data not yet supported, use URL instead')
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('[DuckDB Plugin] Error loading data:', error)
|
|
168
|
+
throw error
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
id: 'duckdb',
|
|
174
|
+
name: 'DuckDB WASM',
|
|
175
|
+
|
|
176
|
+
configure(newConfig: Record<string, any>) {
|
|
177
|
+
// Allow runtime reconfiguration
|
|
178
|
+
Object.assign(config, newConfig)
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
isReady() {
|
|
182
|
+
return isReady
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
async getSuggestions(
|
|
186
|
+
input: string,
|
|
187
|
+
_context?: AutocompleteContext
|
|
188
|
+
): Promise<AutocompleteResult> {
|
|
189
|
+
// Ensure DuckDB is initialized
|
|
190
|
+
if (!isReady) {
|
|
191
|
+
try {
|
|
192
|
+
await initialize()
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('[DuckDB Plugin] Failed to initialize:', error)
|
|
195
|
+
return { type: 'options', options: [] }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!connection) {
|
|
200
|
+
return { type: 'options', options: [] }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!input.trim()) {
|
|
204
|
+
return { type: 'options', options: [] }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Replace :search placeholder with actual value
|
|
209
|
+
const preparedQuery = query.replace(/:search/g, input.replace(/'/g, "''"))
|
|
210
|
+
|
|
211
|
+
const result = await connection.query(preparedQuery)
|
|
212
|
+
const rows = result.toArray()
|
|
213
|
+
|
|
214
|
+
// Get column names
|
|
215
|
+
const columns = result.schema.fields.map((f: any) => f.name)
|
|
216
|
+
const valueColumn = columns[0]
|
|
217
|
+
const labelColumn = columns.length > 1 ? columns[1] : columns[0]
|
|
218
|
+
|
|
219
|
+
const options: AutocompleteOption[] = rows.map((row: any) => {
|
|
220
|
+
// Convert row to object
|
|
221
|
+
const rowObj: Record<string, any> = {}
|
|
222
|
+
columns.forEach((col: string, idx: number) => {
|
|
223
|
+
rowObj[col] = row[idx] ?? row[col]
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
value: String(rowObj[valueColumn]),
|
|
228
|
+
label: String(rowObj[labelColumn]),
|
|
229
|
+
metadata: rowObj
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
type: 'options',
|
|
235
|
+
options
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('[DuckDB Plugin] Query error:', error)
|
|
239
|
+
return { type: 'options', options: [] }
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async dispose() {
|
|
244
|
+
if (connection) {
|
|
245
|
+
try {
|
|
246
|
+
await connection.close()
|
|
247
|
+
} catch (e) {
|
|
248
|
+
console.error('[DuckDB Plugin] Error closing connection:', e)
|
|
249
|
+
}
|
|
250
|
+
connection = null
|
|
251
|
+
}
|
|
252
|
+
isReady = false
|
|
253
|
+
initPromise = null
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Preload DuckDB WASM (call early to warm cache)
|
|
260
|
+
*/
|
|
261
|
+
export async function preloadDuckDB(): Promise<void> {
|
|
262
|
+
try {
|
|
263
|
+
await loadDuckDB()
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('[DuckDB Plugin] Preload error:', error)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default createDuckDBPlugin
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groq LLM Autocomplete Plugin
|
|
3
|
+
* Provides LLM-powered text completion suggestions using Groq API
|
|
4
|
+
*
|
|
5
|
+
* Sprint Autocomplete Feature
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
AutocompletePlugin,
|
|
10
|
+
AutocompleteResult,
|
|
11
|
+
AutocompleteContext,
|
|
12
|
+
GroqPluginConfig
|
|
13
|
+
} from '../types'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default system prompt for completion
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_SYSTEM_PROMPT = `You are an autocomplete assistant. Given the user's partial input, provide a natural completion.
|
|
19
|
+
Rules:
|
|
20
|
+
- Complete the text naturally and concisely
|
|
21
|
+
- Return ONLY the completed text (including the original input)
|
|
22
|
+
- Do not add quotes, explanations, or additional text
|
|
23
|
+
- If unsure, return the original input unchanged`
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a Groq LLM autocomplete plugin
|
|
27
|
+
*/
|
|
28
|
+
export function createGroqPlugin(config: GroqPluginConfig): AutocompletePlugin {
|
|
29
|
+
const {
|
|
30
|
+
apiKey,
|
|
31
|
+
model = 'mixtral-8x7b-32768',
|
|
32
|
+
systemPrompt = DEFAULT_SYSTEM_PROMPT,
|
|
33
|
+
maxTokens = 50,
|
|
34
|
+
temperature = 0.3
|
|
35
|
+
} = config
|
|
36
|
+
|
|
37
|
+
let isConfigured = !!apiKey
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
id: 'groq',
|
|
41
|
+
name: 'Groq LLM',
|
|
42
|
+
|
|
43
|
+
configure(newConfig: Record<string, any>) {
|
|
44
|
+
if (newConfig.apiKey) {
|
|
45
|
+
isConfigured = true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
isReady() {
|
|
50
|
+
return isConfigured
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async getSuggestions(
|
|
54
|
+
input: string,
|
|
55
|
+
context?: AutocompleteContext
|
|
56
|
+
): Promise<AutocompleteResult> {
|
|
57
|
+
if (!isConfigured) {
|
|
58
|
+
console.warn('[Groq Plugin] API key not configured')
|
|
59
|
+
return { type: 'completion', completion: input }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!input.trim()) {
|
|
63
|
+
return { type: 'completion', completion: '' }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Build context-aware prompt
|
|
68
|
+
let userPrompt = `Complete this text: "${input}"`
|
|
69
|
+
|
|
70
|
+
if (context?.fieldName) {
|
|
71
|
+
userPrompt = `Field: ${context.fieldName}\nComplete this text: "${input}"`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (context?.formData && Object.keys(context.formData).length > 0) {
|
|
75
|
+
const formContext = Object.entries(context.formData)
|
|
76
|
+
.filter(([key, value]) => value && key !== context.fieldName)
|
|
77
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
78
|
+
.join(', ')
|
|
79
|
+
|
|
80
|
+
if (formContext) {
|
|
81
|
+
userPrompt = `Form context: ${formContext}\n${userPrompt}`
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
'Authorization': `Bearer ${apiKey}`
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
model,
|
|
93
|
+
messages: [
|
|
94
|
+
{ role: 'system', content: systemPrompt },
|
|
95
|
+
{ role: 'user', content: userPrompt }
|
|
96
|
+
],
|
|
97
|
+
max_tokens: maxTokens,
|
|
98
|
+
temperature,
|
|
99
|
+
stream: false
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
const errorText = await response.text()
|
|
105
|
+
console.error('[Groq Plugin] API error:', response.status, errorText)
|
|
106
|
+
return { type: 'completion', completion: input }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = await response.json()
|
|
110
|
+
const completion = data.choices?.[0]?.message?.content?.trim() || input
|
|
111
|
+
|
|
112
|
+
// Clean up the completion (remove quotes if present)
|
|
113
|
+
let cleanCompletion = completion
|
|
114
|
+
if (cleanCompletion.startsWith('"') && cleanCompletion.endsWith('"')) {
|
|
115
|
+
cleanCompletion = cleanCompletion.slice(1, -1)
|
|
116
|
+
}
|
|
117
|
+
if (cleanCompletion.startsWith("'") && cleanCompletion.endsWith("'")) {
|
|
118
|
+
cleanCompletion = cleanCompletion.slice(1, -1)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
type: 'completion',
|
|
123
|
+
completion: cleanCompletion
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('[Groq Plugin] Error:', error)
|
|
127
|
+
return { type: 'completion', completion: input }
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
dispose() {
|
|
132
|
+
// No cleanup needed
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default createGroqPlugin
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete Plugins Index
|
|
3
|
+
* Re-exports all available autocomplete plugins
|
|
4
|
+
*
|
|
5
|
+
* Sprint Autocomplete Feature
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { createGroqPlugin } from './groq'
|
|
9
|
+
export { createSupabasePlugin } from './supabase'
|
|
10
|
+
export { createRestPlugin } from './rest'
|
|
11
|
+
|
|
12
|
+
// DuckDB plugin is exported separately due to WASM dependencies
|
|
13
|
+
// Import directly from './duckdb' when needed:
|
|
14
|
+
// import { createDuckDBPlugin, preloadDuckDB } from '@seed-ship/mcp-ui-solid/plugins/duckdb'
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST API Autocomplete Plugin
|
|
3
|
+
* Provides suggestions from any REST API endpoint
|
|
4
|
+
*
|
|
5
|
+
* Sprint Autocomplete Feature
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
AutocompletePlugin,
|
|
10
|
+
AutocompleteResult,
|
|
11
|
+
AutocompleteContext,
|
|
12
|
+
AutocompleteOption,
|
|
13
|
+
RestPluginConfig
|
|
14
|
+
} from '../types'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get value at path (e.g., "data.results" -> obj.data.results)
|
|
18
|
+
*/
|
|
19
|
+
function getByPath(obj: any, path: string): any {
|
|
20
|
+
return path.split('.').reduce((current, key) => current?.[key], obj)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default transform function
|
|
25
|
+
*/
|
|
26
|
+
function defaultTransform(
|
|
27
|
+
data: any[],
|
|
28
|
+
valueField: string,
|
|
29
|
+
labelField: string
|
|
30
|
+
): AutocompleteOption[] {
|
|
31
|
+
return data.map(item => ({
|
|
32
|
+
value: String(item[valueField] ?? item.value ?? item.id ?? ''),
|
|
33
|
+
label: String(item[labelField] ?? item.label ?? item.name ?? item[valueField] ?? ''),
|
|
34
|
+
metadata: item
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a REST API autocomplete plugin
|
|
40
|
+
*/
|
|
41
|
+
export function createRestPlugin(config: RestPluginConfig): AutocompletePlugin {
|
|
42
|
+
const {
|
|
43
|
+
endpoint,
|
|
44
|
+
method = 'GET',
|
|
45
|
+
headers = {},
|
|
46
|
+
bodyTemplate,
|
|
47
|
+
transform,
|
|
48
|
+
resultPath,
|
|
49
|
+
valueField = 'value',
|
|
50
|
+
labelField = 'label'
|
|
51
|
+
} = config
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
id: 'rest',
|
|
55
|
+
name: 'REST API',
|
|
56
|
+
|
|
57
|
+
configure(newConfig: Record<string, any>) {
|
|
58
|
+
Object.assign(config, newConfig)
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
isReady() {
|
|
62
|
+
return !!endpoint
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async getSuggestions(
|
|
66
|
+
input: string,
|
|
67
|
+
_context?: AutocompleteContext
|
|
68
|
+
): Promise<AutocompleteResult> {
|
|
69
|
+
if (!endpoint) {
|
|
70
|
+
console.warn('[REST Plugin] Endpoint not configured')
|
|
71
|
+
return { type: 'options', options: [] }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!input.trim()) {
|
|
75
|
+
return { type: 'options', options: [] }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Build URL with search placeholder
|
|
80
|
+
const url = endpoint.replace(/\{search\}/g, encodeURIComponent(input))
|
|
81
|
+
|
|
82
|
+
// Build request options
|
|
83
|
+
const requestOptions: RequestInit = {
|
|
84
|
+
method,
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
...headers
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add body for POST requests
|
|
92
|
+
if (method === 'POST' && bodyTemplate) {
|
|
93
|
+
const body = bodyTemplate.replace(/\{search\}/g, input)
|
|
94
|
+
requestOptions.body = body
|
|
95
|
+
} else if (method === 'POST') {
|
|
96
|
+
requestOptions.body = JSON.stringify({ search: input })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const response = await fetch(url, requestOptions)
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const errorText = await response.text()
|
|
103
|
+
console.error('[REST Plugin] API error:', response.status, errorText)
|
|
104
|
+
return { type: 'options', options: [] }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = await response.json()
|
|
108
|
+
|
|
109
|
+
// Extract results from path if specified
|
|
110
|
+
let results = resultPath ? getByPath(data, resultPath) : data
|
|
111
|
+
|
|
112
|
+
// Ensure results is an array
|
|
113
|
+
if (!Array.isArray(results)) {
|
|
114
|
+
if (results && typeof results === 'object') {
|
|
115
|
+
// Try common result structures
|
|
116
|
+
results = results.data || results.results || results.items || [results]
|
|
117
|
+
} else {
|
|
118
|
+
results = []
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Transform results to options
|
|
123
|
+
let options: AutocompleteOption[]
|
|
124
|
+
|
|
125
|
+
if (transform) {
|
|
126
|
+
options = transform(results)
|
|
127
|
+
} else {
|
|
128
|
+
options = defaultTransform(results, valueField, labelField)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
type: 'options',
|
|
133
|
+
options
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('[REST Plugin] Error:', error)
|
|
137
|
+
return { type: 'options', options: [] }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
dispose() {
|
|
142
|
+
// No cleanup needed
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default createRestPlugin
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Autocomplete Plugin
|
|
3
|
+
* Provides data-driven suggestions from a Supabase table
|
|
4
|
+
*
|
|
5
|
+
* Sprint Autocomplete Feature
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
AutocompletePlugin,
|
|
10
|
+
AutocompleteResult,
|
|
11
|
+
AutocompleteContext,
|
|
12
|
+
AutocompleteOption,
|
|
13
|
+
SupabasePluginConfig
|
|
14
|
+
} from '../types'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a Supabase autocomplete plugin
|
|
18
|
+
*/
|
|
19
|
+
export function createSupabasePlugin(config: SupabasePluginConfig): AutocompletePlugin {
|
|
20
|
+
const {
|
|
21
|
+
url,
|
|
22
|
+
anonKey,
|
|
23
|
+
table,
|
|
24
|
+
column,
|
|
25
|
+
searchColumn,
|
|
26
|
+
labelColumn,
|
|
27
|
+
limit = 10,
|
|
28
|
+
filter
|
|
29
|
+
} = config
|
|
30
|
+
|
|
31
|
+
const isConfigured = !!(url && anonKey && table && column)
|
|
32
|
+
const effectiveSearchColumn = searchColumn || column
|
|
33
|
+
const effectiveLabelColumn = labelColumn || column
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
id: 'supabase',
|
|
37
|
+
name: 'Supabase',
|
|
38
|
+
|
|
39
|
+
configure(newConfig: Record<string, any>) {
|
|
40
|
+
// Allow runtime reconfiguration
|
|
41
|
+
Object.assign(config, newConfig)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
isReady() {
|
|
45
|
+
return isConfigured
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async getSuggestions(
|
|
49
|
+
input: string,
|
|
50
|
+
_context?: AutocompleteContext
|
|
51
|
+
): Promise<AutocompleteResult> {
|
|
52
|
+
if (!isConfigured) {
|
|
53
|
+
console.warn('[Supabase Plugin] Not properly configured')
|
|
54
|
+
return { type: 'options', options: [] }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!input.trim()) {
|
|
58
|
+
return { type: 'options', options: [] }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Build query URL
|
|
63
|
+
let queryUrl = `${url}/rest/v1/${table}?select=${column}`
|
|
64
|
+
|
|
65
|
+
if (column !== effectiveLabelColumn) {
|
|
66
|
+
queryUrl += `,${effectiveLabelColumn}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add ILIKE filter for search
|
|
70
|
+
queryUrl += `&${effectiveSearchColumn}=ilike.${encodeURIComponent(input)}%`
|
|
71
|
+
|
|
72
|
+
// Add limit
|
|
73
|
+
queryUrl += `&limit=${limit}`
|
|
74
|
+
|
|
75
|
+
// Add custom filters
|
|
76
|
+
if (filter) {
|
|
77
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
78
|
+
queryUrl += `&${key}=eq.${encodeURIComponent(String(value))}`
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const response = await fetch(queryUrl, {
|
|
83
|
+
headers: {
|
|
84
|
+
'apikey': anonKey,
|
|
85
|
+
'Authorization': `Bearer ${anonKey}`,
|
|
86
|
+
'Content-Type': 'application/json'
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const errorText = await response.text()
|
|
92
|
+
console.error('[Supabase Plugin] API error:', response.status, errorText)
|
|
93
|
+
return { type: 'options', options: [] }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = await response.json()
|
|
97
|
+
|
|
98
|
+
const options: AutocompleteOption[] = data.map((row: Record<string, any>) => ({
|
|
99
|
+
value: String(row[column]),
|
|
100
|
+
label: String(row[effectiveLabelColumn]),
|
|
101
|
+
metadata: row
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
type: 'options',
|
|
106
|
+
options
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('[Supabase Plugin] Error:', error)
|
|
110
|
+
return { type: 'options', options: [] }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
dispose() {
|
|
115
|
+
// No cleanup needed
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default createSupabasePlugin
|