Skip to content

Commit b95ea13

Browse files
committed
2.17.3: feat: switch from internal embeddings to open source another-ai/embedbase
1 parent ce23b7c commit b95ea13

File tree

7 files changed

+159
-208
lines changed

7 files changed

+159
-208
lines changed

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "ava",
33
"name": "🧙 AVA",
4-
"version": "2.17.2",
4+
"version": "2.17.3",
55
"minAppVersion": "0.12.0",
66
"description": "AI assistant for Obsidian",
77
"author": "louis030195",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ava",
3-
"version": "2.17.2",
3+
"version": "2.17.3",
44
"description": "AI assistant for Obsidian",
55
"main": "main.js",
66
"scripts": {

src/LinkComponent.tsx

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
2+
import { Notice } from 'obsidian';
23
import { posthog } from 'posthog-js';
34
import * as React from 'react';
45
import { useForm } from 'react-hook-form';
56
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
67
import { PrimaryButton } from './Button';
7-
import { API_HOST } from './constants';
8+
import { EMBEDBASE_URL } from './constants';
89
import { CopyToClipboardButton } from './CopyToClipboard';
910
import { useApp } from './hooks';
1011
import { InsertButton } from './InsertButton';
1112
import { store } from './store';
12-
import { camelize, ISearchBody } from './utils';
13+
import { camelize } from './utils';
1314

1415
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
1516
children: React.ReactNode;
@@ -90,34 +91,40 @@ const ControlForm = () => {
9091
const state = React.useSyncExternalStore(store.subscribe, store.getState);
9192

9293
const onSubmit = async (data: { limit: number; useNoteTitle: boolean }) => {
94+
if (!state.settings.useLinks) {
95+
new Notice('🧙 Link - You need to enable links in settings', 3000);
96+
return;
97+
}
98+
if (!state.settings.token) {
99+
new Notice('🧙 You need to login to use this feature', 3000);
100+
return;
101+
}
93102
posthog.capture('use-feature', {
94103
feature: 'search',
95104
limit: data.limit,
96105
});
97106
state.setEmbedsLoading(true);
98107

99-
const body: ISearchBody = {
100-
// parenthese are needed for the ternary operator to work
101-
query:
102-
(data.useNoteTitle ? `File:\n${state.currentFilePath}\n` : '') +
103-
`Content:\n${state.currentFileContent}`,
104-
vault_id: state.settings.vaultId,
105-
top_k: Number(data.limit),
106-
};
107-
const url = posthog.isFeatureEnabled('new-search') ?
108-
`https://obsidian-search-dev-c6txy76x2q-uc.a.run.app/v1/search` :
109-
`${API_HOST}/v1/search`;
110-
const response = await fetch(url, {
111-
method: 'POST',
112-
headers: {
113-
'Content-Type': 'application/json',
114-
Authorization: `Bearer ${state.settings.token}`,
115-
'X-Client-Version': state.version,
116-
},
117-
body: JSON.stringify(body),
118-
}).then((res) => res.json());
108+
const response = await fetch(`${EMBEDBASE_URL}/v1/${state.settings.vaultId}/search`, {
109+
method: 'POST',
110+
headers: {
111+
'Content-Type': 'application/json',
112+
Authorization: `Bearer ${state.settings.token}`,
113+
'X-Client-Version': state.version,
114+
},
115+
body: JSON.stringify({
116+
// parenthese are needed for the ternary operator to work
117+
query:
118+
(data.useNoteTitle ? `File:\n${state.currentFilePath}\n` : '') +
119+
`Content:\n${state.currentFileContent}`,
120+
top_k: Number(data.limit),
121+
}),
122+
}).then((res) => res.json());
119123

120-
const embeds = camelize(response.similarities);
124+
const embeds = camelize(response.similarities.filter(
125+
(similarity: any) =>
126+
similarity.id !== state.currentFilePath
127+
));
121128

122129
state.setEmbedsLoading(false);
123130
/* @ts-expect-error need to work on better types */
@@ -194,7 +201,7 @@ export function LinkComponent() {
194201
// get the top value
195202
const topValue = embeds[0].score;
196203
const results = embeds.map((embed) => {
197-
const [path, similarity] = [embed.notePath, embed.score];
204+
const [path, similarity] = [embed.id, embed.score];
198205
const opacity = sCurve(similarity, threshold, topValue);
199206

200207
return {
@@ -206,7 +213,6 @@ export function LinkComponent() {
206213
});
207214
setResults(results);
208215
}, [embeds, threshold]);
209-
210216
const textToInsert = `${results
211217
.map((similarity) => '- [[' + similarity?.path?.replace('.md', '') + ']]')
212218
.join('\n')}`;

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// TODO: firebase hosting custom domain name does not work with SSE somehow?
22
export const API_HOST = process.env.API_HOST || "https://obsidian-ai-c6txy76x2q-uc.a.run.app";
3+
export const EMBEDBASE_URL = "https://embedbase-internal-c6txy76x2q-uc.a.run.app";
34

45
export const buildHeaders = (token: string, version: string) => ({
56
'Content-Type': 'application/json',

src/main.tsx

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
Plugin,
99
PluginSettingTab,
1010
TFile,
11-
WorkspaceLeaf,
11+
WorkspaceLeaf
1212
} from 'obsidian';
1313

1414
import * as React from 'react';
@@ -17,26 +17,24 @@ import { CustomSettings } from './Settings';
1717
import {
1818
createImage,
1919
RequestImageCreate,
20-
ResponseImageCreate,
20+
ResponseImageCreate
2121
} from './stableDiffusion';
2222
import { StatusBar } from './StatusBar';
2323
import {
2424
clearIndex,
2525
complete,
2626
createParagraph,
2727
createSemanticLinks,
28+
deleteFromIndex,
2829
EMBED_CHAR_LIMIT,
2930
getCompleteFiles,
3031
getLinkData,
3132
getVaultId,
3233
ICompletion,
33-
ISearchData,
34-
ISearchRequest,
35-
refreshSemanticSearch,
36-
rewrite,
34+
ISearchRequest, ISearchResponse, rewrite,
3735
REWRITE_CHAR_LIMIT as TEXT_CREATE_CHAR_LIMIT,
3836
search,
39-
suggestTags,
37+
suggestTags, syncIndex
4038
} from './utils';
4139

4240
import posthog from 'posthog-js';
@@ -135,7 +133,7 @@ export default class AvaPlugin extends Plugin {
135133
```
136134
*/
137135

138-
public search: (request: ISearchRequest) => Promise<ISearchData>;
136+
public search: (request: ISearchRequest) => Promise<ISearchResponse>;
139137
public clearIndex: () => Promise<any>;
140138
private eventRefRenamed: EventRef;
141139
private eventRefDeleted: EventRef;
@@ -148,7 +146,7 @@ export default class AvaPlugin extends Plugin {
148146
this.streamingSource = source;
149147
}
150148

151-
private async link(currentText: string, tags: string[], path: string) {
149+
private async link(currentText: string, path: string) {
152150
if (currentText.length > EMBED_CHAR_LIMIT) {
153151
new Notice(
154152
'Link - Note is too long. 🧙 AVA only supports notes that are up to 25k characters'
@@ -160,10 +158,9 @@ export default class AvaPlugin extends Plugin {
160158
completion = await createSemanticLinks(
161159
path,
162160
currentText,
163-
tags,
164161
this.settings?.token,
165162
this.settings.vaultId,
166-
this.manifest.version
163+
this.manifest.version,
167164
);
168165

169166
this.statusBarItem.render(<StatusBar status="disabled" />);
@@ -238,11 +235,10 @@ export default class AvaPlugin extends Plugin {
238235
return acc;
239236
}, [])
240237
.map((batch) =>
241-
refreshSemanticSearch(
238+
syncIndex(
242239
batch.map((file: any) => ({
243-
notePath: file.path,
244-
noteTags: file.tags,
245-
noteContent: file.content,
240+
id: file.path,
241+
data: `File:\n${file.path}\nContent:\n${file.content}`
246242
})),
247243
this.settings?.token,
248244
this.settings?.vaultId,
@@ -312,12 +308,11 @@ export default class AvaPlugin extends Plugin {
312308
if (!cache) return setLastFile(leaf);
313309
this.app.vault.adapter.read(this.lastFile.path).then((data) => {
314310
if (!this.lastFile) return setLastFile(leaf);
315-
refreshSemanticSearch(
311+
syncIndex(
316312
[
317313
{
318-
notePath: this.lastFile.path,
319-
noteTags: cache.tags?.map((tag) => tag.tag) || [],
320-
noteContent: data,
314+
id: this.lastFile.path,
315+
data: `File:\n${this.lastFile.path}\nContent:\n${data}`
321316
},
322317
],
323318
this.settings?.token,
@@ -344,18 +339,20 @@ export default class AvaPlugin extends Plugin {
344339
const f = file as TFile;
345340
// if file in ignored folder, ignore
346341
if (isIgnored(this.settings?.ignoredFolders, f.path)) return;
342+
347343
try {
344+
if (oldPath) {
345+
deleteFromIndex([oldPath], this.settings?.token, this.settings?.vaultId, this.manifest.version);
346+
}
348347
if (!this.settings.useLinks) {
349348
this.unlistenToNoteEvents();
350349
return;
351350
}
352-
refreshSemanticSearch(
351+
syncIndex(
353352
[
354353
{
355-
notePath: f.path,
356-
noteTags: cache.tags?.map((tag) => tag.tag) || [],
357-
noteContent: data,
358-
pathToDelete: oldPath,
354+
id: f.path,
355+
data: `File:\n${f.path}\nContent:\n${data}`
359356
},
360357
],
361358
this.settings?.token,
@@ -377,16 +374,7 @@ export default class AvaPlugin extends Plugin {
377374
}
378375
// if file in ignored folder, ignore
379376
if (isIgnored(this.settings?.ignoredFolders, file.path)) return;
380-
refreshSemanticSearch(
381-
[
382-
{
383-
pathToDelete: file.path,
384-
},
385-
],
386-
this.settings?.token,
387-
this.settings.vaultId,
388-
this.manifest.version
389-
);
377+
deleteFromIndex([file.path], this.settings?.token, this.settings?.vaultId, this.manifest.version);
390378
} catch (e) {
391379
onGeneralError(e);
392380
this.unlistenToNoteEvents();
@@ -442,18 +430,15 @@ export default class AvaPlugin extends Plugin {
442430

443431
const file = this.app.workspace.getActiveFile();
444432
const currentText = await this.app.vault.read(file);
445-
const tags = this.app.metadataCache.getFileCache(file).tags || [];
446-
const tagsArray = tags.map((tag) => tag.tag);
447433
const path = file.path;
448434

449435
// we need to do this so we can fire /search inside of the sidebar later
450436
store.setState({
451437
currentFileContent: currentText,
452438
currentFilePath: path,
453-
currentFileTags: tagsArray,
454439
});
455440

456-
const results = await this.link(currentText, tagsArray, path);
441+
const results = await this.link(currentText, path);
457442
if (results) {
458443
store.setState({ embeds: results });
459444
}
@@ -523,7 +508,7 @@ export default class AvaPlugin extends Plugin {
523508
req,
524509
this.settings.token,
525510
this.settings.vaultId,
526-
this.manifest.version
511+
this.manifest.version,
527512
);
528513
this.clearIndex = () =>
529514
clearIndex(
@@ -877,6 +862,8 @@ export default class AvaPlugin extends Plugin {
877862
onunload(): void {
878863
this.app.workspace.detachLeavesOfType(VIEW_TYPE_LINK);
879864
this.app.workspace.detachLeavesOfType(VIEW_TYPE_WRITE);
865+
// TODO: skip when local development (annoying have to index every time I change a line of code)
866+
// TODO: careful not using node stuff for mobile?
880867
this.unlistenToNoteEvents();
881868
}
882869
}

0 commit comments

Comments
 (0)