Skip to content

Commit

Permalink
Get rid of the onLoad signal
Browse files Browse the repository at this point in the history
  • Loading branch information
zcbenz committed May 28, 2023
1 parent 9c362b7 commit 6131618
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 41 deletions.
2 changes: 1 addition & 1 deletion TODO
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
* chat view
* synch panel size when switching chats in dashboard
* show suggestions in blank chat view
* entry
* flash when sening empty message
Expand All @@ -12,3 +11,4 @@
* save custom icon to smaller size
* update yoga to use gap
* lazy main view creation / reuse same type of view in dashboard
* load service history lazily
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"eventsource-parser": "0.1.0",
"fetch-yode": "1.x",
"fs-extra": "11.1.0",
"gui": "0.13.8",
"gui": "0.13.9",
"highlight.js": "11.7.0",
"html-escaper": "3.0.3",
"marked": "4.3.0",
Expand Down
41 changes: 26 additions & 15 deletions src/model/base-chat-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten
static nextId = 0;
static fromId = (id: number) => BaseChatService.services[id];

onLoad: Signal<() => void> = new Signal;
onNewTitle: Signal<(title: string | null) => void> = new Signal;
onUserMessage: Signal<(message: ChatMessage) => void> = new Signal;
onClearError: Signal<() => void> = new Signal;
Expand All @@ -49,7 +48,7 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten
id: number = ++BaseChatService.nextId;

// Whether the chat messages have be recovered from disk.
isLoaded;
isLoaded = false;

// ID of the chat history kept on disk.
moment?: string;
Expand All @@ -69,6 +68,9 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten
// The aborter that can be used to abort current call.
aborter: AbortController;

// The promise of current load process.
protected loadPromise?: Promise<void>;

// Title of the chat.
protected customTitle?: string;
protected title?: string;
Expand All @@ -84,20 +86,8 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten

constructor(options: BaseChatServiceOptions<T, P>) {
super(options);
this.moment = options.moment;
BaseChatService.services[this.id] = this;
if (options.moment) {
// Load from saved history.
this.isLoaded = false;
this.moment = options.moment;
historyKeeper.remember(this.moment).then((data?: BaseChatHistoryData) => {
if (data)
this.deserializeHistory(data);
this.isLoaded = true;
this.onLoad.emit();
});
} else {
this.isLoaded = true;
}
}

serialize() {
Expand All @@ -115,6 +105,17 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten
delete BaseChatService.services[this.id];
}

// Load chat history.
async load() {
if (this.isLoaded)
return;
if (this.loadPromise)
return await this.loadPromise;
this.loadPromise = this.loadHistory();
await this.loadPromise;
this.isLoaded = true;
}

// Restore from serialized history data.
deserializeHistory(data: BaseChatHistoryData) {
if (data.history)
Expand Down Expand Up @@ -383,6 +384,16 @@ export default abstract class BaseChatService<T extends WebAPI = WebAPI, P exten
this.onMessage.emit(message);
}

// Load history from disk.
protected async loadHistory() {
if (!this.moment)
return;
// Load from saved history.
const data = await historyKeeper.remember(this.moment);
if (data)
this.deserializeHistory(data);
}

