Skip to content

Commit 8250938

Browse files
committed
2.18.4: feat: Ask your vault questions with the command Ava Ask
1 parent 246b42f commit 8250938

File tree

8 files changed

+148
-23
lines changed

8 files changed

+148
-23
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.18.3",
4+
"version": "2.18.4",
55
"minAppVersion": "0.12.0",
66
"description": "AI assistant for Obsidian",
77
"author": "louis030195",

package-lock.json

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ava",
3-
"version": "2.18.3",
3+
"version": "2.18.4",
44
"description": "AI assistant for Obsidian",
55
"main": "main.js",
66
"scripts": {
@@ -18,6 +18,7 @@
1818
"license": "MIT",
1919
"dependencies": {
2020
"@heroicons/react": "^2.0.13",
21+
"embeddings-splitter": "^0.0.5",
2122
"lodash": "^4.17.21",
2223
"posthog-js": "^1.37.0",
2324
"react": "^18.2.0",

src/PromptModal.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { App, Modal, Setting } from 'obsidian';
22

3+
interface Titles {
4+
heading: string;
5+
subheading: string;
6+
button: string;
7+
}
8+
const defaultTitles: Titles = {
9+
heading: 'Write a paragraph about',
10+
subheading: 'Write a paragraph about',
11+
button: 'Write paragraph',
12+
};
313
export class PromptModal extends Modal {
414
text: string;
15+
titles: Titles;
516

617
onSubmit: (text: string) => void;
718

8-
constructor(app: App, onSubmit: (text: string) => void) {
19+
constructor(app: App, onSubmit: (text: string) => void, titles: Titles = defaultTitles) {
920
super(app);
1021
this.onSubmit = onSubmit;
22+
this.titles = titles;
1123
}
1224

1325
search = (evt: Event) => {
@@ -19,10 +31,10 @@ export class PromptModal extends Modal {
1931
onOpen() {
2032
const { contentEl } = this;
2133

22-
contentEl.createEl('h1', { text: 'AVA - Paragraph Assist' });
34+
contentEl.createEl('h1', { text: this.titles.heading });
2335
const form = contentEl.createEl('form');
2436
form.onsubmit = this.search;
25-
new Setting(form).setName('Write a paragraph about').addText((text) =>
37+
new Setting(form).setName(this.titles.subheading).addText((text) =>
2638
text.setValue(this.text).onChange((value) => {
2739
this.text = value;
2840
})
@@ -31,7 +43,7 @@ export class PromptModal extends Modal {
3143
new Setting(form).addButton((btn) => {
3244
btn.buttonEl.type = 'submit';
3345

34-
return btn.setButtonText('Write paragraph').setCta().onClick(this.search);
46+
return btn.setButtonText(this.titles.button).setCta().onClick(this.search);
3547
});
3648
}
3749

src/main.tsx

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { RewriteModal } from './RewriteModal';
4949
import { store } from './store';
5050
import { tutorial } from './tutorial';
5151
import { VIEW_TYPE_WRITE, WriteView } from './writeView';
52+
import { generativeSearch } from './prompt';
5253

5354
// e.g. ["Buddhism/Veganism"]
5455
// path: "Buddhism/Veganism/2021-01-01.md" should be ignored
@@ -125,18 +126,14 @@ export default class AvaPlugin extends Plugin {
125126
* Semantically search your vault
126127
* Example:
127128
```ts
128-
const response = await search({
129-
note: {
130-
notePath: 'path/to/note.md',
131-
noteContent: 'text of the note',
132-
noteTags: ['tag1', 'tag2'],
133-
},
134-
});
129+
const response = await search(
130+
'the note disccusing the white horse of Henry VIII'
131+
);
135132
console.log(response);
136133
```
137134
*/
138135

139-
public search: (request: ISearchRequest) => Promise<ISearchResponse>;
136+
public search: (query: string) => Promise<ISearchResponse>;
140137
public clearIndex: () => Promise<any>;
141138
private eventRefRenamed: EventRef;
142139
private eventRefDeleted: EventRef;
@@ -862,6 +859,60 @@ export default class AvaPlugin extends Plugin {
862859
// can configure various aspects of the plugin
863860
this.addSettingTab(new AvaSettingTab(this.app, this));
864861
});
862+
863+
this.addCommand({
864+
id: 'ava-generative-search',
865+
name: 'Ask',
866+
editorCallback: (editor: Editor) => {
867+
posthog.capture('use-feature', { feature: 'ask' });
868+
new Notice('🧙 Asking your vault', 2000);
869+
if (!this.settings.token) {
870+
new Notice('🧙 You need to login to use this feature', 2000);
871+
return;
872+
}
873+
874+
const onSubmit = async (text: string) => {
875+
this.statusBarItem.render(<StatusBar status="loading" />);
876+
try {
877+
this.setStreamingSource(
878+
await generativeSearch(
879+
text,
880+
this.settings.token,
881+
this.settings.vaultId,
882+
this.manifest.version,
883+
)
884+
);
885+
this.streamingSource.addEventListener(
886+
'message',
887+
function (e: any) {
888+
const payload = JSON.parse(e.data);
889+
console.log(payload);
890+
const currentLine = editor.getCursor().line;
891+
const lastChar = editor.getLine(currentLine).length;
892+
editor.setCursor({ line: currentLine, ch: lastChar });
893+
editor.replaceRange(
894+
`${payload.choices[0].text}`,
895+
editor.getCursor()
896+
);
897+
}
898+
);
899+
this.streamingSource.addEventListener('error', onSSEError);
900+
this.streamingSource.stream();
901+
this.statusBarItem.render(<StatusBar status="success" />);
902+
} catch (e) {
903+
onGeneralError(e.message);
904+
new Notice(`️⛔️ AVA ${e}`, 4000);
905+
this.statusBarItem.render(<StatusBar status="error" />);
906+
}
907+
};
908+
909+
new PromptModal(this.app, onSubmit, {
910+
heading: 'Ask your vault',
911+
subheading: 'What do you want to know?',
912+
button: 'Ask',
913+
}).open();
914+
},
915+
});
865916
}
866917

867918
// eslint-disable-next-line require-jsdoc

src/prompt.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { complete, search } from "./utils";
2+
3+
// TODO: make it work on browser
4+
// import { merge } from 'embeddings-splitter';
5+
const maxLen = 1800; // TODO: probably should be more with string length
6+
// TODO: very experimental
7+
export const createContext = async (question: string, token: string, vaultId: string, version: string) => {
8+
const searchResponse = await search(question, token, vaultId, version);
9+
let curLen = 0;
10+
const returns = [];
11+
for (const similarity of searchResponse["similarities"]) {
12+
const sentence = similarity["data"];
13+
// const nTokens = enc.encode(sentence).length;
14+
const nChars = sentence.length;
15+
// curLen += nTokens + 4;
16+
curLen += nChars;
17+
if (curLen > maxLen) {
18+
break;
19+
}
20+
returns.push(sentence);
21+
}
22+
return returns.join("\n\n###\n\n");
23+
// return output;
24+
// return merge(searchResponse["similarities"].map((similarity) => similarity["data"]));
25+
}
26+
27+
export const generativeSearch = async (question: string, token: string, vaultId: string, version: string) => {
28+
const context = await createContext(question, token, vaultId, version);
29+
const newPrompt = `Answer the question based on the context below, and if the question can't be answered based on the context, say "I don't know"\n\nContext: ${context}\n\n---\n\nQuestion: ${question}\nAnswer:`;
30+
console.log('generativeSearch', newPrompt);
31+
return complete(newPrompt, token, version, {
32+
stream: true,
33+
});
34+
};

src/utils.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { SSE } from 'lib/sse';
22
import { camelCase, isArray, isObject, transform } from 'lodash';
33
import { App } from 'obsidian';
4-
import posthog from 'posthog-js';
54
import { API_HOST, buildHeaders, EMBEDBASE_URL } from './constants';
65
import AvaPlugin from './main';
76

@@ -40,11 +39,12 @@ export interface ISearchResponse {
4039
export const REWRITE_CHAR_LIMIT = 5800;
4140
export const EMBED_CHAR_LIMIT = 25000;
4241
export const search = async (
43-
request: ISearchRequest,
42+
query: string,
4443
token: string,
4544
vaultId: string,
4645
version: string,
4746
): Promise<ISearchResponse> => {
47+
console.log('Search query:', query);
4848
const response = await fetch(`${EMBEDBASE_URL}/v1/${vaultId}/search`, {
4949
method: 'POST',
5050
headers: {
@@ -53,12 +53,13 @@ export const search = async (
5353
'X-Client-Version': version,
5454
},
5555
body: JSON.stringify({
56-
query: request.query,
56+
query: query,
5757
}),
5858
}).then((res) => res.json());
5959
if (response.message) {
6060
throw new Error(`Failed to search: ${response.message}`);
6161
}
62+
console.log('Search response:', response);
6263
return response;
6364
};
6465

@@ -70,9 +71,7 @@ export const createSemanticLinks = async (
7071
version: string,
7172
) => {
7273
const response = await search(
73-
{
74-
query: `File:\n${title}\nContent:\n${text}`,
75-
},
74+
`File:\n${title}\nContent:\n${text}`,
7675
token,
7776
vaultId,
7877
version,

versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,6 @@
151151
"2.17.1": "0.12.0",
152152
"2.17.2": "0.12.0",
153153
"2.17.3": "0.12.0",
154-
"2.18.3": "0.12.0"
154+
"2.18.3": "0.12.0",
155+
"2.18.4": "0.12.0"
155156
}

0 commit comments

Comments
 (0)