Skip to content

Commit

Permalink
Merge pull request #29 from rtuszik/tag-limit
Browse files Browse the repository at this point in the history
allow for multiple tags, tag limit
  • Loading branch information
HyeonseoNam authored Dec 12, 2024
2 parents 9415fa0 + f6b0afe commit e02924f
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 94 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4",
"openai": "4.76.0"
"openai": "4.76.1"
}
}
220 changes: 132 additions & 88 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Plugin, Notice } from "obsidian";
import { AutoClassifierSettingTab, AutoClassifierSettings, DEFAULT_SETTINGS, OutLocation, OutType} from "src/settings";
import {
AutoClassifierSettingTab,
AutoClassifierSettings,
DEFAULT_SETTINGS,
OutLocation,
OutType,
} from "src/settings";
import { ViewManager } from "src/view-manager";
import { ChatGPT } from 'src/api';
import { ChatGPT } from "src/api";

enum InputType {
SelectedArea,
Title,
FrontMatter,
Content
Content,
}

export default class AutoClassifierPlugin extends Plugin {
Expand All @@ -19,32 +25,32 @@ export default class AutoClassifierPlugin extends Plugin {

// Commands
this.addCommand({
id: 'classify-tag-selected',
name: 'Classify tag from Selected Area',
id: "classify-tag-selected",
name: "Classify tag from Selected Area",
callback: async () => {
await this.runClassifyTag(InputType.SelectedArea);
}
},
});
this.addCommand({
id: 'classify-tag-title',
name: 'Classify tag from Note Title',
id: "classify-tag-title",
name: "Classify tag from Note Title",
callback: async () => {
await this.runClassifyTag(InputType.Title);
}
},
});
this.addCommand({
id: 'classify-tag-frontmatter',
name: 'Classify tag from FrontMatter',
id: "classify-tag-frontmatter",
name: "Classify tag from FrontMatter",
callback: async () => {
await this.runClassifyTag(InputType.FrontMatter);
}
},
});
this.addCommand({
id: 'classify-tag-content',
name: 'Classify tag from Note Content',
id: "classify-tag-content",
name: "Classify tag from Note Content",
callback: async () => {
await this.runClassifyTag(InputType.Content);
}
},
});

this.addSettingTab(new AutoClassifierSettingTab(this.app, this));
Expand All @@ -57,7 +63,26 @@ export default class AutoClassifierPlugin extends Plugin {
await this.saveData(this.settings);
}

