@meaningfully/ui 0.0.8 → 0.0.10

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/dist/App.svelte CHANGED
@@ -64,20 +64,20 @@
64
64
 
65
65
  <main class="container mx-auto px-4 py-8">
66
66
  <Route path="">
67
- <FrontPage validApiKeysSet={validApiKeysSet} api={api} />
67
+ <FrontPage validApiKeysSet={validApiKeysSet} api={api} basepath={basepath_app} />
68
68
  </Route>
69
69
  <Route path="configure-upload">
70
- <DatabaseConfig validApiKeysSet={validApiKeysSet} api={api} />
70
+ <DatabaseConfig validApiKeysSet={validApiKeysSet} api={api} basepath={basepath_app} />
71
71
  </Route>
72
72
  <Route path="search/:id" let:params>
73
- <SearchPage validApiKeysSet={validApiKeysSet} documentSetId={Number(params.id)} api={api} />
73
+ <SearchPage validApiKeysSet={validApiKeysSet} documentSetId={Number(params.id)} api={api} basepath={basepath_app} />
74
74
  </Route>
75
75
  <Route path="help">
76
76
  <HelpPage />
77
77
  </Route>
78
78
  <Route path="settings">
79
79
  {#if settings}
80
- <ApiKeyPage settings={settings} settingsUpdated={() => getSettings() } api={api} />
80
+ <ApiKeyPage settings={settings} settingsUpdated={() => getSettings() } api={api} basepath={basepath_app} />
81
81
  {/if}
82
82
  </Route>
83
83
  </main>
@@ -6,9 +6,10 @@
6
6
  settings: Settings;
7
7
  settingsUpdated: () => void;
8
8
  api: MeaningfullyAPI;
9
+ basepath: string;
9
10
  }
10
11
 
11
- let { settings, settingsUpdated, api }: Props = $props();
12
+ let { settings, settingsUpdated, api, basepath }: Props = $props();
12
13
  let openAIKey: string = $state(settings.openAIKey);
13
14
  let oLlamaBaseURL: string = $state(settings.oLlamaBaseURL);
14
15
  let azureOpenAIKey: string = $state(settings.azureOpenAIKey);
@@ -34,7 +35,7 @@
34
35
  throw new Error('Failed to save settings');
35
36
  }
36
37
  settingsUpdated();
37
- navigate("/");
38
+ navigate(basepath.replace(/\/+$/g, "") + "/");
38
39
  } catch (error) {
39
40
  console.error(error);
40
41
  alert('Error saving settings');
@@ -3,6 +3,7 @@ interface Props {
3
3
  settings: Settings;
4
4
  settingsUpdated: () => void;
5
5
  api: MeaningfullyAPI;
6
+ basepath: string;
6
7
  }
7
8
  declare const ApiKeyPage: import("svelte").Component<Props, {}, "">;
8
9
  type ApiKeyPage = ReturnType<typeof ApiKeyPage>;
@@ -4,7 +4,8 @@
4
4
  import { fileDataStore } from '../stores/fileDataStore.js';
5
5
 
6
6
  let {
7
- validApiKeysSet
7
+ validApiKeysSet,
8
+ basepath
8
9
  } = $props();
9
10
 
10
11
  let error = $state('');
@@ -57,7 +58,8 @@
57
58
  fileContent
58
59
  });
59
60
  // Navigate to configuration page
60
- navigate('/configure-upload');
61
+ navigate(basepath.replace(/\/+$/g, "") + "/configure-upload");
62
+
61
63
  };
62
64
  reader.readAsDataURL(file);
63
65
  },
@@ -1,5 +1,6 @@
1
1
  declare const CsvUpload: import("svelte").Component<{
2
2
  validApiKeysSet: any;
3
+ basepath: any;
3
4
  }, {}, "">;
4
5
  type CsvUpload = ReturnType<typeof CsvUpload>;
5
6
  export default CsvUpload;
@@ -13,7 +13,8 @@
13
13
 
14
14
  let {
15
15
  validApiKeysSet,
16
- api
16
+ api,
17
+ basepath
17
18
  } = $props();
18
19
 
19
20
  let fileData: any = $state(null);
@@ -28,14 +29,9 @@
28
29
  let chunkSize = $state(defaultChunkSize);
29
30
  let chunkOverlap = $state(defaultChunkOverlap);
30
31
 
31
- // Model options grouped by provider
32
- const modelOptions: Record<string, string[]> = {
33
- "openai": ["text-embedding-3-small", "text-embedding-3-large"],
34
- "azure": ["text-embedding-3-small", "text-embedding-3-large"],
35
- "ollama": ["mxbai-embed-large", "nomic-embed-text"],
36
- "mistral": ["mistral-embed"],
37
- "gemini": ["gemini-embedding-001"]
38
- };
32
+ // Model options grouped by provider - fetched from API
33
+ let availableModelOptions: Record<string, string[]> = $state({});
34
+ let allModelOptions: Record<string, string[]> = $state({});
39
35
 
