ai: allow selecting chatgpt-compatible chatbot provider (#152)

This commit is contained in:
end-4
2024-02-27 11:27:47 +07:00
parent 71a6ceacc8
commit 7cd77e3168
11 changed files with 295 additions and 98 deletions
+1 -1
View File
@@ -84,6 +84,6 @@ console.log('uwu');
- Random instruction thing
- To update arch lincox, run \`sudo pacman -Syu\`
\`\`\`tex
\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2}
\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} hmmmmmm \\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2}
\`\`\`
`;
+128 -33
View File
@@ -4,12 +4,13 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
import ChatGPT from '../../../services/chatgpt.js';
import ChatGPT from '../../../services/gpt.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
import { markdownTest } from '../../.miscutils/md2pango.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets/icons`);
@@ -19,6 +20,94 @@ export const chatGPTTabIcon = Icon({
icon: `openai-symbolic`,
});
const ProviderSwitcher = () => {
const ProviderChoice = (id, provider) => {
const providerSelected = MaterialIcon('check', 'norm', {
setup: (self) => self.hook(ChatGPT, (self) => {
self.toggleClassName('invisible', ChatGPT.providerID !== id);
}, 'providerChanged')
});
return Button({
tooltipText: provider.description,
onClicked: () => {
ChatGPT.providerID = id;
providerList.revealChild = false;
indicatorChevron.label = 'expand_more';
},
child: Box({
className: 'spacing-h-10 txt',
children: [
Icon({
icon: provider['logo_name'],
className: 'txt-large'
}),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
label: provider.name,
}),
providerSelected
],
}),
setup: setupCursorHover,
});
}
const indicatorChevron = MaterialIcon('expand_more', 'norm');
const indicatorButton = Button({
tooltipText: 'Select ChatGPT-compatible API provider',
child: Box({
className: 'spacing-h-10 txt',
children: [
MaterialIcon('cloud', 'norm'),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
label: ChatGPT.providerID,
setup: (self) => self.hook(ChatGPT, (self) => {
self.label = `${ChatGPT.providers[ChatGPT.providerID]['name']}`;
}, 'providerChanged')
}),
indicatorChevron,
]
}),
onClicked: () => {
providerList.revealChild = !providerList.revealChild;
indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more');
},
setup: setupCursorHover,
});
const providerList = Revealer({
revealChild: false,
transition: 'slide_down',
transitionDuration: 180,
child: Box({
vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list',
children: [
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
Box({
className: 'spacing-v-5',
vertical: true,
setup: (self) => self.hook(ChatGPT, (self) => {
self.children = Object.entries(ChatGPT.providers)
.map(([id, provider]) => ProviderChoice(id, provider));
}, 'initialized'),
})
]
})
})
return Box({
hpack: 'center',
vertical: true,
className: 'sidebar-chat-providerswitcher',
children: [
indicatorButton,
providerList,
]
})
}
const ChatGPTInfo = () => {
const openAiLogo = Icon({
hpack: 'center',
@@ -34,7 +123,7 @@ const ChatGPTInfo = () => {
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
label: 'Assistant (ChatGPT 3.5)',
label: 'Assistant (GPTs)',
}),
Box({
className: 'spacing-h-5',
@@ -134,11 +223,11 @@ export const OpenaiApiKeyInstructions = () => Box({
wrap: true,
className: 'txt sidebar-chat-welcome-txt',
justify: Gtk.Justification.CENTER,
label: 'An OpenAI API key is required\nYou can grab one <u>here</u>, then enter it below'
label: 'An API key is required\nYou can grab one <u>here</u>, then enter it below'
}),
setup: setupCursorHover,
onClicked: () => {
Utils.execAsync(['bash', '-c', `xdg-open https://platform.openai.com/api-keys &`]);
Utils.execAsync(['bash', '-c', `xdg-open ${ChatGPT.getKeyUrl}`]);
}
})
})]
@@ -180,34 +269,6 @@ const clearChat = () => {
}
}
export const chatGPTView = Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
chatGPTWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
});
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
@@ -267,4 +328,38 @@ export const sendMessage = (text) => {
else {
ChatGPT.send(text);
}
}
}
export const chatGPTView = Box({
vertical: true,
children: [
ProviderSwitcher(),
Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
chatGPTWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
})
]
});
+32 -29
View File
@@ -172,34 +172,6 @@ const clearChat = () => {
}
}
export const geminiView = Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
geminiWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
});
const CommandButton = (command) => Button({
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
onClicked: () => sendMessage(command),
@@ -259,4 +231,35 @@ export const sendMessage = (text) => {
else {
Gemini.send(text);
}
}
}
export const geminiView = Box({
homogeneous: true,
children: [Scrollable({
className: 'sidebar-chat-viewport',
vexpand: true,
child: Box({
vertical: true,
children: [
geminiWelcome,
chatContent,
]
}),
setup: (scrolledWindow) => {
// Show scrollbar
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = scrolledWindow.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
// Avoid click-to-scroll-widget-to-view behavior
Utils.timeout(1, () => {
const viewport = scrolledWindow.child;
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
})
// Always scroll to bottom with new content
const adjustment = scrolledWindow.get_vadjustment();
adjustment.connect("changed", () => {
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
})
}
})]
});
+3 -3
View File
@@ -6,7 +6,7 @@ const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverInfo } from '../.widgetutils/cursorhover.js';
import { contentStack } from './sideleft.js';
// APIs
import ChatGPT from '../../services/chatgpt.js';
import ChatGPT from '../../services/gpt.js';
import Gemini from '../../services/gemini.js';
import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
@@ -26,12 +26,12 @@ const APIS = [
placeholderText: 'Message Gemini...',
},
{
name: 'Assistant (ChatGPT 3.5)',
name: 'Assistant (GPTs)',
sendCommand: chatGPTSendMessage,
contentWidget: chatGPTView,
commandBar: chatGPTCommands,
tabIcon: chatGPTTabIcon,
placeholderText: 'Message ChatGPT...',
placeholderText: 'Message the model...',
},
{
name: 'Waifus',