Skip to content

Commit

Permalink
Merge pull request #134 from cloud-atlas-ai/interactive_ui_poc
Browse files Browse the repository at this point in the history
New interactive panel mode
  • Loading branch information
muness authored Jun 11, 2024
2 parents 1b53372 + 6831146 commit facb744
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 4 deletions.
82 changes: 78 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,51 @@ Each flow is like having a personal assistant within Obsidian, helping you manag

## Cloud Atlas Plugin Usage Guide

We believe that good context is key to leveraging LLMs effectively. We offer multiple modes for using Cloud Atlas in Obsidian:

- **Canvas Flow Mode**: Use the interactive canvas to set up and run flows with components like input, context, and user prompts.
- **Markdown Notes Mode**: Run flows directly on Markdown files with support for context selection or using the current file as context.
- **Interactive Panel Mode**: Send a prompt along with selected context.

This will walk you through creating, running, and customizing your personal flows. Let's get started.

## Canvas Flow Mode

The Canvas Flow mode in the Cloud Atlas plugin enhances your Obsidian experience by enabling a visual representation of your workflows. By creating a Canvas in Obsidian, you can structurally map out your notes, ideas, and tasks, and leverage Cloud Atlas's AI capabilities to interact with them more intuitively.

### Features

- **Visual Workflow Mapping**: Easily map out your workflow in a visual canvas and define how each part of your notes interacts with each other.
- **AI-Powered Processing**: Cloud Atlas AI analyzes your canvas flow, providing insights, summaries, or any other AI-powered feature directly within your canvas.
- **Seamless Integration**: The Canvas Flow mode works hand-in-hand with other Cloud Atlas features, ensuring a smooth and efficient workflow within Obsidian.

### How to Use Canvas Flow Mode

1. Create a new `.flow` canvas.
2. Add nodes to your canvas, which can be of different types such as Card, Note from Vault, and Media from Vault.
3. Assign colors to your nodes to indicate their types:
- Red: Input
- Orange: User Prompt
- Blue: System
- Green: Context
4. Use the `Run Canvas Flow` command to execute the flow, which will pass data between nodes and utilize the Cloud Atlas AI to process and generate content.

### Example Usage

You can create a Canvas Flow to summarize a meeting by:

1. Adding a Note to your canvas with the meeting transcript. Color it Red to indicate it as an input.
2. Adding a Card colored Blue for the System Instructions, telling the LLM to act as a summarizer.
3. Adding a Card colored Orange for the User Prompt, asking the LLM to summarize the meeting.
4. Running the Canvas Flow to get a summarized version of the meeting right within your Obsidian canvas.

We often use Canvas Flows as a Pair Programmer or Assistant while preparing for a presentation based on notes.

## Markdown Notes Mode

### Step 1: Creating a New Flow

1. **Create a New Flow**: Use the command palette (`Ctrl/Cmd + P`) and type `Create New Flow`. This will create a new `.flow.md` file in the `CloudAtlas` directory.
1. **Create a New Flow**: Use the command palette (`Ctrl/Cmd + P`) and type `Create New Flow`. This will create a new `.flow` file in the `CloudAtlas` directory.

2. **Name Your Flow**: Follow the naming convention `CloudAtlas/<flow name>.flow.md` for markdown notes mode.

Expand All @@ -83,14 +123,13 @@ This will walk you through creating, running, and customizing your personal flo

4. **Running Flows**:


### Step 2: Running Your Flow

1. **Open Any Note**: With your flow created, open any note in your vault where you want to run the flow.
1. **Open Any Note**: With your flow created, open any note in your vault where you want to run the flow. The note, flowdata (see below), backlinks, and forward links will be used as context for the flow.
2. **Execute the Flow**:
1. **From the Command Palette:** After creating or updating a flow, you can register it as a command. Go to the Settings and register it as a command. This will allow you to run the flow from the command palette.
2. **From the Cloud Atlas view:** You can also run flows from the Cloud Atlas view by clicking on the Play button next to the flow name.
3. **View the Results**: Cloud Atlas processes the note and appends or replaces the content based on your flow settings. The response from Cloud Atlas will appear in your note.
3. **View the Results**: Cloud Atlas processes the flow and adds the response at the cursor or replaces the selected content.

### Step 3: Customizing Your Flow

Expand All @@ -114,6 +153,41 @@ This will walk you through creating, running, and customizing your personal flo

That's it! You've now learned how to create, run, and customize your own flows in Obsidian using the Cloud Atlas plugin. Go wild!

### Example Usage

We often use Markdown Notes Mode to prepare for a day:

1. Create a flow named `Morning Routine` that includes a list of tasks to complete.
2. Customize the flow with a `Morning Routine.flowdata` file that includes additional context about current goals and strengths.
3. Run the flow on your daily notes (which has your calendar and tasks) to get a pep talk.

We also use it to:

- review a day after it's done, summarizing the day's events and achievements.
- summarize progress at the end of a week.

### Alpha Features

There are some alpha features we are working on, the most exciting of which is to develop a flow and turn it into a Chrome Extension. This will allow you to run flows on any webpage you visit, making it easier to get information and context from the web. Let us know if you'd like to be a part of the alpha testing.

## Interactive Panel Mode

The Interactive Panel mode is a dedicated panel within Obsidian that allows you to interact with Cloud Atlas in real-time. It enables you to send prompts to Cloud Atlas and receive responses directly within Obsidian.

### How to Use

1. Open the Interactive Panel by clicking on the Cloud Atlas panel icon in the Obsidian ribbon. This will display the Cloud Atlas Interactive Mode panel.
2. Enter your prompt in the provided textbox. This could be any question or command you wish to send to Cloud Atlas.
3. Optionally, you can attach notes from your vault to provide additional context to Cloud Atlas. Click the '+' button to attach the currently active note.
4. Once you're ready, click the 'Send to LLM' button to submit your prompt to Cloud Atlas.
5. The panel will display a loading indicator while waiting for a response.
6. Once Cloud Atlas processes your prompt, the response will be displayed in the panel.
7. You can copy the response to your clipboard using the 'Copy to Clipboard' button, which appears after a response is received.

### Example Usage

We often use the Interactive Panel Mode to have a Q&A about the contents of a specific note.

## Manually installing the plugin

### With [Obsidian BRAT](https://github.com/TfTHacker/obsidian42-brat)
Expand Down
242 changes: 242 additions & 0 deletions src/interactive_panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { ItemView, Notice, WorkspaceLeaf, setIcon } from "obsidian";
import CloudAtlasPlugin from "./main";

import { Payload } from "./interfaces";
import ShortUniqueId from "short-unique-id";

export const INTERACTIVE_PANEL_TYPE = "interactive-panel";

let noticeTimeout: NodeJS.Timeout;

const animateNotice = (notice: Notice) => {
let message = notice.noticeEl.innerText;
const dots = [...message].filter((c) => c === ".").length;
if (dots == 0) {
message = message.replace(" ", " . ");
} else if (dots == 1) {
message = message.replace(" . ", " .. ");
} else if (dots == 2) {
message = message.replace(" .. ", " ...");
} else if (dots == 3) {
message = message.replace(" ...", " ");
}
notice.setMessage(message);
noticeTimeout = setTimeout(() => animateNotice(notice), 500);
};