40
36
  let modelProvider = $state("openai");
41
37
  let modelName = $state("text-embedding-3-small");
@@ -49,8 +45,8 @@
49
45
 
50
46
  // Update model name when provider changes
51
47
  $effect(() => {
52
- if (modelProvider && modelOptions[modelProvider]) {
53
- modelName = modelOptions[modelProvider][0];
48
+ if (modelProvider && availableModelOptions[modelProvider]) {
49
+ modelName = availableModelOptions[modelProvider][0];
54
50
  }
55
51
  });
56
52
 
@@ -77,11 +73,27 @@
77
73
  return result;
78
74
  };
79
75
 
80
- onMount(() => {
76
+ onMount(async () => {
77
+ // Fetch available model options from API
78
+ try {
79
+ const modelOptionsData = await api.getAvailableModelOptions();
80
+ availableModelOptions = modelOptionsData.availableModelOptions;
81
+ allModelOptions = modelOptionsData.allModelOptions;
82
+
83
+ // Set default provider to first available one
84
+ const availableProviders = Object.keys(availableModelOptions);
85
+ if (availableProviders.length > 0) {
86
+ modelProvider = availableProviders[0];
87
+ modelName = availableModelOptions[modelProvider][0];
88
+ }
89
+ } catch (e) {
90
+ console.error('Error fetching available model options:', e);
91
+ }
92
+
81
93
  // Subscribe to the file data store
82
- const unsubscribe = fileDataStore.subscribe((data) => {
94
+ const unsubscribe = fileDataStore.subscribe((data: any) => {
83
95
  if (!data) {
84
- navigate('/'); // Redirect back to home if no file data
96
+ navigate(basepath.replace(/\/+$/g, "") + "/"); // Redirect back to home if no file data
85
97
  return;
86
98
  }
87
99
  fileData = data;
@@ -187,7 +199,8 @@
187
199
  });
188
200
 
189
201
  if (uploadResponse.success) {
190
- navigate("/search/" + uploadResponse.documentSetId);
202
+ navigate(basepath.replace(/\/+$/g, "") + "/search/" + uploadResponse.documentSetId);
203
+
191
204
  } else {
192
205
  error = uploadResponse.message || 'Upload failed'; // fastify responses don't throw
193
206
  }
@@ -237,7 +250,7 @@
237
250
  });
238
251
 
239
252
  const goBack = () => {
240
- navigate('/');
253
+ navigate(basepath.replace(/\/+$/g, "") + "/");
241
254
  };
242
255
  </script>
243
256
 
@@ -282,15 +295,15 @@
282
295
  <select
283
296
  bind:value={modelProvider}
284
297
  class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-violet-500 focus:ring-violet-500">
285
- <option value="openai">OpenAI</option>
286
- <option value="azure">Azure OpenAI</option>
287
- <option value="ollama">Ollama</option>
288
- <option value="mistral">Mistral AI</option>
289
- <option value="gemini">Google Gemini</option>
298
+ <option value="openai" disabled={!availableModelOptions.openai}>OpenAI</option>
299
+ <option value="azure" disabled={!availableModelOptions.azure}>Azure OpenAI</option>
300
+ <option value="ollama" disabled={!availableModelOptions.ollama}>Ollama</option>
301
+ <option value="mistral" disabled={!availableModelOptions.mistral}>Mistral AI</option>
302
+ <option value="gemini" disabled={!availableModelOptions.gemini}>Google Gemini</option>
290
303
  </select>
291
304
  </label>
292
305
  <p class="text-xs text-gray-500">
293
- The provider for the embedding model.
306
+ The provider for the embedding model. {Object.keys(availableModelOptions).length === 0 ? 'No providers are configured yet. Please set up API keys in the settings.' : ''}
294
307
  </p>
295
308
  </div>
296
309
 
@@ -299,8 +312,9 @@
299
312
  What embedding model should we use?
300
313
  <select
301
314
  bind:value={modelName}
302
- class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-violet-500 focus:ring-violet-500">
303
- {#each modelOptions[modelProvider] as modelNameChoice}
315
+ disabled={!availableModelOptions[modelProvider]}
316
+ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-violet-500 focus:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed">
317
+ {#each (availableModelOptions[modelProvider] || allModelOptions[modelProvider] || []) as modelNameChoice}
304
318
  <option value={modelNameChoice}>{modelNameChoice}</option>
305
319
  {/each}
306
320
  </select>
@@ -1,6 +1,7 @@
1
1
  declare const DatabaseConfig: import("svelte").Component<{
2
2
  validApiKeysSet: any;
3
3
  api: any;
4
+ basepath: any;
4
5
  }, {}, "">;
5
6
  type DatabaseConfig = ReturnType<typeof DatabaseConfig>;
6
7
  export default DatabaseConfig;
@@ -7,13 +7,14 @@
7
7
  interface Props {
8
8
  validApiKeysSet: boolean;
9
9
  api: MeaningfullyAPI;
10
+ basepath: string;
10
11
  }
11
12
 
12
- let { validApiKeysSet, api }: Props = $props();
13
+ let { validApiKeysSet, api, basepath }: Props = $props();
13
14
  </script>
14
15
 
15
16
  <div class="container mx-auto px-4 space-y-8">
16
- <CsvUpload validApiKeysSet={validApiKeysSet} />
17
+ <CsvUpload validApiKeysSet={validApiKeysSet} basepath={basepath} />
17
18
  <ExistingDatabases api={api} />
18
19
  <div class="max-w-2xl mx-auto text-left leading-tight space-y-2">
19
20
  Meaningfully is a semantic search tool for text data in spreadsheets. Search by the meaning of a sentence instead of keywords. <Link to="help" class="nav-link underline text-blue-600 hover:text-blue-800 visited:text-purple-600">Learn more</Link>.
@@ -2,6 +2,7 @@ import type { MeaningfullyAPI } from '../types';
2
2
  interface Props {
3
3
  validApiKeysSet: boolean;
4
4
  api: MeaningfullyAPI;
5
+ basepath: string;
5
6
  }
6
7
  declare const FrontPage: import("svelte").Component<Props, {}, "">;
7
8
  type FrontPage = ReturnType<typeof FrontPage>;
@@ -95,8 +95,10 @@
95
95
  link.setAttribute('download', 'results.csv');
96
96
  document.body.appendChild(link);
97
97
  link.click();
98
- document.body.removeChild(link);
99
- URL.revokeObjectURL(url);
98
+ setTimeout(() => {
99
+ document.body.removeChild(link);
100
+ URL.revokeObjectURL(url);
101
+ }, 5000);
100
102
  };
101
103
 
102
104
  // Computed property for visible results
@@ -111,6 +113,7 @@
111
113
  onclick={downloadCSV}
112
114
  class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors flex items-center gap-2"
113
115
  title="Download results as CSV"
116
+ data-testid="download-csv"
114
117
  >
115
118
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
116
119
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { navigate } from 'svelte-routing';
2
+ import { navigate, Link } from 'svelte-routing';
3
3
  import type { DocumentSet, MeaningfullyAPI } from '../types.js';
4
4
  import Results from './Results.svelte';
5
5
 
@@ -7,9 +7,10 @@
7
7
  validApiKeysSet: boolean;
8
8
  documentSetId: number;
9
9
  api: MeaningfullyAPI;
10
+ basepath: string;
10
11
  }
11
12
 
12
- let { validApiKeysSet, documentSetId, api }: Props = $props();
13
+ let { validApiKeysSet, documentSetId, api, basepath }: Props = $props();
13
14
 
14
15
  let documentSet: DocumentSet | null = $state(null);
15
16
  let documentSetLoading = $state(true);
@@ -36,7 +37,7 @@
36
37
  documentSetLoading = false;
37
38
  }).catch(error => {
38
39
  console.error('Error fetching document set:', error);
39
- navigate('/');
40
+ navigate(basepath.replace(/\/+$/g, "") + "/");
40
41
  });
41
42
 
42
43
  const placeholderQueries = [
@@ -105,15 +106,15 @@
105
106
 
106
107
  <div class="p-6 space-y-6">
107
108
  <div class="flex items-center space-x-4">
108
- <button
109
+ <Link
109
110
  class="text-blue-500 hover:text-blue-600 flex items-center space-x-1"
110
- onclick={() => navigate('/') }
111
+ to=""
111
112
  >
112
113
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
113
114
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
114
115
  </svg>
115
116
  <span>Back to Home</span>
116
- </button>
117
+ </Link>
117
118
  </div>
118
119
 
119
120
  {#if documentSetLoading}
@@ -253,7 +254,7 @@
253
254
  {/each}
254
255
  </tbody>
255
256
  </table>
256
- <button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" onclick={closeModal}>Close</button>
257
+ <button data-testid="modal-close-button" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" onclick={closeModal}>Close</button>
257
258
  </div>
258
259
  </div>
259
260
  {/if}
@@ -3,6 +3,7 @@ interface Props {
3
3
  validApiKeysSet: boolean;
4
4
  documentSetId: number;
5
5
  api: MeaningfullyAPI;
6
+ basepath: string;
6
7
  }
7
8
  declare const SearchPage: import("svelte").Component<Props, {}, "">;
8
9
  type SearchPage = ReturnType<typeof SearchPage>;
package/dist/types.d.ts CHANGED
@@ -57,4 +57,8 @@ export interface MeaningfullyAPI {
57
57
  elapsedTimeMs: number;
58
58
  estimatedTimeRemainingMs: number | null;
59
59
  }>;
60
+ getAvailableModelOptions: () => Promise<{
61
+ availableModelOptions: Record<string, string[]>;
62
+ allModelOptions: Record<string, string[]>;
63
+ }>;
60
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meaningfully/ui",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "description": "Svelte components for meaningfully semantic search",
6
6
  "repo": "https://github.com/jeremybmerrill/meaningfully-ui",