Skip to content

Commit

Permalink
add passages
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicoptima committed Jul 3, 2023
1 parent 6aed144 commit f3aed39
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 9 deletions.
4 changes: 4 additions & 0 deletions common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface LoomSettings {
ocpApiKey: string;
ocpUrl: string;

passageFolder: string;
defaultPassageSeparator: string;
defaultPassageFrontmatter: string;

provider: Provider;
model: string;
maxTokens: number;
Expand Down
87 changes: 83 additions & 4 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { LoomView, LoomSiblingsView, LoomEditorPlugin, loomEditorPluginSpec } from './views';
import {
LoomView,
LoomSiblingsView,
LoomEditorPlugin,
loomEditorPluginSpec,
MakePromptFromPassagesModal,
} from './views';
import { PROVIDERS, Provider, LoomSettings, Node, NoteState } from './common';

import {
Expand All @@ -23,6 +29,7 @@ import p50k from "gpt-tokenizer/esm/model/text-davinci-003";
import r50k from "gpt-tokenizer/esm/model/davinci";

import * as fs from "fs";
import { toRoman } from "roman-numerals";
import { v4 as uuidv4 } from "uuid";
const untildify = require("untildify") as any;

Expand All @@ -48,6 +55,10 @@ const DEFAULT_SETTINGS: LoomSettings = {
ocpApiKey: "",
ocpUrl: "",

passageFolder: "",
defaultPassageSeparator: "\\n\\n---\\n\\n",
defaultPassageFrontmatter: "%r:\\n",

provider: "ocp",
model: "code-davinci-002",
maxTokens: 60,
Expand Down Expand Up @@ -494,6 +505,24 @@ export default class LoomPlugin extends Plugin {
}),
});

const getState = () => this.withFile((file) => this.state[file.path]);
const getSettings = () => this.settings;

this.addCommand({
id: "make-prompt-from-passages",
name: "Make prompt from passages",
callback: () => {
if (this.settings.passageFolder.trim() === "") {
new Notice("Please set the passage folder in settings");
return;
}
new MakePromptFromPassagesModal(
this.app,
getSettings,
).open();
}
});

