Initial Commit

This commit is contained in:
Evan's Oasis 2024-06-19 13:24:19 -04:00 committed by GitHub
parent 08dbc40e81
commit b1c90ad1b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 14981 additions and 0 deletions

110
README.md Normal file
View file

@ -0,0 +1,110 @@
# Smart Connections Visualizer `v1.0`
![App Demo]
Welcome to the Smart Connections Visualizer Plugin! This plugin integrates seamlessly with the Smart Connections tool, offering an advanced, interactive way to visualize connections between your notes. My goal is to enhance your ability to discover relationships and insights within your notes, transforming the way you interact with and understand your information.
## Features
- **Dynamic Force-Directed Graph:** Visualize connections as a force-directed graph where nodes represent notes or specific excerpts from them and edges represent connections.
- **Easily view more relevant connections:** The distances between nodes and the central node (currently viewed note) are proportional to the relevance of the connections. The closer the distance to the main node, the more relevant the connection is. You can also adjust min and mx width of links to better distinguish these notes/blocks based on relevance.
- **Adjustable Visualization Settings:** Customize node size, label size, and text fade distance and more to tailor the visualization to your preferences.
- **Previewing Notes:** Ability to see note previews in the visualization view when hovering over a node.
- **Customizable Forces:** Adjust the repel force, link force, and center force for a more personalized layout.
## Installation
Getting started with the Smart Connections Visualizer Plugin is easy. Follow these steps to install and integrate it with your Smart Connections setup:
1. **Install & Configure Smart Connections Plugin:** Ensure you have the Smart Connections plugin installed and all set up in your Obsidian environment.
2. **Install Smart Connections Visualizer Plugin:** Download and install the Smart Connections Visualizer Plugin from the Obsidian Community Plugins.
## Usage
Once installed, the Smart Connections Visualizer Plugin provides an intuitive interface to explore your notes' connections.
### Opening the Visualizer
To open the Visualizer View:
1. Select the ribbon icon to the left that will display "Smart Connections Visualizer"
2. The visualization pane will appear.
3. Select a note and you'll see a dynamic force-directed graph of that note and its connections displayed.
### Interacting with the Visualization
- **Zooming:** Use the mouse wheel or touchpad to zoom in and out of the view.
- **Panning:** Click and drag the visualization to move it around.
- **Hovering:** Hover over nodes to highlight them - displaying the full node label and link label, which right now the link label displays the relevance score.
- **Previewing Notes:** When hovering over a node, press the `Ctrl` (`Command` for Mac) key to view a preview of the note or block that the node represents.
### Customizing the Visualization
Access the settings menu by clicking the gear icon in the top right corner of the visualization pane. Here, you can adjust various parameters:
- **Minimum Relevance**: Adjust the slider to change the minimum relevance score needed for displaying connections.
- **Connection Type**: Choose whether to display connections by block, or by note.
- **Node Size**: Change the size of the nodes to make them more visible or to declutter the visualization.
- **Maximum Label Characters**: Define the maximum number of characters displayed on node labels before they truncate/are shortened. Note: hovering over a node will display the full node label.
- **Minimum Link Thickness**: Set the minimum thickness for the links. Helps to distinguish between less relevant connections.
- **Maximum Link Thickness**: Set the maximum thickness for the links. Helps to distinguish between less relevant connections.
- **Link Label Size**: Change the font size of the link labels. Note: Link label will display when hovering over a node.
- **Node Label Size**: Adjust the font size of the node labels.
- **Text Fade Threshold**: Set the zoom level at which labels fade in and out.
- **Repel Force**: Adjust the force that pushes nodes apart.
- **Link Force**: Modify the strength of the links between nodes.
- **Link Distance**: Adjust the distance between connected nodes - relevance distance will increase/decrease proportionally.
## Vision
The text communication format between humans and AI (including other humans) is **incredibly** inefficient. I believe data visualization tools like this will not only exponentially enhance our ability to communicate with AI but also create a new discovery and decision-making process that will transform how we approach virtually everything. Our brains can't physically retain all the information and use logic to solve our more complex problems simultaneously. Having all the information in the form of text that's neatly scattered across different notes and folders helps, but is nowhere near sufficient. We need to offload this information into a visualization to save our progress and pick up where we left off.
Imagine solving a Sudoku puzzle without a board. That's what we're doing right now when communicating, arguing, and solving problems. You have to memorize all the factors in the starting position, all the moves you make, all the relationships that change with every single move, and so on.. For the complex problems/puzzles we have in life, imagine the difference in our ability to solve them WITH the board. The puzzle is there we just haven't made the board for it yet!
With this plugin, I envision a future where note-taking, whether text or voice, doesn't get lost. I struggle with maintaining a strict note-taking structure, and things often fall through the cracks. This challenge, combined with my obsession, drives me to create this plugin. While it's not perfect yet, it's a promising start and could improve significantly with your help :) I can hear the music.. and I'm excited to see who else can too!
The ultimate mission is to move away from text-based organization and instead visualize information in a dynamic, context-driven state. As you add more notes, the state updates in a single visualization, showing how new information impacts the old through various relationships (e.g., supportive vs. contradictory statements). This approach eliminates the need for separate folders, as everything is interconnected and builds on top of each other.
I believe this will also solve the "hallucination" problem with AI. By using a visualization as the saved state/knowledge base/memory, we ensure the AI always has the correct context that both you and the AI have worked on and, importantly, that **you** have approved.
The goal is to see everything you couldn't see before and observe how updating information cascades and affects all other connections. We're on the brink of exponentially increased problem-solving capabilities through visualizing our text-based information, and the possibilities are endless! I've been dedicating a lot of time to this project alongside my other jobs, so any support would be greatly appreciated to allow me more time to keep going :)
## About Me
Hello there! Name's Evan :D
I'm a senior software developer/architect consultant, currently managing and developing data visualization tools for the CDC. I've been obsessed about what's transpiring with AI for the last 12-ish years, fully aware that it would soon completely change the world.
Inspired by Brian, the creator of Smart Connections, I started an AI consulting company called "Evan's Oasis." At Evan's Oasis, I audit business workflows for clients and recommend AI tools and practices that significantly enhance decision-making, productivity, and quality—while reducing the time and money needed to run a business.
I've been actively sharing my knowledge by giving webinars to both General Dynamics and the CDC on the most significant AI updates, as well as hosting in-person seminars for the local community. One of my life's main questlines is to inspire people to take advantage of the outragoues new capabilities we have with AI and to show how these tools can assist us in our everyday lives.
TLDR: I was born for this.
In addition to my passion for AI, I'm also developing a device that allows people to control technology using motion capture of their tongue—effectively giving us a fifth limb for universal, hands-free remote control. And don't worry, you won't be moving your tongue around with your mouth open; you can keep your mouth completely closed, and it looks like mind control. We're hoping this device will do wonders for those with physical limitations who can't use technology as effectively. We are currently in the funding phase but have developed several functional prototypes. You can find out more at [Glosdex.com](https://glosdex.com/)
## Community and Support
Your involvement is crucial to the evolution of Smart Connections Visualizer. From troubleshooting issues to suggesting new features, every contribution enriches our community and drives the project forward!
### Join The Community
- **GitHub Discussions**: Participate in discussions on GitHub to share your experiences and ask questions.
- **Contribute**: Help develop the plugin, report issues, or suggest new features.
- **User Testimonials**: Share how the plugin has impacted your workflow and creativity.
## Acknowledgements
Special thanks to Brian, the developer behind [Smart Connections](https://github.com/brianpetro/obsidian-smart-connections).
## License
The Smart Connections Visualization Plugin is open-source and available under the MIT License. Contributions are welcome!
---
Feel free to reach out with any questions or suggestions. I hope this plugin enhances your note-taking experience and helps you uncover new insights and connections within your notes. Happy visualizing!

76
esbuild.config.mjs Normal file
View file

@ -0,0 +1,76 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import dotenv from "dotenv";
dotenv.config();
import fs from "fs";
import path from "path";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === "production");
const copy_to_plugins = {
name: 'copy_to_plugins',
setup(build) {
build.onEnd(() => {
const plugin_path = path.join(process.env.OBSIDIAN_PLUGINS_PATH, "smart-transcriptions");
if (!fs.existsSync(plugin_path)) {
fs.mkdirSync(plugin_path);
}
fs.copyFileSync("./main.js", path.join(plugin_path, "main.js"));
fs.copyFileSync("./manifest.json", path.join(plugin_path, "manifest.json"));
fs.copyFileSync("./styles.css", path.join(plugin_path, "styles.css"));
// add empty .hotreload file
fs.writeFileSync(path.join(plugin_path, ".hotreload"), "");
console.log("Plugin built and copied to obsidian plugins folder");
});
}
};
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins
],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: "inline",
treeShaking: true,
outfile: "main.js",
plugins: [
copy_to_plugins
]
});
if(!prod) await context.watch();
else {
await context.rebuild();
process.exit(0);
}