async onunload() {
async onunload() {}

// create loading spin in the Notice message
createLoadingNotice(text: string, number = 10000): Notice {
const notice = new Notice("", number);
const loadingContainer = document.createElement("div");
loadingContainer.addClass("loading-container");

const loadingIcon = document.createElement("div");
loadingIcon.addClass("loading-icon");
const loadingText = document.createElement("span");
loadingText.textContent = text;
//@ts-ignore
notice.noticeEl.empty();
loadingContainer.appendChild(loadingIcon);
loadingContainer.appendChild(loadingText);
//@ts-ignore
notice.noticeEl.appendChild(loadingContainer);

return notice;
}

async runClassifyTag(inputType: InputType) {
Expand All @@ -76,28 +101,25 @@ export default class AutoClassifierPlugin extends Plugin {
// ------- [API Key check] -------
if (!this.settings.apiKey) {
new Notice(`⛔ ${this.manifest.name}: You shuld input your API Key`);
return null
return null;
}
// ------- [Input] -------
const refs = this.settings.commandOption.refs;
// reference check
if (this.settings.commandOption.useRef && (!refs || refs.length == 0)) {
new Notice(`⛔ ${this.manifest.name}: no reference tags`);
return null
return null;
}

// Set Input
let input: string | null = '';
// Set Input
let input: string | null = "";
if (inputType == InputType.SelectedArea) {
input = await this.viewManager.getSelection();
}
else if (inputType == InputType.Title) {
} else if (inputType == InputType.Title) {
input = await this.viewManager.getTitle();
}
else if (inputType == InputType.FrontMatter) {
} else if (inputType == InputType.FrontMatter) {
input = await this.viewManager.getFrontMatter();
}
else if (inputType == InputType.Content) {
} else if (inputType == InputType.Content) {
input = await this.viewManager.getContent();
}

Expand All @@ -109,16 +131,16 @@ export default class AutoClassifierPlugin extends Plugin {

// Replace {{input}}, {{reference}}
let user_prompt = this.settings.commandOption.prmpt_template;
user_prompt = user_prompt.replace('{{input}}', input);
user_prompt = user_prompt.replace('{{reference}}', refs.join(','));
user_prompt = user_prompt.replace("{{input}}", input);
user_prompt = user_prompt.replace("{{reference}}", refs.join(","));

const system_role = this.settings.commandOption.prmpt_template;

// ------- [API Processing] -------
// Call API
const responseRaw = await ChatGPT.callAPI(
system_role,
user_prompt,
system_role,
user_prompt,
this.settings.apiKey,
this.settings.commandOption.model,
this.settings.commandOption.max_tokens,
Expand All @@ -128,65 +150,87 @@ export default class AutoClassifierPlugin extends Plugin {
undefined,
this.settings.baseURL,
);
const jsonRegex = /reliability[\s\S]*?:\s*([\d.]+)[\s\S]*?output[\s\S]*?:\s*"([^"^}]+)/;
const match = responseRaw.match(jsonRegex);
let resOutput;
let resReliabity;
if (match && match.length > 1) {
resOutput = match[2];
resReliabity = parseFloat(match[1]);
} else {
new Notice(`⛔ ${this.manifest.name}: output format error (output: ${responseRaw})`);
return null;
}


// Avoid row reliability
if (resReliabity <= 0.2) {
new Notice(`⛔ ${this.manifest.name}: response has row reliability (${resReliabity})`);
return null;
}

// ------- [Add Tag] -------
// Output Type 1. [Tag Case] + Output Type 2. [Wikilink Case]
if (commandOption.outType == OutType.Tag || commandOption.outType == OutType.Wikilink) {
if (commandOption.outLocation == OutLocation.Cursor) {
this.viewManager.insertAtCursor(resOutput, commandOption.overwrite, commandOption.outType, commandOption.outPrefix, commandOption.outSuffix);
}
else if (commandOption.outLocation == OutLocation.ContentTop) {
this.viewManager.insertAtContentTop(resOutput, commandOption.outType, commandOption.outPrefix, commandOption.outSuffix);
try {
try {
const response = JSON.parse(responseRaw);
const resReliability = response.reliability;
const resOutputs = response.outputs;

// Validate response format
if (!Array.isArray(resOutputs)) {
new Notice(`⛔ ${this.manifest.name}: output format error (expected array)`);
return null;
}

// Limit number of suggestions
const limitedOutputs = resOutputs.slice(0, this.settings.commandOption.max_suggestions);

// Validate output format
if (!Array.isArray(limitedOutputs)) {
new Notice(`⛔ ${this.manifest.name}: output format error (expected array)`);
return null;
}

// Avoid low reliability
if (resReliability <= 0.2) {
new Notice(
`⛔ ${this.manifest.name}: response has low reliability (${resReliability})`,
);
return null;
}

// ------- [Add Tags] -------
for (const resOutput of limitedOutputs) {
// Output Type 1. [Tag Case] + Output Type 2. [Wikilink Case]
if (
commandOption.outType == OutType.Tag ||
commandOption.outType == OutType.Wikilink
) {
if (commandOption.outLocation == OutLocation.Cursor) {
this.viewManager.insertAtCursor(
resOutput,
commandOption.overwrite,
commandOption.outType,
commandOption.outPrefix,
commandOption.outSuffix,
);
} else if (commandOption.outLocation == OutLocation.ContentTop) {
this.viewManager.insertAtContentTop(
resOutput,
commandOption.outType,
commandOption.outPrefix,
commandOption.outSuffix,
);
}
}
// Output Type 3. [Frontmatter Case]
else if (commandOption.outType == OutType.FrontMatter) {
this.viewManager.insertAtFrontMatter(
commandOption.key,
resOutput,
commandOption.overwrite,
commandOption.outPrefix,
commandOption.outSuffix,
);
}
// Output Type 4. [Title]
else if (commandOption.outType == OutType.Title) {
this.viewManager.insertAtTitle(
resOutput,
commandOption.overwrite,
commandOption.outPrefix,
commandOption.outSuffix,
);
}
}
new Notice(`✅ ${this.manifest.name}: classified with ${limitedOutputs.length} tags`);
} catch (error) {
new Notice(`⛔ ${this.manifest.name}: JSON parsing error - ${error}`);
return null;
}
} catch (error) {
new Notice(`⛔ ${this.manifest.name}: ${error}`);
return null;
}
// Output Type 3. [Frontmatter Case]
else if (commandOption.outType == OutType.FrontMatter) {
this.viewManager.insertAtFrontMatter(commandOption.key, resOutput, commandOption.overwrite, commandOption.outPrefix, commandOption.outSuffix);
}
// Output Type 4. [Title]
else if (commandOption.outType == OutType.Title) {
this.viewManager.insertAtTitle(resOutput, commandOption.overwrite, commandOption.outPrefix, commandOption.outSuffix);
}
new Notice(`✅ ${this.manifest.name}: classified to ${resOutput}`);
}

// create loading spin in the Notice message
createLoadingNotice(text: string, number = 10000): Notice {
const notice = new Notice('', number);
const loadingContainer = document.createElement('div');
loadingContainer.addClass('loading-container');

const loadingIcon = document.createElement('div');
loadingIcon.addClass('loading-icon');
const loadingText = document.createElement('span');
loadingText.textContent = text;
//@ts-ignore
notice.noticeEl.empty();
loadingContainer.appendChild(loadingIcon);
loadingContainer.appendChild(loadingText);
//@ts-ignore
notice.noticeEl.appendChild(loadingContainer);

return notice;
}
}


22 changes: 21 additions & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface CommandOption {
prmpt_template: string;
model: string;
max_tokens: number;
max_suggestions: number;
}


Expand Down Expand Up @@ -82,6 +83,7 @@ export const DEFAULT_SETTINGS: AutoClassifierSettings = {
prmpt_template: DEFAULT_PROMPT_TEMPLATE,
model: "gpt-3.5-turbo",
max_tokens: 150,
max_suggestions: 3,
},
};

Expand Down Expand Up @@ -422,8 +424,25 @@ export class AutoClassifierSettingTab extends PluginSettingTab {


// ------- [Advanced Setting] -------
// Toggle custom rule
containerEl.createEl('h1', { text: 'Advanced Setting' });

new Setting(containerEl)
.setName('Maximum Tag Suggestions')
.setDesc("Maximum number of tags to suggest (1-10)")
.addText((text) =>
text
.setPlaceholder('3')
.setValue(String(commandOption.max_suggestions))
.onChange(async (value) => {
const num = parseInt(value);
if (num >= 1 && num <= 10) {
commandOption.max_suggestions = num;
await this.plugin.saveSettings();
}
})
);

// Toggle custom rule
new Setting(containerEl)
.setName('Use Custom Request Template')
.addToggle((toggle) =>
Expand Down Expand Up @@ -527,6 +546,7 @@ export class AutoClassifierSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);

}
}

Expand Down
8 changes: 4 additions & 4 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ export const DEFAULT_PROMPT_TEMPLATE = `Classify this content:
"""
{{input}}
"""
Answer format is JSON {reliability:0~1, output:selected_category}.
Even if you are not sure, qualify the reliability and select one.
Output must be one of these:
Answer format is JSON {reliability:0~1, outputs:[tag1,tag2,...]}.
Even if you are not sure, qualify the reliability and select the best matches.
Output tags must be from these options:
{{reference}}
`;
Expand All @@ -17,4 +17,4 @@ export const DEFAULT_PROMPT_TEMPLATE_WO_REF = `Classify this content:
Answer format is JSON {reliability:0~1, output:selected_category}.
Even if you are not sure, qualify the reliability and recommend a proper category.
`;
`;

0 comments on commit e02924f

Please sign in to comment.