// Write history to disk.
protected saveHistory() {
if (!this.moment) {
Expand Down
8 changes: 7 additions & 1 deletion src/view/base-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ export default abstract class BaseView<T extends WebService<WebAPI> = WebService
this.unload();
}

loadService(service: T) {
// Load service, return false if there is nothing to do in sub-classes.
async loadService(service: T) {
if (this.service == service)
return false;
this.unload();
this.service = service;
return true;
}

// Disconnect from the service.
unload() {
this.service = null;
this.serviceConnections.disconnectAll();
Expand Down
14 changes: 7 additions & 7 deletions src/view/chat-list-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class ChatListItem extends Clickable {
constructor(service: ChatService) {
super();
this.service = service;
this.connections.add(service.onNewTitle.connect(this.setTitle.bind(this)));

this.view.setStyle({width: '100%', height: 32, marginBottom: 2});
this.view.onMouseEnter = () => this.closeButton.view.setVisible(true);
this.view.onMouseLeave = () => this.closeButton.view.setVisible(false);
Expand All @@ -44,12 +44,8 @@ export default class ChatListItem extends Clickable {
this.closeButton.onClick = () => this.onClose.emit(this);
this.view.addChildView(this.closeButton.view);

this.setTitle(service.getTitle());
if (!service.isLoaded) {
this.connections.add(service.onLoad.connect(() => {
this.setTitle(service.getTitle());
}));
}
this.service.load().then(() => this.setTitle(service.getTitle()));
this.connections.add(service.onNewTitle.connect(this.setTitle.bind(this)));
}

destructor() {
Expand Down Expand Up @@ -108,6 +104,8 @@ export default class ChatListItem extends Clickable {
}

onDraw(view, painter: gui.Painter) {
if (!this.#titleText) // no title set yet
return;
// Background color.
const theme = this.darkMode ? style.dark : style.light;
if (this.selected)
Expand All @@ -129,6 +127,8 @@ export default class ChatListItem extends Clickable {
}

#updateTooltip() {
if (!this.#titleText) // no title set yet
return;
const bounds = Object.assign(this.view.getBounds(), {x: 0, y: 0});
// Always consider space for the close button since the tooltip only shows
// when mouse is hovering on it.
Expand Down
15 changes: 6 additions & 9 deletions src/view/chat-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,18 @@ export default class ChatView extends BaseView<BaseChatService> {
}

async loadService(service: BaseChatService) {
if (this.service == service)
return;
if (!(service instanceof BaseChatService))
throw new Error('ChatView can only be used with BaseChatService.');
// Clear previous load.
this.unload();
if (!await super.loadService(service))
return false;

// Delay loading until the service is ready.
if (!service.isLoaded) {
this.serviceConnections.add(service.onLoad.connect(this.loadService.bind(this, service)));
return;
}
await service.load();
// Load messages.
this.messagesView.loadChatService(service);
super.loadService(service);
this.onNewTitle.emit();
this.#updateSwitchButton();

// Connect signals.
this.serviceConnections.add(service.onNewTitle.connect(
this.onNewTitle.emit.bind(this.onNewTitle)));
Expand Down Expand Up @@ -211,6 +207,7 @@ export default class ChatView extends BaseView<BaseChatService> {
this.service.history[this.service.history.length - 1].role == ChatRole.User)
this.messagesView.setReplyActions(['resend']);
}
return true;
}

unload() {
Expand Down
9 changes: 4 additions & 5 deletions src/view/multi-chats-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,11 @@ export default class MultiChatsView extends SplitView<BaseMultiChatsService> {
item.destructor();
}

loadService(service: BaseMultiChatsService) {
if (this.service == service)
return;
async loadService(service: BaseMultiChatsService) {
if (!(service instanceof BaseMultiChatsService))
throw new Error('MultiChatsView can only be used with MultiChatsService');
this.unload();
super.loadService(service);
if (!await super.loadService(service))
return false;

// Load existing chats.
if (this.service.chats.length == 0)
Expand All @@ -146,6 +144,7 @@ export default class MultiChatsView extends SplitView<BaseMultiChatsService> {
this.connections.add(service.onNewChat.connect(this.#onNewChat.bind(this)));
this.connections.add(service.onRemoveChat.connect(this.#onRemoveChat.bind(this)));
this.connections.add(service.onClearChats.connect(this.#onClearChats.bind(this)));
return true;
}

unload() {
Expand Down
2 changes: 1 addition & 1 deletion test/chat-service-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('ChatService', () => {
await historyKeeper.flush();
assert.deepEqual(record, await historyKeeper.remember(moment));
service = new ChatService({name: 'Test', api: service.api, moment});
await new Promise<void>((resolve) => service.onLoad.connect(resolve));
await service.load();
assert.equal(record.title, service.getTitle());
assert.deepEqual(record.history, service.history);
});
Expand Down
2 changes: 1 addition & 1 deletion test/multi-chats-service-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('MultiChatsService', () => {
chats: [ {moment} ],
};
service = new MultiChatsService(MultiChatsService.deserialize(data));
await new Promise<void>((resolve) => service.chats[0].onLoad.connect(resolve));
await service.chats[0].load();
assert.equal(service.chats[0].getTitle(), record.title);
assert.deepEqual(service.chats[0].history, record.history);
assert.deepEqual(service.serialize(), data);
Expand Down

0 comments on commit 6131618

Please sign in to comment.