export class InteractivePanel extends ItemView {
plugin: CloudAtlasPlugin;
attachedFiles: Set<string>;
settings: import("./settings").CloudAtlasPluginSettings;
responseContainer: HTMLDivElement;
responsePre: HTMLPreElement;
responseCode: HTMLElement;
loadingIndicator: HTMLDivElement;
copyButton: HTMLButtonElement;

constructor(leaf: WorkspaceLeaf, plugin: CloudAtlasPlugin) {
super(leaf);
this.plugin = plugin;
this.settings = this.plugin.settings;
this.attachedFiles = new Set(); // Initialize the Set
}

setLoading(loading: boolean) {
if (loading) {
this.loadingIndicator.style.display = "block";
} else {
this.loadingIndicator.style.display = "none";
}
}

getViewType() {
return INTERACTIVE_PANEL_TYPE;
}

getIcon(): string {
return "cloud-cog";
}

getDisplayText() {
return "Cloud Atlas Interactive Mode";
}

createUserPromptTextbox() {
// Implement the logic for creating the user prompt textbox here.
const promptTextbox = document.createElement('textarea');
promptTextbox.placeholder = 'Enter your prompt here...';
promptTextbox.classList.add('ca-interactive-prompt-textbox');
this.containerEl.appendChild(promptTextbox);
return promptTextbox;
}

createAttachedFilesList() {
const attachedFilesList = this.containerEl.createDiv({ cls: 'attached-notes-list' });
const listHeader = attachedFilesList.createEl('h4', { text: 'Attached Notes' });
listHeader.style.marginBottom = '1em';
const filesList = attachedFilesList.createEl('ul');
filesList.style.height = '200px'; // Set a fixed height
filesList.style.overflowY = 'auto'; // Make it scrollable
return filesList;
}

// Function to update the attached files list
updateAttachedFilesList(filesList: HTMLElement) {
filesList.empty();
if (this.attachedFiles.size === 0) {
filesList.createEl('li', { text: 'No notes attached.' });
} else {
this.attachedFiles.forEach(file => {
const listItem = filesList.createEl('li', { text: file });
const removeButton = listItem.createEl('button', {
text: 'Remove',
cls: 'remove-button',
});
removeButton.onClickEvent(() => {
this.attachedFiles.delete(file);
this.updateAttachedFilesList(filesList);
});
});
}
}

// Updated onOpen method
async onOpen() {
const container = this.containerEl.children[1];
container.empty();
container.createEl('h4', { text: 'Cloud Atlas Interactive Mode' });

// User prompt text box
const prompt = this.createUserPromptTextbox();
container.appendChild(prompt);

// Attach button
const attachButton = container.createEl('button', {
cls: 'ca-attach-note-button',
text: '+',
});

container.createDiv();

// Send button
const sendButton = container.createEl('button', {
cls: 'send-button',
text: 'Send to LLM',
});

// Attached files list
const attachedFilesList = this.createAttachedFilesList();
this.updateAttachedFilesList(attachedFilesList);

// Copy to Clipboard button and Response header positioning
const responseHeader = container.createEl('h4', { text: 'Response' });
this.copyButton = container.createEl('button', { text: 'Copy to Clipboard', cls: 'ca-a-interactive-copy-button' });
this.copyButton.hide();
responseHeader.insertAdjacentElement('afterend', this.copyButton);

// Listener for the Copy to Clipboard button
this.copyButton.onClickEvent(() => {
if (this.responseCode) {
navigator.clipboard.writeText(this.responseCode.innerText).then(() => {
new Notice('Response copied to clipboard.');
}, (err) => {
console.error('Could not copy text: ', err);
new Notice('Failed to copy response to clipboard.');
});
}
});

// Response container
this.responseContainer = container.createDiv({ cls: 'response-container' });
this.responsePre = this.responseContainer.createEl('pre', { cls: 'ca-scrollable-response' });
this.responseCode = this.responsePre.createEl('code');
this.responseCode.addClass('ca-response-code');

// Loading indicator
this.loadingIndicator = container.createDiv({ cls: 'loading-indicator' });
setIcon(this.loadingIndicator, 'sync');
this.loadingIndicator.setText(' Waiting for response');
this.setLoading(false); // Initially hidden

// Event listeners
sendButton.onClickEvent(async () => {
if (prompt.value.trim() === "") {
new Notice("Please enter a prompt before sending.");
return;
}
await this.handleSendClick(prompt.value);
});

attachButton.onClickEvent(() => {
const activeFile = this.app.workspace.getActiveFile();
if (activeFile) {
this.attachedFiles.add(activeFile.path);
new Notice(`Attached file: ${activeFile.path}`);
this.updateAttachedFilesList(attachedFilesList);
} else {
new Notice("No file is currently open.");
}
});
}

private async handleSendClick(promptValue: string) {
// Build the payload using the promptValue and attached files
const payload: Payload = {
user: {
user_prompt: promptValue,
input: "See Additional Context and Respond to Prompt",
additional_context: {},
},
system: null,
options: {
generate_embeddings: this.settings.generateEmbeddings,
entity_recognition: this.settings.entityRecognition,
wikify: this.settings.wikify,
},
provider: this.settings.useOpenAi ? "openai" : "azureai",
llmOptions: {
temperature: this.settings.llmOptions.temperature,
max_tokens: this.settings.llmOptions.max_tokens,
},
requestId: new ShortUniqueId({ length: 10 }).rnd(),
};

// Add contents from attached files to the payload's additional context
if (!payload.user.additional_context) {
payload.user.additional_context = {};
}
for (const filePath of this.attachedFiles) {
const fileContent = await this.plugin.readAndFilterContent(filePath, []);
if (fileContent !== null) {
payload.user.additional_context[filePath] = fileContent;
}
}

this.setLoading(true);
const notice = new Notice(`Running Interactive flow ...`, 0);
animateNotice(notice);

try {
const responseText = await this.plugin.caApiFetch(payload);
this.setLoading(false);
notice.hide();
clearTimeout(noticeTimeout);
this.renderResponse(responseText);
this.copyButton.show();
} catch (error) {
this.setLoading(false);
notice.hide();
console.error("Failed to fetch response from Cloud Atlas:", error);
new Notice("Failed to send payload to Cloud Atlas. Check the console for more details.");
}
}

private renderResponse(responseText: string) {
this.responseCode.setText(responseText);
}


async onClose() {
// Nothing to clean up.
}
}
Loading

0 comments on commit facb744

Please sign in to comment.