From 600618455b3a95ddc2eccfe9719acbb8f71ffced Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 4 Apr 2023 00:01:35 -0700 Subject: [PATCH 01/68] update version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index ba87ce3..6463e18 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.0.0", + "version": "1.1.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 2ba4d38..b0abeeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "0.1.0", + "version": "1.1.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From e403c1f44249bbba1a17eac14de7f24225d93d22 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 19:27:42 -0700 Subject: [PATCH 02/68] fmt --- main.ts | 86 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/main.ts b/main.ts index 2ee3f6a..35175c0 100644 --- a/main.ts +++ b/main.ts @@ -106,13 +106,14 @@ export default class LoomPlugin extends Plugin { state: Record; editor: Editor; - // view: LoomView; statusBarItem: HTMLElement; openai: OpenAIApi; view(): LoomView { - return this.app.workspace.getLeavesOfType("loom").map((leaf) => leaf.view)[0] as LoomView; + return this.app.workspace + .getLeavesOfType("loom") + .map((leaf) => leaf.view)[0] as LoomView; } withFile(callback: (file: TFile) => T): T | null { @@ -236,7 +237,7 @@ export default class LoomPlugin extends Plugin { withState((state) => this.app.workspace.trigger("loom:merge-with-parent", state.current) ), - hotkeys: [{ modifiers: ["Alt"], key: "m" }] + hotkeys: [{ modifiers: ["Alt"], key: "m" }], }); this.addCommand({ @@ -430,8 +431,14 @@ export default class LoomPlugin extends Plugin { }; const updateDecorations = () => { - const nodeLengths = ancestors.map((id) => [id, this.state[view.file.path].nodes[id].text.length]); - plugin.state = { nodeLengths, showNodeBorders: this.settings.showNodeBorders }; + const nodeLengths = ancestors.map((id) => [ + id, + this.state[view.file.path].nodes[id].text.length, + ]); + plugin.state = { + nodeLengths, + showNodeBorders: this.settings.showNodeBorders, + }; plugin.update(); }; @@ -637,22 +644,29 @@ export default class LoomPlugin extends Plugin { new Notice("Can't merge a root node with its parent"); return; } - if (Object.values(state.nodes).filter((n) => n.parentId === parentId).length > 1) { - new Notice("Can't merge this node with its parent; it has siblings"); + if ( + Object.values(state.nodes).filter((n) => n.parentId === parentId) + .length > 1 + ) { + new Notice( + "Can't merge this node with its parent; it has siblings" + ); return; } state.nodes[parentId].text += state.nodes[id].text; - const children = Object.entries(state.nodes).filter(([_, node]) => node.parentId === id); - for (const [childId, ] of children) + const children = Object.entries(state.nodes).filter( + ([_, node]) => node.parentId === id + ); + for (const [childId] of children) this.state[file.path].nodes[childId].parentId = parentId; this.app.workspace.trigger("loom:switch-to", parentId); this.app.workspace.trigger("loom:delete", id); - } + }) ) - )); + ); this.registerEvent( // @ts-expect-error @@ -723,23 +737,26 @@ export default class LoomPlugin extends Plugin { ); this.registerEvent( + this.app.workspace.on( // @ts-expect-error - this.app.workspace.on("loom:set-setting", (setting: string, value: any) => { - this.thenSaveAndRender( - () => (this.settings = { ...this.settings, [setting]: value }) - ); + "loom:set-setting", + (setting: string, value: any) => { + this.thenSaveAndRender( + () => (this.settings = { ...this.settings, [setting]: value }) + ); - if (setting === "showNodeBorders") { - // @ts-expect-error - const editor = this.editor.cm; + if (setting === "showNodeBorders") { + // @ts-expect-error + const editor = this.editor.cm; - const plugin = editor.plugin(loomEditorPlugin); - plugin.state.showNodeBorders = this.settings.showNodeBorders; - plugin.update(); + const plugin = editor.plugin(loomEditorPlugin); + plugin.state.showNodeBorders = this.settings.showNodeBorders; + plugin.update(); - editor.focus(); + editor.focus(); + } } - }) + ) ); this.registerEvent( @@ -807,7 +824,10 @@ export default class LoomPlugin extends Plugin { ancestors = ancestors.reverse(); const ancestorTexts = ancestors.map((id) => state.nodes[id].text); - const nodeLengths = ancestors.map((id, i) => [id, ancestorTexts[i].length]); + const nodeLengths = ancestors.map((id, i) => [ + id, + ancestorTexts[i].length, + ]); plugin.state = { nodeLengths, @@ -1634,7 +1654,9 @@ class LoomView extends ItemView { menu.addItem((item) => { item.setTitle("Merge with parent"); item.setIcon("arrow-up-left"); - item.onClick(() => this.app.workspace.trigger("loom:merge-with-parent", id)); + item.onClick(() => + this.app.workspace.trigger("loom:merge-with-parent", id) + ); }); } @@ -1846,7 +1868,9 @@ const loomEditorPluginSpec: PluginSpec = { if (!target.classList.contains("loom-bct")) return false; const className = target.classList[target.classList.length - 1]; - for (const el of [].slice.call(document.getElementsByClassName(className))) + for (const el of [].slice.call( + document.getElementsByClassName(className) + )) el.classList.add("loom-bct-hover"); return true; @@ -1858,7 +1882,9 @@ const loomEditorPluginSpec: PluginSpec = { if (!target.classList.contains("loom-bct")) return false; const className = target.classList[target.classList.length - 1]; - for (const el of [].slice.call(document.getElementsByClassName(className))) + for (const el of [].slice.call( + document.getElementsByClassName(className) + )) el.classList.remove("loom-bct-hover"); return true; @@ -1875,7 +1901,7 @@ const loomEditorPluginSpec: PluginSpec = { app.workspace.trigger("loom:switch-to", id); return true; - } + }, }, }; @@ -1886,7 +1912,9 @@ class LoomBorderWidget extends WidgetType { return el; } - eq() { return true; } + eq() { + return true; + } } class LoomSettingTab extends PluginSettingTab { From a0402c18fb1616553ce16ef12d5fee0646ad50f0 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 19:29:35 -0700 Subject: [PATCH 03/68] Fix rendering of whitespace nodes --- main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 35175c0..a0d1ee9 100644 --- a/main.ts +++ b/main.ts @@ -1532,9 +1532,9 @@ class LoomView extends ItemView { if (node.unread) itemDiv.createDiv({ cls: "loom-node-unread-indicator" }); // the node's text - const nodeText = itemDiv.createEl(node.text ? "span" : "em", { + const nodeText = itemDiv.createEl(node.text.trim() ? "span" : "em", { cls: "loom-node-inner tree-item-inner", - text: node.text || "No text", + text: node.text.trim() || "No text", }); nodeText.addEventListener("click", () => this.app.workspace.trigger("loom:switch-to", id) From 897d1c8188ee30cd7cd6cb9e0735136db8bd415c Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 20:30:55 -0700 Subject: [PATCH 04/68] add sibling view --- main.ts | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++---- styles.css | 17 +++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/main.ts b/main.ts index a0d1ee9..4a8ba71 100644 --- a/main.ts +++ b/main.ts @@ -116,6 +116,12 @@ export default class LoomPlugin extends Plugin { .map((leaf) => leaf.view)[0] as LoomView; } + siblingsView(): LoomSiblingsView { + return this.app.workspace + .getLeavesOfType("loom-siblings") + .map((leaf) => leaf.view)[0] as LoomSiblingsView; + } + withFile(callback: (file: TFile) => T): T | null { const file = this.app.workspace.getActiveFile(); if (!file) return null; @@ -127,6 +133,7 @@ export default class LoomPlugin extends Plugin { this.save(); this.view().render(); + this.siblingsView().render(); } wftsar(callback: (file: TFile) => void) { @@ -176,12 +183,23 @@ export default class LoomPlugin extends Plugin { }); }; - const openLoomPane = () => { + const openLoomPane = (focus: boolean) => { const loomPanes = this.app.workspace.getLeavesOfType("loom"); try { if (loomPanes.length === 0) this.app.workspace.getRightLeaf(false).setViewState({ type: "loom" }); - else this.app.workspace.revealLeaf(loomPanes[0]); + else if (focus) this.app.workspace.revealLeaf(loomPanes[0]); + } catch (e) { + console.error(e); + } + }; + + const openLoomSiblingsPane = (focus: boolean) => { + const loomPanes = this.app.workspace.getLeavesOfType("loom-siblings"); + try { + if (loomPanes.length === 0) + this.app.workspace.getRightLeaf(false).setViewState({ type: "loom-siblings" }); + else if (focus) this.app.workspace.revealLeaf(loomPanes[0]); } catch (e) { console.error(e); } @@ -334,7 +352,7 @@ export default class LoomPlugin extends Plugin { this.addCommand({ id: "loom-open-pane", name: "Open Loom pane", - callback: openLoomPane, + callback: () => openLoomPane(true), }); this.addCommand({ @@ -351,13 +369,19 @@ export default class LoomPlugin extends Plugin { (leaf) => new LoomView(leaf, getState, getSettings) ); + this.registerView( + "loom-siblings", + (leaf) => new LoomSiblingsView(leaf, getState) + ); + const loomEditorPlugin = ViewPlugin.fromClass( LoomEditorPlugin, loomEditorPluginSpec ); this.registerEditorExtension([loomEditorPlugin]); - openLoomPane(); + openLoomPane(true); + openLoomSiblingsPane(false); this.registerEvent( this.app.workspace.on( @@ -796,6 +820,7 @@ export default class LoomPlugin extends Plugin { if (!file) return; this.view().render(); + this.siblingsView().render(); this.app.workspace.iterateRootLeaves((leaf) => { if ( @@ -847,7 +872,7 @@ export default class LoomPlugin extends Plugin { ); this.registerEvent( - this.app.workspace.on("resize", () => this.view().render()) + this.app.workspace.on("resize", () => { this.view().render(); this.siblingsView().render(); }) ); this.registerEvent( @@ -914,6 +939,7 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = state.current; this.save(); this.view().render(); + this.siblingsView().render(); let prompt = this.fullText(state.current, state); @@ -1093,6 +1119,7 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = null; this.save(); this.view().render(); + this.siblingsView().render(); this.statusBarItem.style.display = "none"; } @@ -1810,6 +1837,57 @@ class LoomView extends ItemView { } } +class LoomSiblingsView extends ItemView { + getNoteState: () => NoteState | null; + + constructor(leaf: WorkspaceLeaf, getNoteState: () => NoteState | null) { + super(leaf); + this.getNoteState = getNoteState; + this.render(); + } + + render() { + this.containerEl.empty(); + const outline = this.containerEl.createDiv({ cls: "loom outline" }); + + const state = this.getNoteState(); + + if (!state) { + outline.createEl("div", { + text: "No note selected.", + cls: "pane-empty", + }); + return; + } + + const siblings = Object.entries(state.nodes).filter(([, node]) => node.parentId === state.nodes[state.current].parentId); + for (const i in siblings) { + const [id, node] = siblings[i]; + + const siblingDiv = outline.createEl("div", { + text: node.text, + cls: "loom-sibling", + }); + siblingDiv.addEventListener("click", () => this.app.workspace.trigger("loom:switch-to", id)); + + if (parseInt(i) !== siblings.length - 1) + outline.createEl("hr", { cls: "loom-sibling-divider" }); + } + } + + getViewType(): string { + return "loom-siblings"; + } + + getDisplayText(): string { + return "Siblings"; + } + + getIcon(): string { + return "layout-list"; + } +} + interface LoomEditorPluginState { nodeLengths: [string, number][]; showNodeBorders: boolean; diff --git a/styles.css b/styles.css index 6348b98..7fa9423 100644 --- a/styles.css +++ b/styles.css @@ -264,3 +264,20 @@ .theme-dark .loom-bct-hover { background-color: #111; } + +.loom-sibling { + color: var(--nav-item-color); + + line-height: 1.3; + white-space: pre-wrap; + + cursor: pointer; +} + +.loom-sibling:hover { + color: var(--nav-item-color-active); +} + +.loom-sibling-divider { + margin: 1.2em 0; +} From e946b0de2096e35620c247ff141cb34b37a01176 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 20:44:38 -0700 Subject: [PATCH 05/68] add prepending ellipsis --- main.ts | 8 +++++--- styles.css | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/main.ts b/main.ts index 4a8ba71..d9e894c 100644 --- a/main.ts +++ b/main.ts @@ -1864,10 +1864,12 @@ class LoomSiblingsView extends ItemView { for (const i in siblings) { const [id, node] = siblings[i]; - const siblingDiv = outline.createEl("div", { - text: node.text, - cls: "loom-sibling", + const siblingDiv = outline.createEl("div", { cls: "loom-sibling" }); + siblingDiv.createEl("span", { + text: "…", + cls: "loom-sibling-ellipsis", }); + siblingDiv.createEl("span", { text: node.text.trim() }); siblingDiv.addEventListener("click", () => this.app.workspace.trigger("loom:switch-to", id)); if (parseInt(i) !== siblings.length - 1) diff --git a/styles.css b/styles.css index 7fa9423..0a0b979 100644 --- a/styles.css +++ b/styles.css @@ -281,3 +281,8 @@ .loom-sibling-divider { margin: 1.2em 0; } + +.loom-sibling-ellipsis { + color: var(--text-faint); + margin-right: 0.3em; +} From 8948b8386fae07f8d6da7f5fc31cee1465d15ac0 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 21:00:32 -0700 Subject: [PATCH 06/68] update sibling view ui --- main.ts | 14 +++++++++----- styles.css | 11 ++++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/main.ts b/main.ts index d9e894c..98df3c5 100644 --- a/main.ts +++ b/main.ts @@ -1860,15 +1860,19 @@ class LoomSiblingsView extends ItemView { return; } - const siblings = Object.entries(state.nodes).filter(([, node]) => node.parentId === state.nodes[state.current].parentId); + const parentId = state.nodes[state.current].parentId; + const siblings = Object.entries(state.nodes).filter(([, node]) => node.parentId === parentId); for (const i in siblings) { const [id, node] = siblings[i]; - const siblingDiv = outline.createEl("div", { cls: "loom-sibling" }); - siblingDiv.createEl("span", { - text: "…", - cls: "loom-sibling-ellipsis", + const siblingDiv = outline.createEl("div", { + cls: `loom-sibling${id === state.current ? " is-active" : ""}`, }); + if (parentId !== null) + siblingDiv.createEl("span", { + text: "…", + cls: "loom-sibling-ellipsis", + }); siblingDiv.createEl("span", { text: node.text.trim() }); siblingDiv.addEventListener("click", () => this.app.workspace.trigger("loom:switch-to", id)); diff --git a/styles.css b/styles.css index 0a0b979..d8ba000 100644 --- a/styles.css +++ b/styles.css @@ -267,19 +267,24 @@ .loom-sibling { color: var(--nav-item-color); + font-size: 0.9em; line-height: 1.3; + padding: 0.6em 0.6em; white-space: pre-wrap; - - cursor: pointer; } .loom-sibling:hover { color: var(--nav-item-color-active); } +.loom-sibling.is-active { + background-color: var(--nav-item-background-active); + color: var(--nav-item-color-active); +} + .loom-sibling-divider { - margin: 1.2em 0; + margin: 0; } .loom-sibling-ellipsis { From b6feae5b41e527379c3c8a9ef698e20ad1934088 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 21:11:23 -0700 Subject: [PATCH 07/68] 1.2.0 --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 6463e18..0c160d5 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.1.0", + "version": "1.2.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index b0abeeb..7430255 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.1.0", + "version": "1.2.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 9309a515bd9f209436f98e29d16e55ec70a9b695 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 22:36:24 -0700 Subject: [PATCH 08/68] more padding on siblings view --- styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles.css b/styles.css index d8ba000..8bcc726 100644 --- a/styles.css +++ b/styles.css @@ -270,7 +270,7 @@ font-size: 0.9em; line-height: 1.3; - padding: 0.6em 0.6em; + padding: 0.8em; white-space: pre-wrap; } From 35800867b83a083c3896e328df4930de3e6cae19 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 5 Apr 2023 23:43:19 -0700 Subject: [PATCH 09/68] preserve scroll in siblings view --- main.ts | 9 +++++++-- styles.css | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 98df3c5..626682f 100644 --- a/main.ts +++ b/main.ts @@ -1299,7 +1299,7 @@ class LoomView extends ItemView { const settings = this.getSettings(); // get scroll position, which will be restored at the end - const scroll = (this.containerEl as HTMLElement).scrollTop; + const scroll = this.containerEl.scrollTop; this.containerEl.empty(); this.containerEl.addClass("loom"); @@ -1847,8 +1847,11 @@ class LoomSiblingsView extends ItemView { } render() { + const scroll = this.containerEl.scrollTop; + this.containerEl.empty(); - const outline = this.containerEl.createDiv({ cls: "loom outline" }); + this.containerEl.addClass("loom"); + const outline = this.containerEl.createDiv({ cls: "outline" }); const state = this.getNoteState(); @@ -1879,6 +1882,8 @@ class LoomSiblingsView extends ItemView { if (parseInt(i) !== siblings.length - 1) outline.createEl("hr", { cls: "loom-sibling-divider" }); } + + this.containerEl.scrollTop = scroll; } getViewType(): string { diff --git a/styles.css b/styles.css index 8bcc726..bd034ba 100644 --- a/styles.css +++ b/styles.css @@ -270,6 +270,7 @@ font-size: 0.9em; line-height: 1.3; + margin: 0.2em 0; padding: 0.8em; white-space: pre-wrap; } From 74c3950777e7a1f117768ed96cc804528f418a68 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:04:44 -0700 Subject: [PATCH 10/68] improve readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5632cbd..18072cc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an Obsidian plugin, designed to be easier to use and more modular and extensible. -Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar. +Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. @@ -20,6 +20,9 @@ Default hotkeys: - `Alt+Right` - switch to (most recently visited) child - `Alt+e` - collapse/expand current node +In the editor: +- `Shift+click` on the text corresponding to a node to switch to it + **If you are using MacOS:** a few hotkeys -- `Alt+n`, `Alt+c` and `Alt+e` -- are bound to special characters. You can either: 1. Disable MacOS's special character shortcuts, as explained here: https://superuser.com/questions/941286/disable-default-option-key-binding From 135f0d3863e0177deeabb2a5bb3b0fc1374fe169 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:09:57 -0700 Subject: [PATCH 11/68] readme improvement 2 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 18072cc..fdff48c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Default hotkeys: - `Alt+n` - create sibling - `Ctrl+Alt+c` - clone current node - `Alt+c` - split in-range node at caret - - `Alt+Backspace` - delete current node - `Alt+Down` - switch to next sibling - `Alt+Up` - switch to previous sibling From 95d21aebbd768c8bf759e31d973fc30774b3df0b Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:12:08 -0700 Subject: [PATCH 12/68] add patreon --- manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/manifest.json b/manifest.json index 0c160d5..e0e20df 100644 --- a/manifest.json +++ b/manifest.json @@ -6,5 +6,6 @@ "description": "Loom in Obsidian", "author": "celeste", "authorUrl": "https://celeste.exposed", + "fundingUrl": "https://www.patreon.com/parafactual", "isDesktopOnly": true } From 333cb747bc499a4640fb47022256b19b76d083e4 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:24:52 -0700 Subject: [PATCH 13/68] add installation instructions to readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index fdff48c..888c21b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,20 @@ Default hotkeys: In the editor: - `Shift+click` on the text corresponding to a node to switch to it +**How to install** (until Loom is added to the Obsidian store)**:** + +1. Go to the latest release under the "Releases" subheading on the right +2. Download the zip file under "Assets" +3. Unzip the file you downloaded in `[path to vault]/.obsidian/plugins` +4. Go to the "Community plugins" tab in Obsidian settings, then enable "Loom" + +Alternatively, you can build from source, which makes it easy to update: + +1. Clone this repository (`git clone https://github.com/cosmicoptima/loom`) in `[path to vault]/.obsidian/plugins` +2. Run the following: `cd loom; npm i; npm run build` +3. Go to the "Community plugins" tab in Obsidian settings, then enable "Loom" +4. To update, go to the repository and `git pull`, then disable and re-enable Loom + **If you are using MacOS:** a few hotkeys -- `Alt+n`, `Alt+c` and `Alt+e` -- are bound to special characters. You can either: 1. Disable MacOS's special character shortcuts, as explained here: https://superuser.com/questions/941286/disable-default-option-key-binding From 0b20fa77ce48c89d79bf342ee00da51e31729f02 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:26:52 -0700 Subject: [PATCH 14/68] readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 888c21b..1420a32 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,16 @@ Loom is a recursively branching interface to GPT-3 and other language models; it **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. -Default hotkeys: +**Default hotkeys:** + - `Ctrl+Space` - complete - `Ctrl+Alt+n` - create child - `Alt+n` - create sibling - `Ctrl+Alt+c` - clone current node - `Alt+c` - split in-range node at caret - `Alt+Backspace` - delete current node + +Navigation: - `Alt+Down` - switch to next sibling - `Alt+Up` - switch to previous sibling - `Alt+Left` - switch to parent From 7304dd95db66b0a28e6ecf2c27db065893fe00f3 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:35:19 -0700 Subject: [PATCH 15/68] add patreon badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1420a32..dd172e7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. +If you are interested in funding development of Loom(sidian): + + **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. **Default hotkeys:** From 53f5de5d530314adc6eadb1632e6414c86d5fa40 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:37:11 -0700 Subject: [PATCH 16/68] dujfbsjf --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index dd172e7..8082646 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. -If you are interested in funding development of Loom(sidian): - +If you are interested in funding development of Loom(sidian), you can [support me on Patreon](https://patreon.com/parafactual); I would then be able to devote more time to this and other independent projects. **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. From 52cf195cb49035356cfc4728f9cb2798625808f6 Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:39:15 -0700 Subject: [PATCH 17/68] bold link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8082646..f2ff46b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. -If you are interested in funding development of Loom(sidian), you can [support me on Patreon](https://patreon.com/parafactual); I would then be able to devote more time to this and other independent projects. +If you are interested in funding development of Loom(sidian), you can **[support me on Patreon](https://patreon.com/parafactual)**; I would then be able to devote more time to this and other independent projects. **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. From 6c8d34c5e0db017be823cb22f5474a4a5c7c216a Mon Sep 17 00:00:00 2001 From: celeste Date: Thu, 6 Apr 2023 21:55:22 -0700 Subject: [PATCH 18/68] current sibling auto scrolls into view --- main.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.ts b/main.ts index 626682f..9ea233b 100644 --- a/main.ts +++ b/main.ts @@ -1865,6 +1865,8 @@ class LoomSiblingsView extends ItemView { const parentId = state.nodes[state.current].parentId; const siblings = Object.entries(state.nodes).filter(([, node]) => node.parentId === parentId); + + let currentDiv; for (const i in siblings) { const [id, node] = siblings[i]; @@ -1881,9 +1883,17 @@ class LoomSiblingsView extends ItemView { if (parseInt(i) !== siblings.length - 1) outline.createEl("hr", { cls: "loom-sibling-divider" }); + + if (id === state.current) currentDiv = siblingDiv; } this.containerEl.scrollTop = scroll; + + if (currentDiv) { + const rect = currentDiv.getBoundingClientRect(); + if (rect.top < 25 || rect.bottom > this.containerEl.clientHeight) + currentDiv.scrollIntoView(); + } } getViewType(): string { From 2c13b15cc3fadbb1586e6d71539a85365b450c7a Mon Sep 17 00:00:00 2001 From: celeste Date: Sun, 9 Apr 2023 00:07:26 -0700 Subject: [PATCH 19/68] clarify readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2ff46b..8583c33 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Alternatively, you can build from source, which makes it easy to update: 1. Clone this repository (`git clone https://github.com/cosmicoptima/loom`) in `[path to vault]/.obsidian/plugins` 2. Run the following: `cd loom; npm i; npm run build` 3. Go to the "Community plugins" tab in Obsidian settings, then enable "Loom" -4. To update, go to the repository and `git pull`, then disable and re-enable Loom +4. To update, go to the repository and `git pull; npm i; npm run build`, then disable and re-enable Loom **If you are using MacOS:** a few hotkeys -- `Alt+n`, `Alt+c` and `Alt+e` -- are bound to special characters. You can either: From beca1fa18d9f1533caa07ef787d762c373a7fd3a Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 17 Apr 2023 22:26:01 -0400 Subject: [PATCH 20/68] fix funding url indentation --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index e0e20df..a557fcd 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "description": "Loom in Obsidian", "author": "celeste", "authorUrl": "https://celeste.exposed", - "fundingUrl": "https://www.patreon.com/parafactual", + "fundingUrl": "https://www.patreon.com/parafactual", "isDesktopOnly": true } From e7efca21b6e0554be08c08610b973f76ef094e86 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 17 Apr 2023 22:54:13 -0400 Subject: [PATCH 21/68] changes --- README.md | 2 ++ main.ts | 97 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 8583c33..44e74d4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. +Loom can request completions from the following providers: [Cohere](https://docs.cohere.ai/docs), [TextSynth](https://textsynth.com/documentation.html), and [OpenAI](https://platform.openai.com/docs/introduction). It can also request completions from implementations of [openai-cd2-proxy](https://github.com/cosmicoptima/openai-cd2-proxy), in which case you must provide the base URL in settings. + If you are interested in funding development of Loom(sidian), you can **[support me on Patreon](https://patreon.com/parafactual)**; I would then be able to devote more time to this and other independent projects. **If you are new to Obsidian:** if you want to see the tree interface, make sure to open the right sidebar using the button on the top right, or using `Ctrl+P` then `Toggle right sidebar`. Once you've done that, go to the Loom tab, which is signified by a network icon. diff --git a/main.ts b/main.ts index 9ea233b..f91409f 100644 --- a/main.ts +++ b/main.ts @@ -10,6 +10,7 @@ import { Setting, TFile, WorkspaceLeaf, + requestUrl, setIcon, } from "obsidian"; import { Range } from "@codemirror/state"; @@ -110,24 +111,27 @@ export default class LoomPlugin extends Plugin { openai: OpenAIApi; - view(): LoomView { - return this.app.workspace - .getLeavesOfType("loom") - .map((leaf) => leaf.view)[0] as LoomView; - } - - siblingsView(): LoomSiblingsView { - return this.app.workspace - .getLeavesOfType("loom-siblings") - .map((leaf) => leaf.view)[0] as LoomSiblingsView; - } - withFile(callback: (file: TFile) => T): T | null { const file = this.app.workspace.getActiveFile(); if (!file) return null; return callback(file); } + view() { + // return this.app.workspace.getActiveViewOfType(LoomView) as LoomView; + return this.app.workspace + .getLeavesOfType("loom") + .map((leaf) => leaf.view)[0] as LoomView; + + } + + siblingsView() { + // return this.app.workspace.getActiveViewOfType(LoomSiblingsView) as LoomSiblingsView; + return this.app.workspace + .getLeavesOfType("loom-siblings") + .map((leaf) => leaf.view)[0] as LoomSiblingsView; + } + thenSaveAndRender(callback: () => void) { callback(); @@ -167,7 +171,7 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; this.addCommand({ - id: "loom-complete", + id: "complete", name: "Complete from current point", icon: "wand", callback: async () => this.complete(), @@ -206,7 +210,7 @@ export default class LoomPlugin extends Plugin { }; this.addCommand({ - id: "loom-create-child", + id: "create-child", name: "Create child of current node", icon: "plus", callback: () => @@ -217,7 +221,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-create-sibling", + id: "create-sibling", name: "Create sibling of current node", icon: "list-plus", callback: () => @@ -228,7 +232,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-clone-current-node", + id: "clone-current-node", name: "Clone current node", icon: "copy", callback: () => @@ -239,7 +243,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-break-at-point", + id: "break-at-point", name: "Branch from current point", callback: () => withState((state) => @@ -249,7 +253,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-merge-with-parent", + id: "merge-with-parent", name: "Merge current node with parent", callback: () => withState((state) => @@ -259,7 +263,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-switch-to-next-sibling", + id: "switch-to-next-sibling", name: "Switch to next sibling", icon: "arrow-down", callback: () => @@ -272,7 +276,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-switch-to-previous-sibling", + id: "switch-to-previous-sibling", name: "Switch to previous sibling", icon: "arrow-up", callback: () => @@ -285,7 +289,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-switch-to-parent", + id: "switch-to-parent", name: "Switch to parent", icon: "arrow-left", callback: () => @@ -297,7 +301,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-switch-to-child", + id: "switch-to-child", name: "Switch to child", icon: "arrow-right", callback: () => @@ -310,7 +314,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-delete-current-node", + id: "delete-current-node", name: "Delete current node", icon: "trash", callback: () => @@ -321,7 +325,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-clear-children", + id: "clear-children", name: "Delete current node's children", callback: () => withState((state) => @@ -330,7 +334,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-clear-siblings", + id: "clear-siblings", name: "Delete current node's siblings", callback: () => withState((state) => @@ -339,7 +343,7 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-toggle-collapse-current-node", + id: "toggle-collapse-current-node", name: "Toggle whether current node is collapsed", icon: "folder-up", callback: () => @@ -350,13 +354,13 @@ export default class LoomPlugin extends Plugin { }); this.addCommand({ - id: "loom-open-pane", + id: "open-pane", name: "Open Loom pane", callback: () => openLoomPane(true), }); this.addCommand({ - id: "loom-debug-reset-state", + id: "debug-reset-state", name: "Debug: Reset state", callback: () => this.thenSaveAndRender(() => (this.state = {})), }); @@ -872,7 +876,10 @@ export default class LoomPlugin extends Plugin { ); this.registerEvent( - this.app.workspace.on("resize", () => { this.view().render(); this.siblingsView().render(); }) + this.app.workspace.on("resize", () => { + this.view().render(); + this.siblingsView().render(); + }) ); this.registerEvent( @@ -1025,20 +1032,18 @@ export default class LoomPlugin extends Plugin { (generation) => generation.text ); } else if (this.settings.provider === "textsynth") { - // TextSynth API doesn't have CORS enabled, so we have to proxy - const response = await fetch("https://celeste.exposed/api/textsynth", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - api_key: this.settings.textsynthApiKey, - model: this.settings.model, - prompt, - max_tokens: this.settings.maxTokens, - n: this.settings.n, - temperature: this.settings.temperature, - top_p: this.settings.topP, - }), - }); + const response = await requestUrl({ + url: `https://api.textsynth.com/v1/engines/${this.settings.model}/completions`, + method: "POST", + headers: { "Content-Type": "application/json", "Authorization": `Bearer ${this.settings.textsynthApiKey}` }, + body: JSON.stringify({ + prompt, + max_tokens: this.settings.maxTokens, + n: this.settings.n, + temperature: this.settings.temperature, + top_p: this.settings.topP, + }), + }); if (response.status !== 200) { new Notice( "TextSynth API responded with status code " + response.status @@ -1048,8 +1053,8 @@ export default class LoomPlugin extends Plugin { return; } if (this.settings.n === 1) - completions = [(await response.json()).response.text]; - else completions = (await response.json()).response.text; + completions = [response.json.text]; + else completions = response.json.text; } else if (this.settings.provider === "ocp") { let url = this.settings.ocpUrl; @@ -1119,7 +1124,7 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = null; this.save(); this.view().render(); - this.siblingsView().render(); + this.siblingsView().render(); this.statusBarItem.style.display = "none"; } From 46d1aad48acbd33107f753a5c4af5495fd89d5e5 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 17 Apr 2023 22:56:22 -0400 Subject: [PATCH 22/68] clarify readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44e74d4..18d5966 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. -Loom can request completions from the following providers: [Cohere](https://docs.cohere.ai/docs), [TextSynth](https://textsynth.com/documentation.html), and [OpenAI](https://platform.openai.com/docs/introduction). It can also request completions from implementations of [openai-cd2-proxy](https://github.com/cosmicoptima/openai-cd2-proxy), in which case you must provide the base URL in settings. +Loom can request completions from the following providers: [Cohere](https://docs.cohere.ai/docs), [TextSynth](https://textsynth.com/documentation.html), and [OpenAI](https://platform.openai.com/docs/introduction). It can also request completions from implementations of [openai-cd2-proxy](https://github.com/cosmicoptima/openai-cd2-proxy), in which case you must provide the base URL in the Loom tab in Settings. If you are interested in funding development of Loom(sidian), you can **[support me on Patreon](https://patreon.com/parafactual)**; I would then be able to devote more time to this and other independent projects. From f994f90d6a76aac01a57ae5b00ff1f2cb9411929 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 16:06:14 -0400 Subject: [PATCH 23/68] various chanegs --- main.ts | 278 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 188 insertions(+), 90 deletions(-) diff --git a/main.ts b/main.ts index f91409f..97da0b2 100644 --- a/main.ts +++ b/main.ts @@ -32,7 +32,7 @@ import { v4 as uuidv4 } from "uuid"; const dialog = require("electron").remote.dialog; const untildify = require("untildify") as any; -const tokenizer = new GPT3Tokenizer({ type: "codex" }); +const tokenizer = new GPT3Tokenizer({ type: "codex" }); // TODO depends on model const PROVIDERS = ["cohere", "textsynth", "ocp", "openai", "openai-chat"]; type Provider = (typeof PROVIDERS)[number]; @@ -118,16 +118,13 @@ export default class LoomPlugin extends Plugin { } view() { - // return this.app.workspace.getActiveViewOfType(LoomView) as LoomView; - return this.app.workspace + return this.app.workspace .getLeavesOfType("loom") .map((leaf) => leaf.view)[0] as LoomView; - } siblingsView() { - // return this.app.workspace.getActiveViewOfType(LoomSiblingsView) as LoomSiblingsView; - return this.app.workspace + return this.app.workspace .getLeavesOfType("loom-siblings") .map((leaf) => leaf.view)[0] as LoomSiblingsView; } @@ -174,17 +171,61 @@ export default class LoomPlugin extends Plugin { id: "complete", name: "Complete from current point", icon: "wand", - callback: async () => this.complete(), + checkCallback: (checking: boolean) => { + const file = this.app.workspace.getActiveFile(); + if (!file) return false; + + // only check if api keys are set, not if they're valid, because that sometimes requires additional api calls + if ( + ["openai", "openai-chat"].includes(this.settings.provider) && + !this.settings.openaiApiKey + ) + return false; + if (this.settings.provider === "cohere" && !this.settings.cohereApiKey) + return false; + if ( + this.settings.provider === "textsynth" && + !this.settings.textsynthApiKey + ) + return false; + if (this.settings.provider === "ocp" && !this.settings.ocpApiKey) + return false; + + if (!checking) this.complete(file); + return true; + }, hotkeys: [{ modifiers: ["Ctrl"], key: " " }], }); - const withState = (callback: (state: NoteState) => void) => { - return this.withFile((file) => { - const state = this.state[file.path]; - if (!state) this.initializeFile(file); + const withState = ( + checking: boolean, + callback: (state: NoteState) => void + ) => { + const file = this.app.workspace.getActiveFile(); + if (!file) return false; - callback(state); - }); + const state = this.state[file.path]; + if (!state) this.initializeFile(file); + + if (!checking) callback(state); + return true; + }; + + const withStateAddl = ( + checking: boolean, + checkCallback: (state: NoteState) => boolean, + callback: (state: NoteState) => void + ) => { + const file = this.app.workspace.getActiveFile(); + if (!file) return false; + + const state = this.state[file.path]; + if (!state) this.initializeFile(file); + + if (!checkCallback(state)) return false; + + if (!checking) callback(state); + return true; }; const openLoomPane = (focus: boolean) => { @@ -202,7 +243,9 @@ export default class LoomPlugin extends Plugin { const loomPanes = this.app.workspace.getLeavesOfType("loom-siblings"); try { if (loomPanes.length === 0) - this.app.workspace.getRightLeaf(false).setViewState({ type: "loom-siblings" }); + this.app.workspace + .getRightLeaf(false) + .setViewState({ type: "loom-siblings" }); else if (focus) this.app.workspace.revealLeaf(loomPanes[0]); } catch (e) { console.error(e); @@ -213,10 +256,10 @@ export default class LoomPlugin extends Plugin { id: "create-child", name: "Create child of current node", icon: "plus", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:create-child", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:create-child", state.current); + }), hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "n" }], }); @@ -224,10 +267,10 @@ export default class LoomPlugin extends Plugin { id: "create-sibling", name: "Create sibling of current node", icon: "list-plus", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:create-sibling", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:create-sibling", state.current); + }), hotkeys: [{ modifiers: ["Alt"], key: "n" }], }); @@ -235,29 +278,53 @@ export default class LoomPlugin extends Plugin { id: "clone-current-node", name: "Clone current node", icon: "copy", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:clone", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:clone", state.current); + }), hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "c" }], }); this.addCommand({ id: "break-at-point", name: "Branch from current point", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:break-at-point", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:break-at-point", state.current); + }), hotkeys: [{ modifiers: ["Alt"], key: "c" }], }); this.addCommand({ id: "merge-with-parent", name: "Merge current node with parent", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:merge-with-parent", state.current) + checkCallback: (checking: boolean) => + withStateAddl( + checking, + (state) => { + const parentId = state.nodes[state.current].parentId; + + if (parentId === null) { + if (!checking) + new Notice("Can't merge a root node with its parent"); + return false; + } + if ( + Object.values(state.nodes).filter((n) => n.parentId === parentId) + .length > 1 + ) { + if (!checking) + new Notice( + "Can't merge this node with its parent; it has siblings" + ); + return false; + } + + return true; + }, + (state) => { + this.app.workspace.trigger("loom:merge-with-parent", state.current); + } ), hotkeys: [{ modifiers: ["Alt"], key: "m" }], }); @@ -266,8 +333,8 @@ export default class LoomPlugin extends Plugin { id: "switch-to-next-sibling", name: "Switch to next sibling", icon: "arrow-down", - callback: () => - withState((state) => { + checkCallback: (checking: boolean) => + withState(checking, (state) => { const nextSibling = this.nextSibling(state.current, state); if (nextSibling) this.app.workspace.trigger("loom:switch-to", nextSibling); @@ -279,8 +346,8 @@ export default class LoomPlugin extends Plugin { id: "switch-to-previous-sibling", name: "Switch to previous sibling", icon: "arrow-up", - callback: () => - withState((state) => { + checkCallback: (checking: boolean) => + withState(checking, (state) => { const prevSibling = this.prevSibling(state.current, state); if (prevSibling) this.app.workspace.trigger("loom:switch-to", prevSibling); @@ -292,11 +359,18 @@ export default class LoomPlugin extends Plugin { id: "switch-to-parent", name: "Switch to parent", icon: "arrow-left", - callback: () => - withState((state) => { - const parentId = state.nodes[state.current].parentId; - if (parentId) this.app.workspace.trigger("loom:switch-to", parentId); - }), + checkCallback: (checking: boolean) => + withStateAddl( + checking, + (state) => { + return state.nodes[state.current].parentId !== null; + }, + (state) => { + const parentId = state.nodes[state.current].parentId; + if (parentId) + this.app.workspace.trigger("loom:switch-to", parentId); + } + ), hotkeys: [{ modifiers: ["Alt"], key: "ArrowLeft" }], }); @@ -304,12 +378,21 @@ export default class LoomPlugin extends Plugin { id: "switch-to-child", name: "Switch to child", icon: "arrow-right", - callback: () => - withState((state) => { - const lastVisitedChild = this.lastVisitedChild(state); - if (lastVisitedChild) - this.app.workspace.trigger("loom:switch-to", lastVisitedChild); - }), + checkCallback: (checking: boolean) => + withStateAddl( + checking, + (state) => { + const children = Object.values(state.nodes).filter( + (node: Node) => node.parentId === state.current + ); + return children.length > 0; + }, + (state) => { + const lastVisitedChild = this.lastVisitedChild(state); + if (lastVisitedChild) + this.app.workspace.trigger("loom:switch-to", lastVisitedChild); + } + ), hotkeys: [{ modifiers: ["Alt"], key: "ArrowRight" }], }); @@ -317,9 +400,22 @@ export default class LoomPlugin extends Plugin { id: "delete-current-node", name: "Delete current node", icon: "trash", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:delete", state.current) + checkCallback: (checking: boolean) => + withStateAddl( + checking, + (state) => { + const rootNodes = Object.entries(state.nodes).filter( + ([, node]) => node.parentId === null + ); + if (rootNodes.length === 1 && rootNodes[0][0] === state.current) { + if (!checking) new Notice("The last root node can't be deleted"); + return false; + } + return true; + }, + (state) => { + this.app.workspace.trigger("loom:delete", state.current); + } ), hotkeys: [{ modifiers: ["Alt"], key: "Backspace" }], }); @@ -327,29 +423,29 @@ export default class LoomPlugin extends Plugin { this.addCommand({ id: "clear-children", name: "Delete current node's children", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:clear-children", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:clear-children", state.current); + }), }); this.addCommand({ id: "clear-siblings", name: "Delete current node's siblings", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:clear-siblings", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:clear-siblings", state.current); + }), }); this.addCommand({ id: "toggle-collapse-current-node", name: "Toggle whether current node is collapsed", icon: "folder-up", - callback: () => - withState((state) => - this.app.workspace.trigger("loom:toggle-collapse", state.current) - ), + checkCallback: (checking: boolean) => + withState(checking, (state) => { + this.app.workspace.trigger("loom:toggle-collapse", state.current); + }), hotkeys: [{ modifiers: ["Alt"], key: "e" }], }); @@ -766,7 +862,7 @@ export default class LoomPlugin extends Plugin { this.registerEvent( this.app.workspace.on( - // @ts-expect-error + // @ts-expect-error "loom:set-setting", (setting: string, value: any) => { this.thenSaveAndRender( @@ -877,9 +973,9 @@ export default class LoomPlugin extends Plugin { this.registerEvent( this.app.workspace.on("resize", () => { - this.view().render(); - this.siblingsView().render(); - }) + this.view().render(); + this.siblingsView().render(); + }) ); this.registerEvent( @@ -931,11 +1027,7 @@ export default class LoomPlugin extends Plugin { this.thenSaveAndRender(() => {}); } - async complete() { - const file = this.app.workspace.getActiveFile(); - if (!file) return; - // TODO add async support to withFile and wftsar, so `complete` can be wrapped - + async complete(file: TFile) { this.statusBarItem.style.display = "inline-flex"; const state = this.state[file.path]; @@ -1032,18 +1124,21 @@ export default class LoomPlugin extends Plugin { (generation) => generation.text ); } else if (this.settings.provider === "textsynth") { - const response = await requestUrl({ + const response = await requestUrl({ url: `https://api.textsynth.com/v1/engines/${this.settings.model}/completions`, - method: "POST", - headers: { "Content-Type": "application/json", "Authorization": `Bearer ${this.settings.textsynthApiKey}` }, - body: JSON.stringify({ - prompt, - max_tokens: this.settings.maxTokens, - n: this.settings.n, - temperature: this.settings.temperature, - top_p: this.settings.topP, - }), - }); + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.settings.textsynthApiKey}`, + }, + body: JSON.stringify({ + prompt, + max_tokens: this.settings.maxTokens, + n: this.settings.n, + temperature: this.settings.temperature, + top_p: this.settings.topP, + }), + }); if (response.status !== 200) { new Notice( "TextSynth API responded with status code " + response.status @@ -1052,8 +1147,7 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; return; } - if (this.settings.n === 1) - completions = [response.json.text]; + if (this.settings.n === 1) completions = [response.json.text]; else completions = response.json.text; } else if (this.settings.provider === "ocp") { let url = this.settings.ocpUrl; @@ -1124,7 +1218,7 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = null; this.save(); this.view().render(); - this.siblingsView().render(); + this.siblingsView().render(); this.statusBarItem.style.display = "none"; } @@ -1857,7 +1951,7 @@ class LoomSiblingsView extends ItemView { this.containerEl.empty(); this.containerEl.addClass("loom"); const outline = this.containerEl.createDiv({ cls: "outline" }); - + const state = this.getNoteState(); if (!state) { @@ -1869,8 +1963,10 @@ class LoomSiblingsView extends ItemView { } const parentId = state.nodes[state.current].parentId; - const siblings = Object.entries(state.nodes).filter(([, node]) => node.parentId === parentId); - + const siblings = Object.entries(state.nodes).filter( + ([, node]) => node.parentId === parentId + ); + let currentDiv; for (const i in siblings) { const [id, node] = siblings[i]; @@ -1884,7 +1980,9 @@ class LoomSiblingsView extends ItemView { cls: "loom-sibling-ellipsis", }); siblingDiv.createEl("span", { text: node.text.trim() }); - siblingDiv.addEventListener("click", () => this.app.workspace.trigger("loom:switch-to", id)); + siblingDiv.addEventListener("click", () => + this.app.workspace.trigger("loom:switch-to", id) + ); if (parseInt(i) !== siblings.length - 1) outline.createEl("hr", { cls: "loom-sibling-divider" }); From 180ce1e956e220428b81ff00b74bc0b13b92014b Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 16:35:37 -0400 Subject: [PATCH 24/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index a557fcd..ae90ba6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.2.0", + "version": "1.2.1", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 7430255..5ce6b3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.2.0", + "version": "1.2.1", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 7fb1406e15cc378fdfb1054b3460b568f1fe8a26 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 16:59:40 -0400 Subject: [PATCH 25/68] escape open brackets --- main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.ts b/main.ts index 97da0b2..a5c2f9b 100644 --- a/main.ts +++ b/main.ts @@ -1048,7 +1048,9 @@ export default class LoomPlugin extends Plugin { prompt = prompt.replace(/\s+$/, ""); // replace "\<" with "<", because obsidian tries to render html tags + // and "\[" with "[" prompt = prompt.replace(/\\ Date: Tue, 18 Apr 2023 17:01:57 -0400 Subject: [PATCH 26/68] fix bug moving node while generating --- main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.ts b/main.ts index a5c2f9b..086f0ea 100644 --- a/main.ts +++ b/main.ts @@ -1206,7 +1206,7 @@ export default class LoomPlugin extends Plugin { const id = uuidv4(); state.nodes[id] = { text: completion, - parentId: state.current, + parentId: state.generating, unread: true, collapsed: false, bookmarked: false, From 0a373aaa10564fb81ebe942e5f98c3cc7dba7022 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 17:02:44 -0400 Subject: [PATCH 27/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index ae90ba6..3fa62cf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.2.1", + "version": "1.2.2", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 5ce6b3e..d31c0bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.2.1", + "version": "1.2.2", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From bedca2283dccb9ccf04b4498e1c869940bd5f0c5 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 17:09:28 -0400 Subject: [PATCH 28/68] don't scroll bookmark node into view --- main.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main.ts b/main.ts index 086f0ea..d9cb797 100644 --- a/main.ts +++ b/main.ts @@ -1621,7 +1621,7 @@ class LoomView extends ItemView { node: Node, id: string, container: HTMLElement, - children: boolean + main: boolean ) => { // div for the node and its children const nodeDiv = container.createDiv({}); @@ -1633,13 +1633,13 @@ class LoomView extends ItemView { }${id === state.current ? " is-active" : ""}${ node.color ? ` loom-node-${node.color}` : "" }`, - attr: { id: `loom-node-${id}` }, + attr: main ? { id: `loom-node-${id}` } : {}, }); // an expand/collapse button if the node has children const hasChildren = nodes.filter(([, node]) => node.parentId === id).length > 0; - if (children && hasChildren) { + if (main && hasChildren) { const collapseDiv = itemDiv.createDiv({ cls: `collapse-icon loom-collapse${ node.collapsed ? " is-collapsed" : "" @@ -1833,7 +1833,7 @@ class LoomView extends ItemView { ); // indicate if the node is generating children - if (state.generating === id) { + if (state.generating === id && main) { const generatingDiv = nodeDiv.createDiv({ cls: "loom-node-footer" }); const generatingIcon = generatingDiv.createDiv({ cls: "rotating" }); setIcon(generatingIcon, "loader-2"); @@ -1844,7 +1844,7 @@ class LoomView extends ItemView { } // render children if the node is not collapsed - if (children && !node.collapsed) { + if (main && !node.collapsed) { const hasChildren = nodes.filter(([, node]) => node.parentId === id).length > 0; if (nodeDiv.offsetWidth < 150 && hasChildren) { From 15b5c543b60417ec13eff9360052da977ad13336 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 17:22:17 -0400 Subject: [PATCH 29/68] go to last visited sibling on delete --- main.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index d9cb797..6d804f9 100644 --- a/main.ts +++ b/main.ts @@ -808,8 +808,17 @@ export default class LoomPlugin extends Plugin { (id_) => id_ !== id ); - let fallback = this.nextSibling(id, this.state[file.path]); - if (!fallback) fallback = this.state[file.path].nodes[id].parentId; + let fallback + const siblings = Object.entries(this.state[file.path].nodes).filter( + ([_id, node]) => node.parentId === this.state[file.path].nodes[id].parentId && id !== _id + ); + const byLastVisited = siblings.sort(([_, a], [__, b]) => { + if (a.lastVisited === undefined) return 1; + if (b.lastVisited === undefined) return -1; + return b.lastVisited - a.lastVisited; + }); + if (byLastVisited.length > 0) fallback = byLastVisited[0][0]; + else fallback = this.state[file.path].nodes[id].parentId; let deleted = [id]; From 4617cd3f147166556476ca95b8ef918533c09eab Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 19:54:02 -0400 Subject: [PATCH 30/68] fix broken behavior on non-one loom views --- main.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/main.ts b/main.ts index 6d804f9..9fc9cb7 100644 --- a/main.ts +++ b/main.ts @@ -117,24 +117,22 @@ export default class LoomPlugin extends Plugin { return callback(file); } - view() { - return this.app.workspace - .getLeavesOfType("loom") - .map((leaf) => leaf.view)[0] as LoomView; + renderViews() { + const views = this.app.workspace.getLeavesOfType("loom").map((leaf) => leaf.view) as LoomView[]; + views.forEach((view) => view.render()); } - siblingsView() { - return this.app.workspace - .getLeavesOfType("loom-siblings") - .map((leaf) => leaf.view)[0] as LoomSiblingsView; + renderSiblingsViews() { + const views = this.app.workspace.getLeavesOfType("loom-siblings").map((leaf) => leaf.view) as LoomSiblingsView[]; + views.forEach((view) => view.render()); } thenSaveAndRender(callback: () => void) { callback(); this.save(); - this.view().render(); - this.siblingsView().render(); + this.renderViews(); + this.renderSiblingsViews(); } wftsar(callback: (file: TFile) => void) { @@ -928,8 +926,8 @@ export default class LoomPlugin extends Plugin { this.app.workspace.on("file-open", (file) => { if (!file) return; - this.view().render(); - this.siblingsView().render(); + this.renderViews(); + this.renderSiblingsViews(); this.app.workspace.iterateRootLeaves((leaf) => { if ( @@ -982,8 +980,8 @@ export default class LoomPlugin extends Plugin { this.registerEvent( this.app.workspace.on("resize", () => { - this.view().render(); - this.siblingsView().render(); + this.renderViews(); + this.renderSiblingsViews(); }) ); @@ -1046,8 +1044,8 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = state.current; this.save(); - this.view().render(); - this.siblingsView().render(); + this.renderViews(); + this.renderSiblingsViews(); let prompt = this.fullText(state.current, state); @@ -1229,8 +1227,8 @@ export default class LoomPlugin extends Plugin { this.state[file.path].generating = null; this.save(); - this.view().render(); - this.siblingsView().render(); + this.renderViews(); + this.renderSiblingsViews(); this.statusBarItem.style.display = "none"; } From 82600b4508604bc4450a51eade687fd5ff2ce62f Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 19:54:50 -0400 Subject: [PATCH 31/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 3fa62cf..3fb82fe 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.2.2", + "version": "1.2.3", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index d31c0bf..8154de1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.2.2", + "version": "1.2.3", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 32d8cf28ae45f5176f95953ca266856d582bd8bb Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 20:01:52 -0400 Subject: [PATCH 32/68] allow v1 in url --- main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/main.ts b/main.ts index 9fc9cb7..3c02ea9 100644 --- a/main.ts +++ b/main.ts @@ -1164,6 +1164,7 @@ export default class LoomPlugin extends Plugin { if (!(url.startsWith("http://") || url.startsWith("https://"))) url = "https://" + url; if (!url.endsWith("/")) url += "/"; + url = url.replace(/v1\//, ""); url += "v1/completions"; const response = await fetch(url, { From f505286c93376b8ec49bcc1e61a4b7b57d320520 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 18 Apr 2023 20:40:19 -0400 Subject: [PATCH 33/68] correctly handle non-md files --- main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.ts b/main.ts index 3c02ea9..d76f9f3 100644 --- a/main.ts +++ b/main.ts @@ -172,6 +172,7 @@ export default class LoomPlugin extends Plugin { checkCallback: (checking: boolean) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; + if (file.extension !== "md") return false; // only check if api keys are set, not if they're valid, because that sometimes requires additional api calls if ( @@ -201,6 +202,7 @@ export default class LoomPlugin extends Plugin { ) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; + if (file.extension !== "md") return false; const state = this.state[file.path]; if (!state) this.initializeFile(file); @@ -216,6 +218,7 @@ export default class LoomPlugin extends Plugin { ) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; + if (file.extension !== "md") return false; const state = this.state[file.path]; if (!state) this.initializeFile(file); @@ -925,6 +928,7 @@ export default class LoomPlugin extends Plugin { this.registerEvent( this.app.workspace.on("file-open", (file) => { if (!file) return; + if (file.extension !== "md") return; this.renderViews(); this.renderSiblingsViews(); From 20d5395624cde82354e10b2f8cfcad841414c3b2 Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 19 Apr 2023 00:36:56 -0400 Subject: [PATCH 34/68] switch ocp to requesturl --- main.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index d76f9f3..0bc79ed 100644 --- a/main.ts +++ b/main.ts @@ -1171,7 +1171,8 @@ export default class LoomPlugin extends Plugin { url = url.replace(/v1\//, ""); url += "v1/completions"; - const response = await fetch(url, { + const response = await requestUrl({ + url, method: "POST", headers: { Authorization: `Bearer ${this.settings.ocpApiKey}`, @@ -1191,7 +1192,7 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; return; } - completions = (await response.json()).choices.map( + completions = response.json.choices.map( (choice: any) => choice.text ); } From 57504253d25cfcd61029058a72d380a83f87e1ba Mon Sep 17 00:00:00 2001 From: celeste Date: Wed, 19 Apr 2023 00:37:21 -0400 Subject: [PATCH 35/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 3fb82fe..ef5c28b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.2.3", + "version": "1.2.4", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 8154de1..de014b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.2.3", + "version": "1.2.4", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 0f11f570c4c97e37ccf00869e1f8bcf95223fedc Mon Sep 17 00:00:00 2001 From: celeste Date: Sat, 22 Apr 2023 23:21:58 -0400 Subject: [PATCH 36/68] add to readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18d5966..d7544bf 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ In the editor: 1. Go to the latest release under the "Releases" subheading on the right 2. Download the zip file under "Assets" -3. Unzip the file you downloaded in `[path to vault]/.obsidian/plugins` +3. Unzip the file you downloaded in `[path to vault]/.obsidian/plugins`, creating the `plugins` directory if necessary 4. Go to the "Community plugins" tab in Obsidian settings, then enable "Loom" Alternatively, you can build from source, which makes it easy to update: -1. Clone this repository (`git clone https://github.com/cosmicoptima/loom`) in `[path to vault]/.obsidian/plugins` +1. Clone this repository (`git clone https://github.com/cosmicoptima/loom`) in `[path to vault]/.obsidian/plugins`, creating the `plugins` directory if necessary 2. Run the following: `cd loom; npm i; npm run build` 3. Go to the "Community plugins" tab in Obsidian settings, then enable "Loom" 4. To update, go to the repository and `git pull; npm i; npm run build`, then disable and re-enable Loom From 89ea1633e5078646e2da756464f5e9bd059d874f Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 12:25:29 -0400 Subject: [PATCH 37/68] add basic canvas support --- main.ts | 118 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/main.ts b/main.ts index 0bc79ed..ba85c32 100644 --- a/main.ts +++ b/main.ts @@ -172,7 +172,7 @@ export default class LoomPlugin extends Plugin { checkCallback: (checking: boolean) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; - if (file.extension !== "md") return false; + if (!["md", "canvas"].contains(file.extension)) return false; // only check if api keys are set, not if they're valid, because that sometimes requires additional api calls if ( @@ -190,7 +190,12 @@ export default class LoomPlugin extends Plugin { if (this.settings.provider === "ocp" && !this.settings.ocpApiKey) return false; - if (!checking) this.complete(file); + if (!checking) { + if (file.extension === "md") + this.mdComplete(file); + else if (file.extension === "canvas") + this.canvasComplete(file); + } return true; }, hotkeys: [{ modifiers: ["Ctrl"], key: " " }], @@ -1038,21 +1043,9 @@ export default class LoomPlugin extends Plugin { this.thenSaveAndRender(() => {}); } - async complete(file: TFile) { + async complete(prompt: string) { this.statusBarItem.style.display = "inline-flex"; - const state = this.state[file.path]; - - this.breakAtPoint(); - this.app.workspace.trigger("loom:switch-to", state.current); - - this.state[file.path].generating = state.current; - this.save(); - this.renderViews(); - this.renderSiblingsViews(); - - let prompt = this.fullText(state.current, state); - // remove a trailing space if there is one // store whether there was, so it can be added back post-completion const trailingSpace = prompt.match(/\s+$/); @@ -1072,10 +1065,10 @@ export default class LoomPlugin extends Plugin { prompt = tokenizer.decode(tokens); // complete, or visually display an error and return if that fails - let completions; + let rawCompletions; try { if (this.settings.provider === "openai-chat") { - completions = ( + rawCompletions = ( await this.openai.createChatCompletion({ model: this.settings.model, messages: [{ role: "assistant", content: prompt }], @@ -1086,7 +1079,7 @@ export default class LoomPlugin extends Plugin { }) ).data.choices.map((choice) => choice.message?.content); } else if (this.settings.provider === "openai") { - completions = ( + rawCompletions = ( await this.openai.createCompletion({ model: this.settings.model, prompt, @@ -1133,7 +1126,7 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; return; } - completions = response.body.generations.map( + rawCompletions = response.body.generations.map( (generation) => generation.text ); } else if (this.settings.provider === "textsynth") { @@ -1160,8 +1153,8 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; return; } - if (this.settings.n === 1) completions = [response.json.text]; - else completions = response.json.text; + if (this.settings.n === 1) rawCompletions = [response.json.text]; + else rawCompletions = response.json.text; } else if (this.settings.provider === "ocp") { let url = this.settings.ocpUrl; @@ -1192,21 +1185,20 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; return; } - completions = response.json.choices.map( + rawCompletions = response.json.choices.map( (choice: any) => choice.text ); } - if (completions === undefined) { + if (rawCompletions === undefined) { new Notice("Invalid provider: " + this.settings.provider); this.statusBarItem.style.display = "none"; return; } - // create a child node to the current node for each completion - let ids = []; - for (let completion of completions) { + let completions = []; + for (let completion of rawCompletions) { if (!completion) completion = ""; // empty completions are null, apparently completion = completion.replace(/) => { + if (set.size !== 1) { + new Notice("Node has multiple parents"); + throw new Error("Set has more than one member"); + } + return set.values().next().value; + } + + canvas.selection.forEach(async (node: any) => { + let text = node.text; + let currentNode = canvas.edgeTo.data.get(node); + if (currentNode !== undefined) currentNode = onlySetMember(currentNode).from.node; + while (currentNode) { + text = currentNode.text + " " + text; + currentNode = canvas.edgeTo.data.get(currentNode); + if (currentNode !== undefined) currentNode = onlySetMember(currentNode).from.node; + } + + const completions = await this.complete(text); + if (!completions) return; + + const offset = 62.5 * (completions.length - 1); + for (let i = 0; i < completions.length; i++) { + const completion = completions[i]; + const childNode = canvas.createTextNode({ + file, + pos: { x: node.x + node.width + 50, y: node.y + 125 * i - offset }, + size: { width: 300, height: 100 }, + text: completion, + save: true, + focus: false, + }); + + const data = canvas.getData(); + canvas.importData({ + edges: [...data.edges, { id: uuidv4(), fromNode: node.id, fromSide: "right", toNode: childNode.id, toSide: "left" }], + nodes: data.nodes, + }); + canvas.requestFrame(); + } + }); + } + fullText(id: string, state: NoteState) { let text = ""; From f00159d695b7618a8d4729b9690f063aa6e3e08d Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 12:26:30 -0400 Subject: [PATCH 38/68] bump v --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index ef5c28b..9029470 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.2.4", + "version": "1.3.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index de014b4..a3d1829 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.2.4", + "version": "1.3.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 36bbd610dd6b50427e7494ccb88ef037a8d6a952 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 15:29:42 -0400 Subject: [PATCH 39/68] canvas enhancements 1.3.1 --- main.ts | 43 +++++++++++++++++++++++++++++++++++++++---- manifest.json | 2 +- package.json | 2 +- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/main.ts b/main.ts index ba85c32..5a707a0 100644 --- a/main.ts +++ b/main.ts @@ -1260,7 +1260,6 @@ export default class LoomPlugin extends Plugin { async canvasComplete(file: TFile) { // @ts-expect-error const canvas = this.app.workspace.getActiveViewOfType(ItemView).canvas; - console.log(canvas); const onlySetMember = (set: Set) => { if (set.size !== 1) { @@ -1275,7 +1274,7 @@ export default class LoomPlugin extends Plugin { let currentNode = canvas.edgeTo.data.get(node); if (currentNode !== undefined) currentNode = onlySetMember(currentNode).from.node; while (currentNode) { - text = currentNode.text + " " + text; + text = currentNode.text + text; currentNode = canvas.edgeTo.data.get(currentNode); if (currentNode !== undefined) currentNode = onlySetMember(currentNode).from.node; } @@ -1283,12 +1282,12 @@ export default class LoomPlugin extends Plugin { const completions = await this.complete(text); if (!completions) return; - const offset = 62.5 * (completions.length - 1); + let childNodes: any[] = []; for (let i = 0; i < completions.length; i++) { const completion = completions[i]; const childNode = canvas.createTextNode({ file, - pos: { x: node.x + node.width + 50, y: node.y + 125 * i - offset }, + pos: { x: node.x + node.width + 50, y: node.y }, size: { width: 300, height: 100 }, text: completion, save: true, @@ -1301,7 +1300,43 @@ export default class LoomPlugin extends Plugin { nodes: data.nodes, }); canvas.requestFrame(); + + await new Promise(r => setTimeout(r, 50)); // wait for the element to render + + // get height of new node's element + const element = childNode.nodeEl; + const sizer = element.querySelector(".markdown-preview-sizer"); + const height = sizer.getBoundingClientRect().height; + + const data_ = canvas.getData(); + canvas.importData({ + edges: data_.edges, + nodes: data_.nodes.map((node: any) => { + if (node.id === childNode.id) node.height = height / canvas.scale + 52; + return node; + } + )}); + canvas.requestFrame(); + + childNodes.push(childNode.id); } + + // adjust the y positions of the child nodes + const data = canvas.getData(); + let y = node.y; + canvas.importData({ + edges: data.edges, + nodes: data.nodes.map((node: any) => { + if (childNodes.includes(node.id)) { + node.y = y; + y += node.height + 50; + } + return node; + } + )}); + + canvas.deselectAll(); + childNodes.forEach((id: string) => canvas.select(canvas.nodes.get(id))); }); } diff --git a/manifest.json b/manifest.json index 9029470..4b88fd1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.3.0", + "version": "1.3.1", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index a3d1829..7389a50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.3.0", + "version": "1.3.1", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From e35874e7ca85e055a9b3ba7049550ee4afb5c4a6 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 16:26:58 -0400 Subject: [PATCH 40/68] add canvas split from point --- main.ts | 109 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/main.ts b/main.ts index 5a707a0..55bbe1e 100644 --- a/main.ts +++ b/main.ts @@ -194,7 +194,7 @@ export default class LoomPlugin extends Plugin { if (file.extension === "md") this.mdComplete(file); else if (file.extension === "canvas") - this.canvasComplete(file); + this.canvasComplete(); } return true; }, @@ -203,10 +203,12 @@ export default class LoomPlugin extends Plugin { const withState = ( checking: boolean, - callback: (state: NoteState) => void + callback: (state: NoteState) => void, + canvasCallback?: () => boolean, ) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; + if (file.extension === "canvas" && canvasCallback) return canvasCallback(); if (file.extension !== "md") return false; const state = this.state[file.path]; @@ -219,10 +221,12 @@ export default class LoomPlugin extends Plugin { const withStateAddl = ( checking: boolean, checkCallback: (state: NoteState) => boolean, - callback: (state: NoteState) => void + callback: (state: NoteState) => void, + canvasCallback?: () => boolean, ) => { const file = this.app.workspace.getActiveFile(); if (!file) return false; + if (file.extension === "canvas" && canvasCallback) return canvasCallback(); if (file.extension !== "md") return false; const state = this.state[file.path]; @@ -297,7 +301,7 @@ export default class LoomPlugin extends Plugin { checkCallback: (checking: boolean) => withState(checking, (state) => { this.app.workspace.trigger("loom:break-at-point", state.current); - }), + }, () => this.canvasBreakAtPoint()), hotkeys: [{ modifiers: ["Alt"], key: "c" }], }); @@ -1257,7 +1261,7 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; } - async canvasComplete(file: TFile) { + async canvasComplete() { // @ts-expect-error const canvas = this.app.workspace.getActiveViewOfType(ItemView).canvas; @@ -1285,39 +1289,7 @@ export default class LoomPlugin extends Plugin { let childNodes: any[] = []; for (let i = 0; i < completions.length; i++) { const completion = completions[i]; - const childNode = canvas.createTextNode({ - file, - pos: { x: node.x + node.width + 50, y: node.y }, - size: { width: 300, height: 100 }, - text: completion, - save: true, - focus: false, - }); - - const data = canvas.getData(); - canvas.importData({ - edges: [...data.edges, { id: uuidv4(), fromNode: node.id, fromSide: "right", toNode: childNode.id, toSide: "left" }], - nodes: data.nodes, - }); - canvas.requestFrame(); - - await new Promise(r => setTimeout(r, 50)); // wait for the element to render - - // get height of new node's element - const element = childNode.nodeEl; - const sizer = element.querySelector(".markdown-preview-sizer"); - const height = sizer.getBoundingClientRect().height; - - const data_ = canvas.getData(); - canvas.importData({ - edges: data_.edges, - nodes: data_.nodes.map((node: any) => { - if (node.id === childNode.id) node.height = height / canvas.scale + 52; - return node; - } - )}); - canvas.requestFrame(); - + const childNode = await this.canvasCreateChildNode(canvas, node, completion); childNodes.push(childNode.id); } @@ -1478,6 +1450,67 @@ export default class LoomPlugin extends Plugin { }); } + canvasBreakAtPoint(): boolean { + const view = this.app.workspace.getActiveViewOfType(ItemView); + if (!view) return false; + // @ts-expect-error + const canvas = view.canvas; + + canvas.selection.forEach((node: any) => { + if (!node.isEditing) return; + + const editor = node.child.editor; + const text = editor.getValue(); + const lines = text.split("\n"); + const cursor = editor.getCursor(); + + const before = [...lines.slice(0, cursor.line), lines[cursor.line].slice(0, cursor.ch)].join("\n"); + const after = text.slice(before.length); + + editor.setValue(before); + editor.setCursor({line: cursor.line, ch: cursor.ch - 1}); + + this.canvasCreateChildNode(canvas, node, after); + }); + + return true; + } + + async canvasCreateChildNode(canvas: any, node: any, childText: string) { + const childNode = canvas.createTextNode({ + pos: { x: node.x + node.width + 50, y: node.y }, + size: { width: 300, height: 100 }, + text: childText, + save: true, + focus: false, + }); + + const data = canvas.getData(); + canvas.importData({ + edges: [...data.edges, { id: uuidv4(), fromNode: node.id, fromSide: "right", toNode: childNode.id, toSide: "left" }], + nodes: data.nodes, + }); + canvas.requestFrame(); + + await new Promise(r => setTimeout(r, 50)); // wait for the element to render + + const element = childNode.nodeEl; + const sizer = element.querySelector(".markdown-preview-sizer"); + const height = sizer.getBoundingClientRect().height; + + const data_ = canvas.getData(); + canvas.importData({ + edges: data_.edges, + nodes: data_.nodes.map((node: any) => { + if (node.id === childNode.id) node.height = height / canvas.scale + 52; + return node; + } + )}); + canvas.requestFrame(); + + return childNode; + } + async loadSettings() { const settings = (await this.loadData())?.settings || {}; this.settings = Object.assign({}, DEFAULT_SETTINGS, settings); From 7329185a670da7c492fded7f97404dd81b5d58a8 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 16:27:28 -0400 Subject: [PATCH 41/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 4b88fd1..50f8fe5 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.3.1", + "version": "1.4.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 7389a50..8d432c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.3.1", + "version": "1.4.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 9b36961adc5f1761a216568c9c995b2d89f4bb13 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 16:51:11 -0400 Subject: [PATCH 42/68] complete from point in canvas --- main.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/main.ts b/main.ts index 55bbe1e..6013f3a 100644 --- a/main.ts +++ b/main.ts @@ -1274,7 +1274,21 @@ export default class LoomPlugin extends Plugin { } canvas.selection.forEach(async (node: any) => { - let text = node.text; + let text + let childNodes: any[] = []; + + if (node.isEditing) { + const editor = node.child.editor; + const editorValue = editor.getValue(); + const lines = editorValue.split("\n"); + const cursor = editor.getCursor(); + + text = [...lines.slice(0, cursor.line), lines[cursor.line].slice(0, cursor.ch)].join("\n"); + editor.setValue(text); + const after = editorValue.slice(text.length); + const childNode = await this.canvasCreateChildNode(canvas, node, after); + childNodes.push(childNode.id); + } else text = node.text; let currentNode = canvas.edgeTo.data.get(node); if (currentNode !== undefined) currentNode = onlySetMember(currentNode).from.node; while (currentNode) { @@ -1286,7 +1300,6 @@ export default class LoomPlugin extends Plugin { const completions = await this.complete(text); if (!completions) return; - let childNodes: any[] = []; for (let i = 0; i < completions.length; i++) { const completion = completions[i]; const childNode = await this.canvasCreateChildNode(canvas, node, completion); @@ -1295,10 +1308,11 @@ export default class LoomPlugin extends Plugin { // adjust the y positions of the child nodes const data = canvas.getData(); + const reversedNodes = [...data.nodes].reverse(); let y = node.y; canvas.importData({ edges: data.edges, - nodes: data.nodes.map((node: any) => { + nodes: reversedNodes.map((node: any) => { if (childNodes.includes(node.id)) { node.y = y; y += node.height + 50; From ba09791a39a96a6a5198c23ee5ae579e1fb936de Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 16:51:35 -0400 Subject: [PATCH 43/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 50f8fe5..a469d22 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.4.0", + "version": "1.5.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 8d432c5..5903a70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.4.0", + "version": "1.5.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 7adb6d261e4327911052e146520440dd0e3ff6b8 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 21:53:45 -0400 Subject: [PATCH 44/68] small canvas ui improvements --- main.ts | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/main.ts b/main.ts index 6013f3a..7ef8e61 100644 --- a/main.ts +++ b/main.ts @@ -471,7 +471,10 @@ export default class LoomPlugin extends Plugin { callback: () => this.thenSaveAndRender(() => (this.state = {})), }); - const getState = () => this.withFile((file) => this.state[file.path]); + const getState = () => this.withFile((file) => { + if (file.extension === "canvas") return "canvas"; + return this.state[file.path]; + }); const getSettings = () => this.settings; this.registerView( @@ -1262,6 +1265,8 @@ export default class LoomPlugin extends Plugin { } async canvasComplete() { + new Notice("Generating..."); + // @ts-expect-error const canvas = this.app.workspace.getActiveViewOfType(ItemView).canvas; @@ -1542,12 +1547,12 @@ export default class LoomPlugin extends Plugin { } class LoomView extends ItemView { - getNoteState: () => NoteState | null; + getNoteState: () => NoteState | "canvas" | null; getSettings: () => LoomSettings; constructor( leaf: WorkspaceLeaf, - getNoteState: () => NoteState | null, + getNoteState: () => NoteState | "canvas" | null, getSettings: () => LoomSettings ) { super(leaf); @@ -1770,6 +1775,13 @@ class LoomView extends ItemView { }); return; } + if (state === "canvas") { + container.createEl("div", { + cls: "pane-empty", + text: "The selected note is a canvas.", + }); + return; + } const nodes = Object.entries(state.nodes); @@ -2101,9 +2113,9 @@ class LoomView extends ItemView { } class LoomSiblingsView extends ItemView { - getNoteState: () => NoteState | null; + getNoteState: () => NoteState | "canvas" | null; - constructor(leaf: WorkspaceLeaf, getNoteState: () => NoteState | null) { + constructor(leaf: WorkspaceLeaf, getNoteState: () => NoteState | "canvas" | null) { super(leaf); this.getNoteState = getNoteState; this.render(); @@ -2125,6 +2137,13 @@ class LoomSiblingsView extends ItemView { }); return; } + if (state === "canvas") { + outline.createEl("div", { + text: "The selected note is a canvas.", + cls: "pane-empty", + }); + return; + } const parentId = state.nodes[state.current].parentId; const siblings = Object.entries(state.nodes).filter( From ebcaac1a9135cd66bb4d6c169bafddd0fc47c001 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 21:54:18 -0400 Subject: [PATCH 45/68] bv --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index a469d22..40bf326 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.5.0", + "version": "1.5.1", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 5903a70..955aa1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.5.0", + "version": "1.5.1", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From dbb8f1f54ac79597d6f2eeca3046f4affa754b1b Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 22:06:27 -0400 Subject: [PATCH 46/68] remove import/export on canvas --- main.ts | 78 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/main.ts b/main.ts index 7ef8e61..e380793 100644 --- a/main.ts +++ b/main.ts @@ -1607,45 +1607,47 @@ class LoomView extends ItemView { "Show node borders in the editor" ); - const importFileInput = navButtonsContainer.createEl("input", { - cls: "hidden", - attr: { type: "file", id: "loom-import" }, - }); - const importNavButton = navButtonsContainer.createEl("label", { - cls: "clickable-icon nav-action-button", - attr: { "aria-label": "Import JSON", for: "loom-import" }, - }); - setIcon(importNavButton, "import"); - importFileInput.addEventListener("change", () => - // @ts-expect-error - this.app.workspace.trigger("loom:import", importFileInput.files[0].path) - ); + if (state !== "canvas") { + const importFileInput = navButtonsContainer.createEl("input", { + cls: "hidden", + attr: { type: "file", id: "loom-import" }, + }); + const importNavButton = navButtonsContainer.createEl("label", { + cls: "clickable-icon nav-action-button", + attr: { "aria-label": "Import JSON", for: "loom-import" }, + }); + setIcon(importNavButton, "import"); + importFileInput.addEventListener("change", () => + // @ts-expect-error + this.app.workspace.trigger("loom:import", importFileInput.files[0].path) + ); - const exportNavButton = navButtonsContainer.createDiv({ - cls: `clickable-icon nav-action-button${ - settings.showExport ? " is-active" : "" - }`, - attr: { "aria-label": "Export to JSON" }, - }); - setIcon(exportNavButton, "download"); - exportNavButton.addEventListener("click", (e) => { - if (e.shiftKey) - this.app.workspace.trigger( - "loom:set-setting", - "showExport", - !settings.showExport - ); - else - dialog - .showSaveDialog({ - title: "Export to JSON", - filters: [{ extensions: ["json"] }], - }) - .then((result: any) => { - if (result) - this.app.workspace.trigger("loom:export", result.filePath); - }); - }); + const exportNavButton = navButtonsContainer.createDiv({ + cls: `clickable-icon nav-action-button${ + settings.showExport ? " is-active" : "" + }`, + attr: { "aria-label": "Export to JSON" }, + }); + setIcon(exportNavButton, "download"); + exportNavButton.addEventListener("click", (e) => { + if (e.shiftKey) + this.app.workspace.trigger( + "loom:set-setting", + "showExport", + !settings.showExport + ); + else + dialog + .showSaveDialog({ + title: "Export to JSON", + filters: [{ extensions: ["json"] }], + }) + .then((result: any) => { + if (result) + this.app.workspace.trigger("loom:export", result.filePath); + }); + }); + } // create the main container, which uses the `outline` class, which has // a margin visually consistent with other panes From 8ceaa3008c918a5c5aa2f343ddd9f7e929f67045 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 28 Apr 2023 22:06:47 -0400 Subject: [PATCH 47/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 40bf326..e114a6e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.5.1", + "version": "1.5.2", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 955aa1d..9fdd5e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.5.1", + "version": "1.5.2", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 213426d58f7a64dcc952f4285ab2c013e3c4cb85 Mon Sep 17 00:00:00 2001 From: TDC13 <32446533+TDC13@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:22:25 -0500 Subject: [PATCH 48/68] Added a way to use azure openai api. --- main.ts | 82 +++++++++++++++++++++++++++++++++++++++++++---- package-lock.json | 14 ++++++-- package.json | 1 + 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/main.ts b/main.ts index ba85c32..61d7c8c 100644 --- a/main.ts +++ b/main.ts @@ -28,13 +28,14 @@ import * as cohere from "cohere-ai"; import * as fs from "fs"; import GPT3Tokenizer from "gpt3-tokenizer"; import { Configuration, OpenAIApi } from "openai"; +import { Configuration as AzureConfiguration, OpenAIApi as AzureOpenAiApi} from "azure-openai"; import { v4 as uuidv4 } from "uuid"; const dialog = require("electron").remote.dialog; const untildify = require("untildify") as any; const tokenizer = new GPT3Tokenizer({ type: "codex" }); // TODO depends on model -const PROVIDERS = ["cohere", "textsynth", "ocp", "openai", "openai-chat"]; +const PROVIDERS = ["cohere", "textsynth", "ocp", "openai", "openai-chat", "azure", "azure-chat"]; type Provider = (typeof PROVIDERS)[number]; interface LoomSettings { @@ -42,6 +43,10 @@ interface LoomSettings { cohereApiKey: string; textsynthApiKey: string; + azureApiKey: string; + azureEndpoint: string; + + ocpApiKey: string; ocpUrl: string; @@ -68,6 +73,9 @@ const DEFAULT_SETTINGS: LoomSettings = { cohereApiKey: "", textsynthApiKey: "", + azureApiKey: "", + azureEndpoint: "", + ocpApiKey: "", ocpUrl: "", @@ -110,6 +118,7 @@ export default class LoomPlugin extends Plugin { statusBarItem: HTMLElement; openai: OpenAIApi; + azureopenai: AzureOpenAiApi; withFile(callback: (file: TFile) => T): T | null { const file = this.app.workspace.getActiveFile(); @@ -152,6 +161,19 @@ export default class LoomPlugin extends Plugin { cohere.init(this.settings.cohereApiKey); } + setAzureOpenAI() { + const configuration = new AzureConfiguration({ + apiKey: this.settings.azureApiKey, + // add azure info into configuration + azure: { + apiKey: this.settings.azureApiKey, + endpoint: this.settings.azureEndpoint + } + }); + this.azureopenai = new AzureOpenAiApi(configuration); + } + + async onload() { await this.loadSettings(); await this.loadState(); @@ -160,6 +182,7 @@ export default class LoomPlugin extends Plugin { this.setOpenAI(); this.setCohere(); + this.setAzureOpenAI(); this.statusBarItem = this.addStatusBarItem(); this.statusBarItem.setText("Completing..."); @@ -180,6 +203,12 @@ export default class LoomPlugin extends Plugin { !this.settings.openaiApiKey ) return false; + if ( + ["azure", "azure-chat"].includes(this.settings.provider) && + !this.settings.azureApiKey + ) + return false; + if (this.settings.provider === "cohere" && !this.settings.cohereApiKey) return false; if ( @@ -189,7 +218,7 @@ export default class LoomPlugin extends Plugin { return false; if (this.settings.provider === "ocp" && !this.settings.ocpApiKey) return false; - + if (!checking) { if (file.extension === "md") this.mdComplete(file); @@ -1067,7 +1096,7 @@ export default class LoomPlugin extends Plugin { // complete, or visually display an error and return if that fails let rawCompletions; try { - if (this.settings.provider === "openai-chat") { + if (["openai-chat"].includes(this.settings.provider)) { rawCompletions = ( await this.openai.createChatCompletion({ model: this.settings.model, @@ -1078,7 +1107,7 @@ export default class LoomPlugin extends Plugin { top_p: this.settings.topP, }) ).data.choices.map((choice) => choice.message?.content); - } else if (this.settings.provider === "openai") { + } else if (["openai"].includes(this.settings.provider)) { rawCompletions = ( await this.openai.createCompletion({ model: this.settings.model, @@ -1089,18 +1118,40 @@ export default class LoomPlugin extends Plugin { top_p: this.settings.topP, }) ).data.choices.map((choice) => choice.text); + } else if (["azure-chat"].includes(this.settings.provider)) { + rawCompletions = ( + await this.azureopenai.createChatCompletion({ + model: this.settings.model, + messages: [{ role: "assistant", content: prompt }], + max_tokens: this.settings.maxTokens, + n: this.settings.n, + temperature: this.settings.temperature, + top_p: this.settings.topP, + }) + ).data.choices.map((choice) => choice.message?.content); + } else if (["azure"].includes(this.settings.provider)) { + rawCompletions = ( + await this.azureopenai.createCompletion({ + model: this.settings.model, + prompt, + max_tokens: this.settings.maxTokens, + n: this.settings.n, + temperature: this.settings.temperature, + top_p: this.settings.topP, + }) + ).data.choices.map((choice) => choice.text); } } catch (e) { if ( e.response.status === 401 && - ["openai", "openai-chat"].includes(this.settings.provider) + ["openai", "openai-chat", "azure", "azure-chat"].includes(this.settings.provider) ) new Notice( "OpenAI API key is invalid. Please provide a valid key in the settings." ); else if ( e.response.status === 429 && - ["openai", "openai-chat"].includes(this.settings.provider) + ["openai", "openai-chat", "azure", "azure-chat"].includes(this.settings.provider) ) new Notice("OpenAI API rate limit exceeded."); else new Notice("Unknown API error: " + e.response.data.error.message); @@ -1203,7 +1254,7 @@ export default class LoomPlugin extends Plugin { completion = completion.replace(/ { const optionEl = providerSelect.createEl("option", { @@ -2238,6 +2291,8 @@ class LoomSettingTab extends PluginSettingTab { dropdown.addOption("ocp", "OpenAI code-davinci-002 proxy"); dropdown.addOption("openai", "OpenAI (Completion)"); dropdown.addOption("openai-chat", "OpenAI (Chat)"); + dropdown.addOption("azure", "Azure (Completion)"); + dropdown.addOption("azure-chat", "Azure (Chat)"); dropdown.setValue(this.plugin.settings.provider); dropdown.onChange(async (value) => { if (PROVIDERS.find((provider) => provider === value)) @@ -2276,6 +2331,19 @@ class LoomSettingTab extends PluginSettingTab { apiKeySetting("OpenAI", "openaiApiKey"); + apiKeySetting("Azure", "azureApiKey") + + new Setting(containerEl) + .setName("Azure-Openai Resource Endpoint") + .setDesc("Required if using Azure") + .addText((text) => + text.setValue(this.plugin.settings.azureEndpoint).onChange(async (value) => { + this.plugin.settings.azureEndpoint = value; + await this.plugin.save(); + }) + ); + + // TODO: reduce duplication of other settings new Setting(containerEl).setName("Model").addText((text) => diff --git a/package-lock.json b/package-lock.json index c9f2d4e..d248c91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "obsidian-loom", - "version": "0.1.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-loom", - "version": "0.1.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.3", "@types/lodash": "^4.14.191", + "azure-openai": "^0.9.4", "cohere-ai": "^6.1.0", "gpt3-tokenizer": "^1.1.5", "openai": "^3.2.0", @@ -497,6 +498,15 @@ "follow-redirects": "^1.14.8" } }, + "node_modules/azure-openai": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/azure-openai/-/azure-openai-0.9.4.tgz", + "integrity": "sha512-7uii4ZInxzu2zjLg45PdvgOaw3ps18tEAw0Yux9mo8anX4PwnCMSS9xdlKNiNQyyEKPogvAcxH2PIufHXFLx6Q==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index a3d1829..ecc77d6 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.3", "@types/lodash": "^4.14.191", + "azure-openai": "^0.9.4", "cohere-ai": "^6.1.0", "gpt3-tokenizer": "^1.1.5", "openai": "^3.2.0", From 7e681ef5456ebc832c21f3b77531b3102dc72ae1 Mon Sep 17 00:00:00 2001 From: TDC13 <32446533+TDC13@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:37:43 -0500 Subject: [PATCH 49/68] Added link to azure-openai node module doc --- main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/main.ts b/main.ts index 641b8e9..a7935e1 100644 --- a/main.ts +++ b/main.ts @@ -161,6 +161,7 @@ export default class LoomPlugin extends Plugin { cohere.init(this.settings.cohereApiKey); } + // https://github.com/1openwindow/azure-openai-node setAzureOpenAI() { const configuration = new AzureConfiguration({ apiKey: this.settings.azureApiKey, From bc294bc9c471315df7a573c83743e1d65266c799 Mon Sep 17 00:00:00 2001 From: TDC13 <32446533+TDC13@users.noreply.github.com> Date: Mon, 1 May 2023 18:40:35 -0500 Subject: [PATCH 50/68] I forgot something --- main.ts | 5 ++++- package-lock.json | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/main.ts b/main.ts index a7935e1..d446038 100644 --- a/main.ts +++ b/main.ts @@ -272,7 +272,9 @@ export default class LoomPlugin extends Plugin { const loomPanes = this.app.workspace.getLeavesOfType("loom"); try { if (loomPanes.length === 0) - this.app.workspace.getRightLeaf(false).setViewState({ type: "loom" }); + this.app.workspace + .getRightLeaf(false) + .setViewState({ type: "loom" }); else if (focus) this.app.workspace.revealLeaf(loomPanes[0]); } catch (e) { console.error(e); @@ -1594,6 +1596,7 @@ export default class LoomPlugin extends Plugin { async save() { await this.saveData({ settings: this.settings, state: this.state }); this.setOpenAI(); + this.setAzureOpenAI(); this.setCohere(); } } diff --git a/package-lock.json b/package-lock.json index d248c91..b73bcc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-loom", - "version": "1.3.0", + "version": "1.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-loom", - "version": "1.3.0", + "version": "1.5.2", "license": "MIT", "dependencies": { "@codemirror/state": "^6.2.0", From c4db781e2743a4866b95abe8c527e86600bbc794 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 May 2023 22:50:05 -0400 Subject: [PATCH 51/68] add generate siblings --- main.ts | 98 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/main.ts b/main.ts index e380793..3acfd52 100644 --- a/main.ts +++ b/main.ts @@ -165,42 +165,52 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.setText("Completing..."); this.statusBarItem.style.display = "none"; + const complete = (checking: boolean, siblings: boolean) => { + const file = this.app.workspace.getActiveFile(); + if (!file) return false; + if (!["md", "canvas"].contains(file.extension)) return false; + + // only check if api keys are set, not if they're valid, because that sometimes requires additional api calls + if ( + ["openai", "openai-chat"].includes(this.settings.provider) && + !this.settings.openaiApiKey + ) + return false; + if (this.settings.provider === "cohere" && !this.settings.cohereApiKey) + return false; + if ( + this.settings.provider === "textsynth" && + !this.settings.textsynthApiKey + ) + return false; + if (this.settings.provider === "ocp" && !this.settings.ocpApiKey) + return false; + + if (!checking) { + if (file.extension === "md") + this.mdComplete(file, siblings); + else if (file.extension === "canvas") + this.canvasComplete(siblings); + } + return true; + } + this.addCommand({ id: "complete", name: "Complete from current point", icon: "wand", - checkCallback: (checking: boolean) => { - const file = this.app.workspace.getActiveFile(); - if (!file) return false; - if (!["md", "canvas"].contains(file.extension)) return false; - - // only check if api keys are set, not if they're valid, because that sometimes requires additional api calls - if ( - ["openai", "openai-chat"].includes(this.settings.provider) && - !this.settings.openaiApiKey - ) - return false; - if (this.settings.provider === "cohere" && !this.settings.cohereApiKey) - return false; - if ( - this.settings.provider === "textsynth" && - !this.settings.textsynthApiKey - ) - return false; - if (this.settings.provider === "ocp" && !this.settings.ocpApiKey) - return false; - - if (!checking) { - if (file.extension === "md") - this.mdComplete(file); - else if (file.extension === "canvas") - this.canvasComplete(); - } - return true; - }, + checkCallback: (checking: boolean) => complete(checking, false), hotkeys: [{ modifiers: ["Ctrl"], key: " " }], }); + this.addCommand({ + id: "generate-siblings", + name: "Generate siblings", + icon: "wand", + checkCallback: (checking: boolean) => complete(checking, true), + hotkeys: [{ modifiers: ["Ctrl", "Shift"], key: " " }], + }); + const withState = ( checking: boolean, callback: (state: NoteState) => void, @@ -1222,18 +1232,27 @@ export default class LoomPlugin extends Plugin { return completions; } - async mdComplete(file: TFile) { + async mdComplete(file: TFile, siblings: boolean) { const state = this.state[file.path]; - this.breakAtPoint(); - this.app.workspace.trigger("loom:switch-to", state.current); + let rootNode + if (siblings) + rootNode = state.nodes[state.current].parentId; + else { + this.breakAtPoint(); + rootNode = state.current; + } + + if (rootNode !== null) { + this.app.workspace.trigger("loom:switch-to", rootNode); + this.state[file.path].generating = rootNode; + } - this.state[file.path].generating = state.current; this.save(); this.renderViews(); this.renderSiblingsViews(); - let prompt = this.fullText(state.current, state); + let prompt = this.fullText(rootNode, state); const completions = await this.complete(prompt); if (!completions) return; @@ -1264,7 +1283,12 @@ export default class LoomPlugin extends Plugin { this.statusBarItem.style.display = "none"; } - async canvasComplete() { + async canvasComplete(siblings: boolean) { + if (siblings) { + new Notice("Not yet supported in canvas view"); + return; + } + new Notice("Generating..."); // @ts-expect-error @@ -1331,10 +1355,10 @@ export default class LoomPlugin extends Plugin { }); } - fullText(id: string, state: NoteState) { + fullText(id: string | null, state: NoteState) { let text = ""; - let current: string | null = id; + let current = id; while (current) { text = state.nodes[current].text + text; current = state.nodes[current].parentId; From 88ae9993ff8b6f61219e57fe85014fa2ce2545a7 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 May 2023 22:50:50 -0400 Subject: [PATCH 52/68] readme, bump ver --- README.md | 1 + manifest.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7544bf..5fdeed2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ If you are interested in funding development of Loom(sidian), you can **[support **Default hotkeys:** - `Ctrl+Space` - complete +- `Ctrl+Shift+Space` - generate siblings - `Ctrl+Alt+n` - create child - `Alt+n` - create sibling - `Ctrl+Alt+c` - clone current node diff --git a/manifest.json b/manifest.json index e114a6e..8f07cc5 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.5.2", + "version": "1.6.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 9fdd5e2..ea520cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.5.2", + "version": "1.6.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From d3565948c36d0258505f5e1e9e2cc54f4859d207 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 May 2023 23:07:25 -0400 Subject: [PATCH 53/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 8f07cc5..af6ee90 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.6.0", + "version": "1.7.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index ebd6618..a27de4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.6.0", + "version": "1.7.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 0342d74cca67753770b55e61b6ee73015597e5b5 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 May 2023 23:09:04 -0400 Subject: [PATCH 54/68] fix fail if no azure --- main.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.ts b/main.ts index 7f08a1b..54a8dde 100644 --- a/main.ts +++ b/main.ts @@ -160,8 +160,9 @@ export default class LoomPlugin extends Plugin { cohere.init(this.settings.cohereApiKey); } - // https://github.com/1openwindow/azure-openai-node setAzureOpenAI() { + if (!this.settings.azureApiKey || !this.settings.azureEndpoint) return; + const configuration = new AzureConfiguration({ apiKey: this.settings.azureApiKey, // add azure info into configuration From 60d44230747275d03bfc8832d8b05029604677d1 Mon Sep 17 00:00:00 2001 From: celeste Date: Mon, 1 May 2023 23:09:30 -0400 Subject: [PATCH 55/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index af6ee90..98ac3eb 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.7.0", + "version": "1.7.1", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index a27de4d..875d41c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.7.0", + "version": "1.7.1", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 8bb28a8d8c08aaba7a93ac6c68879c0ff5c06a88 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:02:30 -0400 Subject: [PATCH 56/68] ugly settings column --- main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 54a8dde..93c97c1 100644 --- a/main.ts +++ b/main.ts @@ -217,7 +217,7 @@ export default class LoomPlugin extends Plugin { if (!checking) { if (file.extension === "md") - this.mdComplete(file, siblings); + this.mdComplete(file, siblings); else if (file.extension === "canvas") this.canvasComplete(siblings); } @@ -2457,7 +2457,7 @@ class LoomSettingTab extends PluginSettingTab { apiKeySetting("Azure", "azureApiKey") new Setting(containerEl) - .setName("Azure-Openai Resource Endpoint") + .setName("Azure resource endpoint") .setDesc("Required if using Azure") .addText((text) => text.setValue(this.plugin.settings.azureEndpoint).onChange(async (value) => { From a88243b0166b93c9e886afab0e436bdaa95ed6cb Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:02:44 -0400 Subject: [PATCH 57/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 98ac3eb..f5a4ec3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.7.1", + "version": "1.7.2", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 875d41c..7d62edc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.7.1", + "version": "1.7.2", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 88f5177004cd79220ce7682fcee9d2f176d138a0 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:33:39 -0400 Subject: [PATCH 58/68] readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5fdeed2..6916d03 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an O Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. +Loom also works on canvas files, but it doesn't work *well*, and I don't plan to improve this in the near future. + Loom can request completions from the following providers: [Cohere](https://docs.cohere.ai/docs), [TextSynth](https://textsynth.com/documentation.html), and [OpenAI](https://platform.openai.com/docs/introduction). It can also request completions from implementations of [openai-cd2-proxy](https://github.com/cosmicoptima/openai-cd2-proxy), in which case you must provide the base URL in the Loom tab in Settings. If you are interested in funding development of Loom(sidian), you can **[support me on Patreon](https://patreon.com/parafactual)**; I would then be able to devote more time to this and other independent projects. From 599cace1093a40b85206fc7594c454204ebe4162 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:41:48 -0400 Subject: [PATCH 59/68] readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6916d03..35b7671 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # loom(sidian) +**I RECOMMEND YOU READ THE README IF YOU PLAN TO USE THIS PLUGIN. IT IS USEFUL** + This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an Obsidian plugin, designed to be easier to use and more modular and extensible. Loom is a recursively branching interface to GPT-3 and other language models; it is designed to be conducive to exploratory and experimental use of base models. The workflow primarily consists of this: you hit `Ctrl+Space` from a point in the text, and Loom generates `n` child nodes of the current node, where each child contains a different completion of the text leading up to the cursor. This is paired with a tree interface and settings panel in the right sidebar, as well as a pane containing the full text of the current node and its siblings. From 5b61bf092a6fe5deac11e7088901cec3f10d6324 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:42:45 -0400 Subject: [PATCH 60/68] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35b7671..93011b4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # loom(sidian) -**I RECOMMEND YOU READ THE README IF YOU PLAN TO USE THIS PLUGIN. IT IS USEFUL** +**READ THE README IF YOU PLAN TO USE THIS PLUGIN. IT IS USEFUL** This is a reimplementation of [Loom](https://github.com/socketteer/loom) as an Obsidian plugin, designed to be easier to use and more modular and extensible. From 570d0dc8864a1e8ea8128aa88da3938499963e17 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:48:59 -0400 Subject: [PATCH 61/68] dijwuhbfwedw --- README.md | 24 ++++++++++++------------ main.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 93011b4..6c7d766 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,20 @@ If you are interested in funding development of Loom(sidian), you can **[support **Default hotkeys:** -- `Ctrl+Space` - complete -- `Ctrl+Shift+Space` - generate siblings -- `Ctrl+Alt+n` - create child -- `Alt+n` - create sibling -- `Ctrl+Alt+c` - clone current node -- `Alt+c` - split in-range node at caret -- `Alt+Backspace` - delete current node +- generate - `Ctrl+Space` +- generate siblings - `Ctrl+Shift+Space` +- create child - `Ctrl+Alt+n` +- create sibling - `Alt+n` +- clone (current node) - `Ctrl+Alt+c` +- split at point - `Alt+c` +- delete (current node) - `Alt+Backspace` Navigation: -- `Alt+Down` - switch to next sibling -- `Alt+Up` - switch to previous sibling -- `Alt+Left` - switch to parent -- `Alt+Right` - switch to (most recently visited) child -- `Alt+e` - collapse/expand current node +- switch to next sibling - `Alt+Down` +- switch to previous sibling - `Alt+Up` +- switch to parent - `Alt+Left` +- switch to (most recently visited) child - `Alt+Right` +- collapse/expand current node - `Alt+e` In the editor: - `Shift+click` on the text corresponding to a node to switch to it diff --git a/main.ts b/main.ts index 93c97c1..2539715 100644 --- a/main.ts +++ b/main.ts @@ -338,7 +338,7 @@ export default class LoomPlugin extends Plugin { this.addCommand({ id: "break-at-point", - name: "Branch from current point", + name: "Split at current point", checkCallback: (checking: boolean) => withState(checking, (state) => { this.app.workspace.trigger("loom:break-at-point", state.current); From a28657d94050f863e0de0f9458283365f23cb87e Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:54:14 -0400 Subject: [PATCH 62/68] clean up hotkeys --- README.md | 3 --- main.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/README.md b/README.md index 6c7d766..b3f8f3d 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ If you are interested in funding development of Loom(sidian), you can **[support - generate - `Ctrl+Space` - generate siblings - `Ctrl+Shift+Space` -- create child - `Ctrl+Alt+n` -- create sibling - `Alt+n` - clone (current node) - `Ctrl+Alt+c` - split at point - `Alt+c` - delete (current node) - `Alt+Backspace` @@ -29,7 +27,6 @@ Navigation: - switch to previous sibling - `Alt+Up` - switch to parent - `Alt+Left` - switch to (most recently visited) child - `Alt+Right` -- collapse/expand current node - `Alt+e` In the editor: - `Shift+click` on the text corresponding to a node to switch to it diff --git a/main.ts b/main.ts index 2539715..0156eab 100644 --- a/main.ts +++ b/main.ts @@ -311,7 +311,6 @@ export default class LoomPlugin extends Plugin { withState(checking, (state) => { this.app.workspace.trigger("loom:create-child", state.current); }), - hotkeys: [{ modifiers: ["Ctrl", "Alt"], key: "n" }], }); this.addCommand({ @@ -322,7 +321,6 @@ export default class LoomPlugin extends Plugin { withState(checking, (state) => { this.app.workspace.trigger("loom:create-sibling", state.current); }), - hotkeys: [{ modifiers: ["Alt"], key: "n" }], }); this.addCommand({ @@ -497,7 +495,6 @@ export default class LoomPlugin extends Plugin { withState(checking, (state) => { this.app.workspace.trigger("loom:toggle-collapse", state.current); }), - hotkeys: [{ modifiers: ["Alt"], key: "e" }], }); this.addCommand({ From 6bc7ed6b4cae9e77e37c3b92fcbb12a0f3ba29f6 Mon Sep 17 00:00:00 2001 From: celeste Date: Tue, 2 May 2023 17:54:36 -0400 Subject: [PATCH 63/68] bump ver --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index f5a4ec3..c70a51d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.7.2", + "version": "1.8.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index 7d62edc..ae428c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.7.2", + "version": "1.8.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 75ff0adc024642e09016d1d6ea74722d8c270529 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 May 2023 01:06:14 -0400 Subject: [PATCH 64/68] bump version --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index c70a51d..95e1b2e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.8.0", + "version": "1.10.0", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index ae428c9..e5e5ccb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.8.0", + "version": "1.10.0", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 14fc5eda9eb2da1bbf42a07905cf4743a4b47ae5 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 May 2023 01:11:39 -0400 Subject: [PATCH 65/68] fix broken node styling --- styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/styles.css b/styles.css index bd034ba..094196b 100644 --- a/styles.css +++ b/styles.css @@ -145,6 +145,7 @@ overflow: hidden; text-overflow: ellipsis; padding: 0.35em 0 0.35em 0.6em; + flex-grow: 1; } .loom-node-bookmark { From 3c73ba0f56b47c2dbead641560cf569c3a7b12ae Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 May 2023 01:12:11 -0400 Subject: [PATCH 66/68] bump v --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 95e1b2e..4ae69f6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.10.0", + "version": "1.10.1", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index e5e5ccb..eba28f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.10.0", + "version": "1.10.1", "description": "Loom in Obsidian", "main": "main.js", "scripts": { From 5f28f3297d0a1c43d6c4017f992728a41f3200d2 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 May 2023 16:27:50 -0400 Subject: [PATCH 67/68] prepend endoftext --- main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.ts b/main.ts index 0156eab..861b3e0 100644 --- a/main.ts +++ b/main.ts @@ -1296,7 +1296,7 @@ export default class LoomPlugin extends Plugin { this.renderViews(); this.renderSiblingsViews(); - let prompt = this.fullText(rootNode, state); + let prompt = `<|endoftext|>${this.fullText(rootNode, state)}`; const completions = await this.complete(prompt); if (!completions) return; From 359759cc60d877b533850ee09f4713d6f785c1d1 Mon Sep 17 00:00:00 2001 From: celeste Date: Fri, 19 May 2023 16:28:05 -0400 Subject: [PATCH 68/68] bump v --- manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 4ae69f6..93e926e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "loom", "name": "Loom", - "version": "1.10.1", + "version": "1.10.2", "minAppVersion": "0.15.0", "description": "Loom in Obsidian", "author": "celeste", diff --git a/package.json b/package.json index eba28f5..e871c5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-loom", - "version": "1.10.1", + "version": "1.10.2", "description": "Loom in Obsidian", "main": "main.js", "scripts": {