this.addCommand({
id: "open-pane",
name: "Open Loom pane",
Expand All @@ -518,9 +547,6 @@ export default class LoomPlugin extends Plugin {
callback: () => this.wftsar((file) => (this.state[file.path].hoisted = [])),
});

const getState = () => this.withFile((file) => this.state[file.path]);
const getSettings = () => this.settings;

this.registerView(
"loom",
(leaf) => new LoomView(leaf, getState, getSettings)
Expand Down Expand Up @@ -921,6 +947,46 @@ export default class LoomPlugin extends Plugin {
)
);

this.registerEvent(
this.app.workspace.on(
// @ts-expect-error
"loom:make-prompt-from-passages",
(
passages: string[],
rawSeparator: string,
rawFrontmatter: string,
) => this.wftsar((file) => {
const separator = rawSeparator.replace(/\\n/g, "\n");
const frontmatter = (index: number) => rawFrontmatter
.replace(/%n/g, (index + 1).toString())
.replace(/%r/g, toRoman(index + 1))
.replace(/\\n/g, "\n");

const passageTexts = passages.map((passage, index) => {
return Object.entries(this.state[passage].nodes)
.filter(([, node]) => node.parentId === null)
.map(([, node]) => frontmatter(index) + node.text);
});
const text = `${passageTexts.join(separator)}${separator}${frontmatter(passages.length)}`;

const state = this.state[file.path];
const currentNode = state.nodes[state.current];

let id;
if (currentNode.text === "" && currentNode.parentId === null) {
this.state[file.path].nodes[state.current].text = text;
id = state.current;
} else {
const [newId, newNode] = this.newNode(text, null);
this.state[file.path].nodes[newId] = newNode;
id = newId;
}

this.app.workspace.trigger("loom:switch-to", id);
})
)
);

const onFileOpen = (file: TFile) => {
if (file.extension !== "md") return;

Expand Down Expand Up @@ -1396,6 +1462,19 @@ class LoomSettingTab extends PluginSettingTab {
idSetting("OpenAI organization ID", "openaiOrganization");
apiKeySetting("Azure", "azureApiKey")
idSetting("Azure resource endpoint", "azureEndpoint");

new Setting(containerEl)
.setName("Passage folder location")
.setDesc("Passages can be quickly combined into a multipart prompt")
.addText((text) =>
text.setValue(this.plugin.settings.passageFolder).onChange(async (value) => {
this.plugin.settings.passageFolder = value;
await this.plugin.save();
})
);

idSetting("Default passage separator", "defaultPassageSeparator");
idSetting("Default passage frontmatter", "defaultPassageFrontmatter");

idSetting("Model", "model");
intSetting("Length (in tokens)", "maxTokens");
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "loom",
"name": "Loom",
"version": "1.13.1",
"version": "1.14.0",
"minAppVersion": "0.15.0",
"description": "Loom in Obsidian",
"author": "celeste",
Expand Down
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-loom",
"version": "1.13.1",
"version": "1.14.0",
"description": "Loom in Obsidian",
"main": "main.js",
"scripts": {
Expand All @@ -27,10 +27,12 @@
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.3",
"@types/lodash": "^4.14.191",
"@types/roman-numerals": "^0.3.0",
"azure-openai": "^0.9.4",
"cohere-ai": "^6.1.0",
"gpt-tokenizer": "^2.1.1",
"openai": "^3.2.0",
"roman-numerals": "^0.3.2",
"untildify": "^4.0.0",
"uuid": "^9.0.0"
}
Expand Down
31 changes: 31 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,37 @@ body {
border-right: 2px dashed #333;
}

/* "make prompt from passages" modal */

.loom__passage-list {
max-height: 10em;
overflow-y: auto;
}

.loom__passage {
display: flex;
align-items: center;
padding: 0.35em 0 0.35em 0.6em;
}
.loom__passage:hover {
background-color: var(--nav-item-background-active);
}

.loom__selected-passages-title {
font-weight: bold;
margin: 1em 0 0.5em;
}

.loom__selected-passage-list {
margin-bottom: 0.75em;
}

.loom__no-passages-selected {
color: var(--text-faint);
font-size: var(--font-ui-small);
padding: 0.35em 0 0.35em 0.6em;
}

/* hidden -- must be at end */

.hidden {
Expand Down
121 changes: 120 additions & 1 deletion views.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LoomSettings, NoteState } from "./common";
import { ItemView, Menu, WorkspaceLeaf, setIcon } from "obsidian";
import { App, ItemView, Menu, Modal, Setting, WorkspaceLeaf, setIcon } from "obsidian";
import { Range } from "@codemirror/state";
import {
Decoration,
Expand Down Expand Up @@ -636,3 +636,122 @@ export const loomEditorPluginSpec: PluginSpec<LoomEditorPlugin> = {
},
},
};

export class MakePromptFromPassagesModal extends Modal {
getSettings: () => LoomSettings;

constructor(app: App, getSettings: () => LoomSettings) {
super(app);
this.getSettings = getSettings;
}

onOpen() {
this.contentEl.createDiv({
cls: "modal-title",
text: "Make prompt from passages",
});

const pathPrefix = this.getSettings().passageFolder.trim().replace(/\/?$/, "/");
const passages = this.app.vault.getFiles().filter((file) =>
file.path.startsWith(pathPrefix) && file.extension === "md"
).sort((a, b) => b.stat.mtime - a.stat.mtime);

let selectedPassages: string[] = [];

const unselectedContainer = this.contentEl.createDiv({
cls: "loom__passage-list",
});
this.contentEl.createDiv({
cls: "loom__selected-passages-title",
text: "Selected passages",
});
const selectedContainer = this.contentEl.createDiv({
cls: "loom__passage-list loom__selected-passage-list",
});
let button: HTMLElement;

const cleanName = (name: string) => name.slice(pathPrefix.length, -3);

const renderPassageList = () => {
unselectedContainer.empty();
selectedContainer.empty();

const unselectedPassages = passages.filter(
(passage) => !selectedPassages.includes(passage.path)
);

for (const passage of unselectedPassages) {
const passageContainer = unselectedContainer.createDiv({
cls: "tree-item-self loom__passage"
});
passageContainer.createSpan({
cls: "tree-item-inner",
text: cleanName(passage.path),
});
passageContainer.addEventListener("click", () => {
selectedPassages.push(passage.path)
renderPassageList();
});
}

if (selectedPassages.length === 0) {
selectedContainer.createDiv({
cls: "loom__no-passages-selected",
text: "No passages selected.",
});
}
for (const passage of selectedPassages) {
const passageContainer = selectedContainer.createDiv({
cls: "tree-item-self loom__passage",
});
passageContainer.createSpan({
cls: "tree-item-inner",
text: cleanName(passage),
});
passageContainer.addEventListener("click", () => {
selectedPassages = selectedPassages.filter((p) => p !== passage);
renderPassageList();
});
}
};

let separator = this.getSettings().defaultPassageSeparator;
let passageFrontmatter = this.getSettings().defaultPassageFrontmatter;

new Setting(this.contentEl)
.setName("Separator")
.setDesc("Use \\n to denote a newline.")
.addText((text) =>
text.setValue(separator).onChange((value) => (separator = value)));
new Setting(this.contentEl)
.setName("Passage frontmatter")
.setDesc("This will be added before each passage and at the end. %n: 1, 2, 3..., %r: I, II, III...")
.addText((text) =>
text.setValue(passageFrontmatter).onChange((value) => (passageFrontmatter = value)));

const buttonContainer = this.contentEl.createDiv({
cls: "modal-button-container",
});
button = buttonContainer.createEl("button", {
cls: "mod-cta",
text: "Submit",
});
button.addEventListener("click", () => {
if (selectedPassages.length === 0) return;

this.app.workspace.trigger(
"loom:make-prompt-from-passages",
selectedPassages,
separator,
passageFrontmatter,
);
this.close();
});

renderPassageList();
}

onClose() {
this.contentEl.empty();
}
}

0 comments on commit f3aed39

Please sign in to comment.