sidebar: ai: loading skeleton

This commit is contained in:
end-4
2024-04-14 21:00:52 +07:00
parent 72121dac20
commit 69fdb53c9d
6 changed files with 90 additions and 40 deletions
@@ -10,7 +10,7 @@ let configOptions = {
'enhancements': true, 'enhancements': true,
'useHistory': true, 'useHistory': true,
'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering 'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering
'proxyUrl': '', // Can be "socks5://127.0.0.1:9050" or "http://127.0.0.1:8080" for example. Leave it blank if you don't need it. 'proxyUrl': null, // Can be "socks5://127.0.0.1:9050" or "http://127.0.0.1:8080" for example. Leave it blank if you don't need it.
}, },
'animations': { 'animations': {
'choreographyDelay': 35, 'choreographyDelay': 35,
@@ -3,7 +3,7 @@ import GtkSource from "gi://GtkSource?version=3.0";
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Label, Icon, Scrollable } = Widget; const { Box, Button, Label, Icon, Scrollable, Stack } = Widget;
const { execAsync, exec } = Utils; const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import md2pango from '../../.miscutils/md2pango.js'; import md2pango from '../../.miscutils/md2pango.js';
@@ -77,7 +77,7 @@ const TextBlock = (content = '') => Label({
Utils.execAsync(['bash', '-c', `rm ${LATEX_DIR}/*`]) Utils.execAsync(['bash', '-c', `rm ${LATEX_DIR}/*`])
.then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`])) .then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`]))
.catch(() => { }); .catch(() => { });
const Latex = (content = '') => { const Latex = (content = '') => {
const latexViewArea = Box({ const latexViewArea = Box({
// vscroll: 'never', // vscroll: 'never',
@@ -286,7 +286,25 @@ const MessageContent = (content) => {
} }
export const ChatMessage = (message, modelName = 'Model') => { export const ChatMessage = (message, modelName = 'Model') => {
const TextSkeleton = (extraClassName = '') => Box({
className: `sidebar-chat-message-skeletonline ${extraClassName}`,
})
const messageContentBox = MessageContent(message.content); const messageContentBox = MessageContent(message.content);
const messageLoadingSkeleton = Box({
vertical: true,
className: 'spacing-v-5',
children: Array.from({ length: 3 }, (_, id) => TextSkeleton(`sidebar-chat-message-skeletonline-offset${id}`)),
})
const messageArea = Stack({
homogeneous: message.role !== 'user',
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
children: {
'thinking': messageLoadingSkeleton,
'message': messageContentBox,
},
shown: message.thinking ? 'thinking' : 'message',
});
const thisMessage = Box({ const thisMessage = Box({
className: 'sidebar-chat-message', className: 'sidebar-chat-message',
homogeneous: true, homogeneous: true,
@@ -302,11 +320,15 @@ export const ChatMessage = (message, modelName = 'Model') => {
useMarkup: true, useMarkup: true,
label: (message.role == 'user' ? USERNAME : modelName), label: (message.role == 'user' ? USERNAME : modelName),
}), }),
messageContentBox, Box({
homogeneous: true,
className: 'sidebar-chat-messagearea',
children: [messageArea]
})
], ],
setup: (self) => self setup: (self) => self
.hook(message, (self, isThinking) => { .hook(message, (self, isThinking) => {
messageContentBox.toggleClassName('thinking', message.thinking); messageArea.shown = message.thinking ? 'thinking' : 'message';
}, 'notify::thinking') }, 'notify::thinking')
.hook(message, (self) => { // Message update .hook(message, (self) => { // Message update
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user'); messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
+3 -22
View File
@@ -98,28 +98,6 @@ $elevation_margin: 0.476rem;
margin: $elevation_margin; margin: $elevation_margin;
} }
@keyframes flyin-top {
from {
margin-top: -2.795rem;
}
to {
margin-top: 0rem;
}
}
@keyframes flyin-bottom {
from {
margin-top: 4.841rem;
margin-bottom: -4.841rem;
}
to {
margin-bottom: 0rem;
margin-top: 0rem;
}
}
@mixin menu_decel { @mixin menu_decel {
transition: 300ms cubic-bezier(0.1, 1, 0, 1); transition: 300ms cubic-bezier(0.1, 1, 0, 1);
} }
@@ -163,12 +141,15 @@ $elevation_margin: 0.476rem;
@mixin element_decel { @mixin element_decel {
transition: 300ms cubic-bezier(0, 0.55, 0.45, 1); transition: 300ms cubic-bezier(0, 0.55, 0.45, 1);
} }
@mixin element_bounceOut { @mixin element_bounceOut {
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1); transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
} }
@mixin element_accel { @mixin element_accel {
transition: 300ms cubic-bezier(0.55, 0, 1, 0.45); transition: 300ms cubic-bezier(0.55, 0, 1, 0.45);
} }
@mixin element_easeInOut { @mixin element_easeInOut {
transition: 300ms cubic-bezier(0.85, 0, 0.15, 1); transition: 300ms cubic-bezier(0.85, 0, 0.15, 1);
} }
+47 -1
View File
@@ -539,13 +539,59 @@ $colorpicker_rounding: 0.341rem;
background-color: mix($primary, $hovercolor, 40%); background-color: mix($primary, $hovercolor, 40%);
} }
.sidebar-chat-messagearea {
margin: 0.341rem;
}
.sidebar-chat-message { .sidebar-chat-message {
margin: 0.682rem;
@include normal-rounding; @include normal-rounding;
@include group-padding; @include group-padding;
background-color: $layer1; background-color: $layer1;
} }
$skeleton-accent: mix($secondary, $onSecondary, 50%);
@keyframes sidebar-chat-message-skeletonline-anim {
0% {
background-position: 175% 0%;
}
100% {
background-position: 50% 0%;
}
}
.sidebar-chat-message-skeletonline {
border-radius: $rounding_verysmall;
min-height: 1.364rem;
background-color: $layer2;
}
.sidebar-chat-message-skeletonline-offset0 {
background-repeat: no-repeat;
background: linear-gradient(to right, $layer3 0%, $skeleton-accent 25%, $layer3 50%, $layer3 100%);
background-size: 500% 500%;
animation: sidebar-chat-message-skeletonline-anim 3s linear;
animation-iteration-count: infinite;
}
.sidebar-chat-message-skeletonline-offset1 {
background-repeat: no-repeat;
background: linear-gradient(to right, $layer3 0%, $layer3 50%, $skeleton-accent 75%, $layer3 100%);
background-size: 500% 500%;
animation: sidebar-chat-message-skeletonline-anim 3s linear;
animation-iteration-count: infinite;
}
.sidebar-chat-message-skeletonline-offset2 {
margin-right: 5.795rem;
background-repeat: no-repeat;
background: linear-gradient(to right, $layer3 0%, $layer3 25%, $skeleton-accent 50%, $layer3 75%, $layer3 100%);
background-size: 500% 500%;
animation: sidebar-chat-message-skeletonline-anim 3s linear;
animation-iteration-count: infinite;
}
.sidebar-chat-indicator { .sidebar-chat-indicator {
@include element_decel; @include element_decel;
@include full-rounding; @include full-rounding;
+7 -7
View File
@@ -62,11 +62,11 @@ class GeminiMessage extends Service {
_role = ''; _role = '';
_parts = [{ text: '' }]; _parts = [{ text: '' }];
_thinking = false; _thinking;
_done = false; _done = false;
_rawData = ''; _rawData = '';
constructor(role, content, thinking = false, done = false) { constructor(role, content, thinking = true, done = false) {
super(); super();
this._role = role; this._role = role;
this._parts = [{ text: content }]; this._parts = [{ text: content }];
@@ -97,8 +97,8 @@ class GeminiMessage extends Service {
get label() { return this._parserState.parsed + this._parserState.stack.join('') } get label() { return this._parserState.parsed + this._parserState.stack.join('') }
get thinking() { return this._thinking } get thinking() { return this._thinking }
set thinking(thinking) { set thinking(value) {
this._thinking = thinking; this._thinking = value;
this.notify('thinking') this.notify('thinking')
this.emit('changed') this.emit('changed')
} }
@@ -116,7 +116,7 @@ class GeminiMessage extends Service {
parseSection() { parseSection() {
if (this._thinking) { if (this._thinking) {
this._thinking = false; this.thinking = false;
this._parts[0].text = ''; this._parts[0].text = '';
} }
const parsedData = JSON.parse(this._rawData); const parsedData = JSON.parse(this._rawData);
@@ -273,12 +273,12 @@ class GeminiService extends Service {
} }
addMessage(role, message) { addMessage(role, message) {
this._messages.push(new GeminiMessage(role, message)); this._messages.push(new GeminiMessage(role, message, false));
this.emit('newMsg', this._messages.length - 1); this.emit('newMsg', this._messages.length - 1);
} }
send(msg) { send(msg) {
this._messages.push(new GeminiMessage('user', msg)); this._messages.push(new GeminiMessage('user', msg, false));
this.emit('newMsg', this._messages.length - 1); this.emit('newMsg', this._messages.length - 1);
const aiResponse = new GeminiMessage('model', 'thinking...', true, false) const aiResponse = new GeminiMessage('model', 'thinking...', true, false)
+6 -5
View File
@@ -76,10 +76,10 @@ class GPTMessage extends Service {
_role = ''; _role = '';
_content = ''; _content = '';
_thinking = false; _thinking;
_done = false; _done = false;
constructor(role, content, thinking = false, done = false) { constructor(role, content, thinking = true, done = false) {
super(); super();
this._role = role; this._role = role;
this._content = content; this._content = content;
@@ -103,8 +103,8 @@ class GPTMessage extends Service {
get label() { return this._parserState.parsed + this._parserState.stack.join('') } get label() { return this._parserState.parsed + this._parserState.stack.join('') }
get thinking() { return this._thinking } get thinking() { return this._thinking }
set thinking(thinking) { set thinking(value) {
this._thinking = thinking; this._thinking = value;
this.notify('thinking') this.notify('thinking')
this.emit('changed') this.emit('changed')
} }
@@ -202,6 +202,7 @@ class GPTService extends Service {
} }
readResponse(stream, aiResponse) { readResponse(stream, aiResponse) {
aiResponse.thinking = false;
stream.read_line_async( stream.read_line_async(
0, null, 0, null,
(stream, res) => { (stream, res) => {
@@ -234,7 +235,7 @@ class GPTService extends Service {
} }
send(msg) { send(msg) {
this._messages.push(new GPTMessage('user', msg)); this._messages.push(new GPTMessage('user', msg, false, true));
this.emit('newMsg', this._messages.length - 1); this.emit('newMsg', this._messages.length - 1);
const aiResponse = new GPTMessage('assistant', 'thinking...', true, false) const aiResponse = new GPTMessage('assistant', 'thinking...', true, false)