Skip to content

Commit

Permalink
feat: enable prompt template on all urls (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
rpidanny authored May 11, 2023
1 parent d45390c commit 709da43
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 67 deletions.
12 changes: 12 additions & 0 deletions apps/chrome-extension/src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
"js": ["bardContentScript.bundle.js"],
"css": ["bard.content.styles.css"],
"run_at": "document_end"
},
{
"matches": ["<all_urls>"],
"exclude_matches": [
"https://chat.openai.com/*",
"https://bard.google.com/*"
],
"js": ["defaultContentScript.bundle.js"]
}
],
"web_accessible_resources": [
Expand All @@ -36,6 +44,10 @@
{
"resources": ["chatgpt.content.styles.css"],
"matches": ["https://chat.openai.com/*"]
},
{
"resources": ["bard.content.styles.css"],
"matches": ["https://bard.google.com/*"]
}
],
"permissions": ["storage"]
Expand Down
85 changes: 31 additions & 54 deletions apps/chrome-extension/src/pages/Content/base.dom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,95 +7,72 @@ import PromptsView from './PromptsView';

export abstract class BaseDom {
protected abstract name: string;
protected abstract textAreaSelector: string;

templatesView!: HTMLDivElement;
isTemplatesViewOpen = false;
promptsView!: HTMLDivElement;
isPromptsViewOpen = false;

constructor() {
this.init = this.init.bind(this);
this.handleTemplateSelected = this.handleTemplateSelected.bind(this);
this.hideTemplates = this.hideTemplates.bind(this);
this.handlePromptSelected = this.handlePromptSelected.bind(this);
this.hidePrompts = this.hidePrompts.bind(this);
}

protected abstract setText(text: string): void;
protected abstract addCustomTrigger(): void;
protected abstract usePrompt(prompt: IPrompt): void;

init() {
console.log(`Initializing ${this.name} DOM`);
this.templatesView = this.createTemplatesElement();
this.addEventListeners();
this.promptsView = this.createPromptsElement();
this.addTriggers();
}

protected getTextArea(): HTMLTextAreaElement {
const textArea = document.querySelector<HTMLTextAreaElement>(
this.textAreaSelector
);

if (!textArea) throw new Error('Could not find text area');

return textArea;
}

private handleTemplateSelected(template: IPrompt): void {
this.isTemplatesViewOpen = false;
this.setText(template.content);
this.hideTemplates();
private handlePromptSelected(prompt: IPrompt): void {
this.isPromptsViewOpen = false;
this.usePrompt(prompt);
this.hidePrompts();
}

private createTemplatesElement(): HTMLDivElement {
const templatesView = document.createElement('div');
document.body.appendChild(templatesView);
return templatesView;
private createPromptsElement(): HTMLDivElement {
const promptsView = document.createElement('div');
document.body.appendChild(promptsView);
return promptsView;
}

private async render() {
ReactDOM.render(
<PromptsView
groupedPrompts={groupedPrompts}
visible={this.isTemplatesViewOpen}
onItemSelected={this.handleTemplateSelected}
onCancel={this.hideTemplates}
visible={this.isPromptsViewOpen}
onItemSelected={this.handlePromptSelected}
onCancel={this.hidePrompts}
/>,
this.templatesView
this.promptsView
);
}

private async showTemplates() {
this.isTemplatesViewOpen = true;
protected async showPrompts() {
this.isPromptsViewOpen = true;
await this.render();
this.templatesView.focus();
this.promptsView.focus();
}

private async hideTemplates() {
this.isTemplatesViewOpen = false;
protected async hidePrompts() {
this.isPromptsViewOpen = false;
await this.render();
}

private addHotKeysEventListener() {
private addHotKeysTrigger() {
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' || event.key === 'q') {
this.hideTemplates();
this.hidePrompts();
} else if (event.ctrlKey && event.key === 't') {
this.showTemplates();
}
});
}

private addTextAreaEventListener() {
this.getTextArea().addEventListener('input', (event) => {
const input = event.target as HTMLTextAreaElement;
const text = input.value;

if (text === '/templates') {
this.showTemplates();
} else if (this.isTemplatesViewOpen) {
this.hideTemplates();
this.showPrompts();
}
});
}

private addEventListeners() {
this.addHotKeysEventListener();
this.addTextAreaEventListener();
private addTriggers() {
this.addHotKeysTrigger();
this.addCustomTrigger();
}
}
28 changes: 28 additions & 0 deletions apps/chrome-extension/src/pages/Content/generic.dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IPrompt } from '@rpidanny/llm-prompt-templates';
import { message } from 'antd';

import { BaseDom } from './base.dom';

export class GenericDom extends BaseDom {
protected name = 'Generic';

// eslint-disable-next-line @typescript-eslint/no-empty-function
protected addCustomTrigger() {}

protected usePrompt(prompt: IPrompt) {
const clipboardItem = new ClipboardItem({
'text/plain': new Blob([prompt.content], { type: 'text/plain' }),
});
navigator.clipboard.write([clipboardItem]);

message.info(`${prompt.name} prompt copied to clipboard`);
}
}

function init() {
const genericDom = new GenericDom();

setTimeout(genericDom.init, 1000);
}

init();
12 changes: 2 additions & 10 deletions apps/chrome-extension/src/pages/Content/llms/bard/bard.dom.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { BaseDom } from '../../base.dom';
import { ChatGPTDom } from '../chatgpt/chatgpt.dom';

export class BardDom extends BaseDom {
export class BardDom extends ChatGPTDom {
protected name = 'Bard';
protected textAreaSelector = 'div > textarea';

protected setText(text: string) {
const textArea = this.getTextArea();
console.log('Setting text', text);
textArea.focus();
textArea.value = text;
textArea.style.height = textArea.scrollHeight + 'px';
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import { IPrompt } from '@rpidanny/llm-prompt-templates';

import { BaseDom } from '../../base.dom';

export class ChatGPTDom extends BaseDom {
protected name = 'ChatGPT';
protected textAreaSelector = 'div.relative > textarea';

protected setText(text: string) {
private getTextArea(): HTMLTextAreaElement {
const textArea = document.querySelector<HTMLTextAreaElement>(
this.textAreaSelector
);

if (!textArea) throw new Error('Could not find text area');

return textArea;
}

protected addCustomTrigger() {
this.getTextArea().addEventListener('input', (event) => {
const input = event.target as HTMLTextAreaElement;
const text = input.value;

if (text === '/templates') {
this.showPrompts();
} else if (this.isPromptsViewOpen) {
this.hidePrompts();
}
});
}

protected usePrompt(prompt: IPrompt) {
const textArea = this.getTextArea();
console.log('Setting text', text);
console.log('Setting text', prompt.content);
textArea.focus();
textArea.value = text;
textArea.value = prompt.content;
textArea.style.height = textArea.scrollHeight + 'px';
}
}
7 changes: 7 additions & 0 deletions apps/chrome-extension/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
'bard',
'index.ts'
),
defaultContentScript: path.join(
config.context,
'src',
'pages',
'Content',
'generic.dom.ts'
),
},
output: {
filename: '[name].bundle.js',
Expand Down

0 comments on commit 709da43

Please sign in to comment.