11359
main.css Normal file

File diff suppressed because one or more lines are too long

390
main.js Normal file

File diff suppressed because one or more lines are too long

418
main.ts Normal file
View file

@ -0,0 +1,418 @@
import { App, Editor, MarkdownView, normalizePath, Notice, Plugin, PluginSettingTab, requestUrl, RequestUrlParam, Setting, TAbstractFile } from 'obsidian';
interface AudioPluginSettings {
model: string;
apiKey: string;
prompt: string;
}
let DEFAULT_SETTINGS: AudioPluginSettings = {
model: 'gpt-4',
apiKey: 'sk-Test',
prompt: 'You are an expert note-making AI for obsidian who specializes in the Linking Your Thinking (LYK) strategy. The following is a transcription of recording of someone talking aloud or people in a conversation. There may be a lot of random things said given fluidity of conversation or thought process and the microphone\'s ability to pick up all audio. Give me detailed notes in markdown language on what was said in the most easy-to-understand, detailed, and conceptual format. Include any helpful information that can conceptualize the notes further or enhance the ideas, and then summarize what was said. Do not mention \"the speaker\" anywhere in your response. The notes your write should be written as if I were writting them. Finally, ensure to end with code for a mermaid chart that shows an enlightening concept map combining both the transcription and the information you added to it. The following is the transcribed audio:\n\n'
}
interface TokenLimits {
[key: string]: number;
}
const TOKEN_LIMITS: TokenLimits = {
'gpt-3.5-turbo': 4096,
'gpt-3.5-turbo-16k': 16000,
'gpt-3.5-turbo-0301':4096,
'text-davinci-003': 4097,
'text-davinci-002': 4097,
'code-davinci-002': 8001,
'code-davinci-001': 8001,
'gpt-4': 8192,
'gpt-4-0314': 8192,
'gpt-4-32k': 32768,
'gpt-4-32k-0314': 32768
}
const MODELS: string[] = [
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-0613',
'text-davinci-003',
'text-davinci-002',
'code-davinci-002',
'code-davinci-001',
'gpt-4-0613',
'gpt-4-32k-0613',
'gpt-4o'
];
export default class SmartTranscriptionsPlugin extends Plugin {
settings: AudioPluginSettings;
writing: boolean;
transcript: string;
apiKey: string = 'sk-Test';
model: string = 'gpt-3.5-turbo-16k';
async onload() {
await this.loadSettings();
this.addCommand({
id: 'open-transcript-modal',
name: 'Smart transcribe',
editorCallback: (editor: Editor, view: MarkdownView) => {
this.commandGenerateTranscript(editor);
}
});
this.addSettingTab(new SampleSettingTab(this.app, this));
}
writeText(editor: Editor, LnToWrite: number, text: string) {
const newLine = this.getNextNewLine(editor, LnToWrite);
editor.setLine(newLine, '\n' + text.trim() + '\n');
return newLine;
}
getNextNewLine(editor: Editor, Ln: number) {
let newLine = Ln;
while (editor.getLine(newLine).trim().length > 0) {
if (newLine == editor.lastLine()) editor.setLine(newLine, editor.getLine(newLine) + '\n');
newLine++;
}
return newLine;
}
commandGenerateTranscript(editor: Editor) {
const position = editor.getCursor();
const text = editor.getRange({ line: 0, ch: 0 }, position);
const regex = [/(?<=\[\[)(([^[\]])+)\.(mp3|mp4|mpeg|mpga|m4a|wav|webm)(?=]])/g,
/(?<=\[(.*)]\()(([^[\]])+)\.(mp3|mp4|mpeg|mpga|m4a|wav|webm)(?=\))/g];
this.findFilePath(text, regex).then((path) => {
const fileType = path.split('.').pop();
if (fileType == undefined || fileType == null || fileType == '') {
new Notice('No audio file found');
} else {
this.app.vault.adapter.exists(path).then((exists) => {
if (!exists) throw new Error(path + ' does not exist');
this.app.vault.adapter.readBinary(path).then((audioBuffer) => {
if (this.writing) {
new Notice('Generator is already in progress.');
return;
}
this.writing = true;
new Notice("Generating transcript...");
this.generateTranscript(audioBuffer, fileType).then((result) => {
// const selectedText = editor.getSelection();
this.transcript = result;
const prompt = this.settings.prompt + result;
new Notice('Transcript Generated... Reformatting');
this.generateText(prompt, editor , editor.getCursor('to').line);
}).catch(error => {
console.warn(error.message);
new Notice(error.message);
this.writing = false;
});
});
});
}
}).catch(error => {
console.warn(error.message);
new Notice(error.message);
});
}
commandGenerateText(editor: Editor, prompt: string) {
const currentLn = editor.getCursor('to').line;
if (this.writing) {
new Notice('Generator is already in progress.');
return;
}
this.writing = true;
new Notice("Generating text...");
this.generateText(prompt, editor, currentLn).then((text) => {
new Notice("Text completed.");
this.writing = false;
}).catch(error => {
console.warn(error.message);
new Notice(error.message);
this.writing = false;
});
}
async generateTranscript(audioBuffer: ArrayBuffer, filetype: string) {
if (this.settings.apiKey.length <= 1) throw new Error('OpenAI API Key is not provided.');
// Reference: www.stackoverflow.com/questions/74276173/how-to-send-multipart-form-data-payload-with-typescript-obsidian-library
const N = 16 // The length of our random boundry string
const randomBoundryString = 'WebKitFormBoundary' + Array(N + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, N)
const pre_string = `------${randomBoundryString}\r\nContent-Disposition: form-data; name="file"; filename="audio.mp3"\r\nContent-Type: "application/octet-stream"\r\n\r\n`;
const post_string = `\r\n------${randomBoundryString}\r\nContent-Disposition: form-data; name="model"\r\n\r\nwhisper-1\r\n------${randomBoundryString}--\r\n`
const pre_string_encoded = new TextEncoder().encode(pre_string);
const post_string_encoded = new TextEncoder().encode(post_string);
const concatenated = await new Blob([pre_string_encoded, audioBuffer, post_string_encoded]).arrayBuffer()
const options: RequestUrlParam = {
url: 'https://api.openai.com/v1/audio/transcriptions',
method: 'POST',
contentType: `multipart/form-data; boundary=----${randomBoundryString}`,
headers: {
'Authorization': 'Bearer ' + this.settings.apiKey
},
body: concatenated
};
const response = await requestUrl(options).catch((error) => {
if (error.message.includes('401')) throw new Error('OpenAI API Key is not valid.');
else throw error;
});
if ('text' in response.json) return response.json.text;
else throw new Error('Error. ' + JSON.stringify(response.json));
}
async getAttachmentDir() {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) throw new Error('No active file');
const dir = this.app.vault.adapter.getResourcePath(activeFile.path).replace(activeFile.path, '');
return dir;
}
async findFilePath(text: string, regex: RegExp[]) {
const fullPath = await this.getAttachmentDir().then((attachmentPath) => {
let filename = '';
let result: RegExpExecArray | null;
for (const reg of regex) {
while ((result = reg.exec(text)) !== null) {
filename = normalizePath(decodeURI(result[0])).trim();
}
}
if (filename == '') throw new Error('No file found in the text.');
const fileInSpecificFolder = filename.contains('/');
const AttInRootFolder = attachmentPath === '' || attachmentPath === '/';
const AttInCurrentFolder = attachmentPath.startsWith('./');
const AttInSpecificFolder = !AttInRootFolder && !AttInCurrentFolder;
let fullPath = '';
if (AttInRootFolder || fileInSpecificFolder) fullPath = filename;
else {
if (AttInSpecificFolder) fullPath = attachmentPath + '/' + filename;
if (AttInCurrentFolder) {
const attFolder = attachmentPath.substring(2);
if (attFolder.length == 0) fullPath = this.getCurrentPath() + '/' + filename;
else fullPath = this.getCurrentPath() + '/' + attFolder + '/' + filename;
}
}
const exists = this.app.vault.getAbstractFileByPath(fullPath) instanceof TAbstractFile;
if (exists) return fullPath;
else {
let path = '';
let found = false;
this.app.vault.getFiles().forEach((file) => {
if (file.name === filename) {
path = file.path;
found = true;
}
});
if (found) return path;
else throw new Error('File not found');
}
});
return fullPath as string;
}
getCurrentPath() {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) throw new Error('No active file');
const currentPath = activeFile.path.split('/');
currentPath.pop();
const currentPathString = currentPath.join('/');
return currentPathString;
}
async generateText(prompt: string, editor: Editor, currentLn: number, contextPrompt?: string) {
if (prompt.length < 1) throw new Error('Cannot find prompt.');
if ( this.settings.apiKey.length <= 1) throw new Error('OpenAI API Key is not provided.');
if (prompt.length > TOKEN_LIMITS[this.settings.model]) {
new Notice(`shortening prompt`);
prompt = prompt.substring(prompt.length - (TOKEN_LIMITS[this.settings.model] + 300));
}
// console.log('prompt: ', prompt);
prompt = prompt + '.';
let newPrompt = prompt;
const messages = [];
// messages.push({
// role: 'system',
// content: contextPrompt,
// });
messages.push({
role: 'user',
content: newPrompt,
});
const body = JSON.stringify({
model: this.settings.model,
messages: messages,
stream: true
});
// console.log('messages: ', messages);
new Notice(`Starting reformat`);
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
body: body,
headers: {
'Accept': 'text/event-stream',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.settings.apiKey,
},
});
if (!response.ok) {
const errorResponse = await response.json();
const errorMessage = errorResponse && errorResponse.error.message ? errorResponse.error.message : response.statusText;
new Notice(`Error. ${errorMessage}`);
throw new Error(`Error. ${errorMessage}`);
} else {
new Notice(`Should work`);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error('No response body reader available');
}
let LnToWrite = this.getNextNewLine(editor, currentLn);
editor.setLine(LnToWrite++, '\n');
let end = false;
let buffer = '';
while (!end) {
const { done, value } = await reader.read();
end = done;
const chunk = new TextDecoder().decode(value);
const data = chunk.split('\n');
for (const datum of data) {
if (datum.trim() === 'data: [DONE]') {
end = true;
break;
}
if (datum.startsWith('data:')) {
const json = JSON.parse(datum.substring(6));
if ('error' in json) throw new Error('Error: ' + json.error.message);
if (!('choices' in json)) throw new Error('Error: ' + JSON.stringify(json));
if ('content' in json.choices[0].delta) {
const text = json.choices[0].delta.content;
if (buffer.length < 1) buffer += text.trim();
if (buffer.length > 0) {
const lines = text.split('\n');
if (lines.length > 1) {
for (const word of lines) {
editor.setLine(LnToWrite, editor.getLine(LnToWrite++) + word + '\n');
}
} else {
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + text);
}
}
}
}
}
}
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + '\n');
// Add the raw transcript at the end
if (this.transcript) {
editor.setLine(LnToWrite++, '# Transcript');
editor.setLine(LnToWrite++, this.transcript);
}
this.writing = false;
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleSettingTab extends PluginSettingTab {
plugin: SmartTranscriptionsPlugin;
constructor(app: App, plugin: SmartTranscriptionsPlugin) {
super(app, plugin);
this.plugin = plugin;
this.plugin.settings.prompt = 'You are an expert note-making AI for obsidian who specializes in the Linking Your Thinking (LYK) strategy. The following is a transcription of recording of someone talking aloud or people in a conversation. There may be a lot of random things said given fluidity of conversation or thought process and the microphone\'s ability to pick up all audio. Give me detailed notes on what was said in the most easy-to-understand, detailed, and conceptual format. Include any helpful information that can conceptualize the notes further or enhance the ideas, and then summarize what was said. Do not mention the speaker, the notes your write should be written as if they were me writting them. Finally, ensure the type format is readme and end with code for a mermaid chart that shows an enlightening concept map combining both the transcription and the information you added to it. The following is the transcribed audio:\n\n';
this.plugin.settings.model = 'gpt-3.5-turbo-16k';
}
display(): void {
let {containerEl} = this;
containerEl.empty();
containerEl.createEl('h2', {text: 'Smart Transcription Settings'});
new Setting(containerEl)
.setName('OpenAI API Key')
.setDesc('Ex: sk-Test')
.addText(text => text
.setPlaceholder('YOUR API KEY')
.setValue(this.plugin.settings.apiKey)
.onChange(async (value) => {
// console.log('API Key: ' + value);
this.plugin.settings.apiKey = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Model')
.setDesc('Select the model to use for note-generation')
.addDropdown(dropdown => {
dropdown.addOptions(MODELS.reduce((models: {[key: string]: string}, model) => {
models[model] = model;
return models;
}, {}));
dropdown.setValue(this.plugin.settings.model);
dropdown.onChange(async (value) => {
// console.log('Model: ' + value);
this.plugin.settings.model = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Custom Transcription-To-Notes Prompt')
.setDesc('Prompt that will be sent to Chatpgt right before adding your transcribed audio')
.addTextArea(text => {
if (text.inputEl) {
text.inputEl.classList.add('text-box');
}
text.setPlaceholder(
'Act as my personal secretary and worlds greatest entreprenuer and know I will put these notes in my personal obsidian where I have all my notes linked by categories, tags, etc. The following is a transcription of recording of someone talking aloud or people in a conversation. May be a lot of random things that are said given fluidity of conversation and the microphone ability to pick up all audio. Make outline of all topics and points within a structured hierarchy. Make sure to include any quantifiable information said such as the cost of headphones being $400. Then go into to detail with summaries that explain things more eloquently. Finally, Create a mermaid chart code that complements the outline.\n\n')
.setValue(this.plugin.settings.prompt)
.onChange(async (value) => {
this.plugin.settings.prompt = value;
await this.plugin.saveSettings();
})});
}
}

12
manifest.json Normal file
View file

@ -0,0 +1,12 @@
{
"id": "smart-transcriptions",
"name": "Smart Transcriptions",
"version": "1.0.2",
"minAppVersion": "0.15.0",
"description": "Create personalized and intelligent analysis, summaries, and more for audio recordings that can be imported or spoken directly into a note",
"author": "Evan Moscoso",
"authorUrl": "https://github.com/mossy1022",
"fundingUrl": "https://buymeacoffee.com/evansoasis",
"isDesktopOnly": true
}

2517
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

40
package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "smart-transcriptions",
"version": "1.0.2",
"description": "Create personalized and intelligent analysis, summaries, and more for audio recordings that can be imported or spoken directly into a note",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [
"Obsidian",
"Smart Connections",
"Visualizer",
"Smart Connections Visualizer",
"Smart Transcriptions",
"Transcription",
"Smart",
"Transcribe",
"Graph",
"View"
],
"author": "Evan Moscoso",
"license": "MIT",
"devDependencies": {
"@types/d3": "^7.4.3",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"dotenv": "^16.4.5",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
"dependencies": {
"bootstrap": "^5.3.3"
}
}

18
styles.css Normal file
View file

@ -0,0 +1,18 @@
/*
This CSS file will be included with your plugin, and
available in the app when your plugin is enabled.
If your plugin does not need CSS, delete this file.
*/
.mermaid svg {
width: 100%;
height: auto;
}
.text-box {
width: 350px;
height: 400px;
}

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

14
version-bump.mjs Normal file
View file

@ -0,0 +1,14 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}