fix redux dispatch context error

This commit is contained in:
Zhang Minghan 2023-09-05 22:18:28 +08:00
parent 7ab622c6e9
commit 9b4155cf75
10 changed files with 224 additions and 67 deletions

View File

@ -18,6 +18,15 @@
&:last-child {
animation: FlexInAnimationFromBottom 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s 1 normal forwards running;
.bing {
animation: fadein 0.2s ease-in-out;
@keyframes fadein {
from { opacity: 0.5; }
to { opacity: 1; }
}
}
}
.message-content {
@ -99,6 +108,10 @@
}
}
.code-inline {
margin: 0 2px;
}
pre, pre div {
background: #1a1a1a !important;
}
@ -113,3 +126,24 @@
word-break: break-word;
}
}
.bing {
display: flex;
flex-direction: row;
align-items: center;
vertical-align: center;
gap: 6px;
color: #2f7eee;
background: #e8f2ff;
border-radius: 12px;
padding: 6px 12px;
font-size: 16px;
margin: 6px 0;
width: max-content;
user-select: none;
svg {
width: 20px;
height: 20px;
}
}

View File

@ -10,6 +10,7 @@
.sidebar {
display: flex;
flex-shrink: 0;
flex-direction: column;
width: 0;
height: 100%;
@ -129,20 +130,24 @@
}
}
.refresh-action {
margin-left: auto;
margin-right: 6px;
.sidebar-action {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
padding: 0 6px;
&.active {
svg {
animation: RotateAnimation 0.5s linear infinite;
.refresh-action {
&.active {
svg {
animation: RotateAnimation 0.5s linear infinite;
@keyframes RotateAnimation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
@keyframes RotateAnimation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
}

View File

@ -29,11 +29,7 @@ function Markdown({ children, className }: MarkdownProps) {
lang={match[1]}
/>
) : (
<pre>
<div className={`code-block`}>
<code className={className} {...props}>{children}</code>
</div>
</pre>
<code className={`code-inline ${className}`} {...props}>{children}</code>
)
}}}
/>

View File

@ -1,6 +1,6 @@
import axios from "axios";
export const deploy: boolean = false;
export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094";
export let ws_api: string = "ws://localhost:8094";

View File

@ -54,7 +54,7 @@ export class Connection {
public send(data: Record<string, any>): boolean {
if (!this.state || !this.connection) {
console.debug("[connection] connection not ready");
console.debug("[connection] connection not ready, retrying in 500ms...");
return false;
}
this.connection.send(JSON.stringify(data));

View File

@ -25,6 +25,19 @@ export class Conversation {
this.end = true;
}
public setId(id: number): void {
this.id = id;
}
public copyMessages(): Message[] {
// deep copy: cannot use return [...this.data];
return this.data.map((item) => {
return {
...item,
}
});
}
public load(data: Message[]): void {
this.data = data;
this.idx = data.length - 1;
@ -51,7 +64,7 @@ export class Conversation {
}
public triggerCallback() {
this.callback && this.callback(this.id, this.data);
this.callback && this.callback(this.id, this.copyMessages());
}
public addMessage(message: Message): number {
@ -67,7 +80,7 @@ export class Conversation {
}
public updateMessage(idx: number, message: string, keyword?: string) {
this.data[idx].content = message;
this.data[idx].content += message;
if (keyword) this.data[idx].keyword = keyword;
this.triggerCallback();
}
@ -105,17 +118,20 @@ export class Conversation {
}
this.end = false;
this.connection.setCallback(this.useMessage()); // hook
this.connection.send(props);
this.connection.sendWithRetry(props);
}
public sendMessage(auth: boolean, props: SendMessageProps): void {
public sendMessage(auth: boolean, props: SendMessageProps): boolean {
if (!this.end) return false;
this.addMessage({
content: props.message,
role: "user",
});
return auth ?
auth ?
this.sendAuthenticated(props as AuthenticatedProps) :
this.sendAnonymous(props as AnonymousProps);
return true;
}
}

View File

@ -1,13 +1,13 @@
import {Conversation, SendMessageProps} from "./conversation";
import {ConversationMapper, Message} from "./types.ts";
import {loadConversation} from "./history.ts";
import {useSelector} from "react-redux";
import {removeHistory, selectGPT4, selectWeb, setCurrent, setMessages} from "../store/chat.ts";
import {selectAuthenticated} from "../store/auth.ts";
import {addHistory, removeHistory, setCurrent, setMessages} from "../store/chat.ts";
import {useShared} from "../utils.ts";
export class Manager {
conversations: Record<number, Conversation>;
current: number;
dispatch: any;
public constructor() {
this.conversations = {};
@ -15,8 +15,13 @@ export class Manager {
this.current = -1;
}
public setDispatch(dispatch: any): void {
this.dispatch = dispatch;
}
public callback(idx: number, message: Message[]): void {
console.debug(`[manager] conversation migrated (id: ${idx}, length: ${message.length})`);
console.debug(`[manager] conversation receive message (id: ${idx}, length: ${message.length})`);
this.dispatch(setMessages(message));
}
public getCurrent(): number {
@ -29,7 +34,6 @@ export class Manager {
public createConversation(id: number): Conversation {
console.debug(`[manager] create conversation instance (id: ${id})`);
if (this.conversations[id]) return this.conversations[id];
const _this = this;
return new Conversation(id, function (idx: number, message: Message[]) {
_this.callback(idx, message);
@ -48,7 +52,7 @@ export class Manager {
if (!this.conversations[id]) await this.add(id);
this.current = id;
dispatch(setCurrent(id));
dispatch(setMessages(this.get(id)!.data));
dispatch(setMessages(this.get(id)!.copyMessages()));
}
public async delete(dispatch: any, id: number): Promise<void> {
@ -57,13 +61,29 @@ export class Manager {
if (this.conversations[id]) delete this.conversations[id];
}
public async send(auth: boolean, props: SendMessageProps): Promise<void> {
public async send(auth: boolean, props: SendMessageProps): Promise<boolean> {
const id = this.getCurrent();
if (!this.conversations[id]) return;
if (!this.conversations[id]) return false;
console.debug(`[chat] trigger send event: ${props.message} (type: ${auth ? 'user' : 'anonymous'}, id: ${id})`);
if (id === -1 && auth) {
// check for raise conversation
console.debug(`[manager] raise new conversation (name: ${props.message})`);
const { hook, useHook } = useShared<number>();
this.dispatch(addHistory({
message: props.message,
hook,
}));
const target = await useHook();
this.conversations[target] = this.conversations[-1];
this.get(target)!.setId(target);
delete this.conversations[-1]; // fix pointer
this.conversations[-1] = this.createConversation(-1);
this.current = target;
return this.get(target)!.sendMessage(auth, props);
}
const conversation = this.get(id);
if (!conversation) return;
conversation.sendMessage(auth, props);
if (!conversation) return false;
return conversation.sendMessage(auth, props);
}
public get(id: number): Conversation | undefined {

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
import {createSlice} from "@reduxjs/toolkit";
import {ConversationInstance} from "../conversation/types.ts";
import {Message} from "postcss";
import {insertStart} from "../utils.ts";
type initialStateType = {
history: ConversationInstance[];
@ -26,6 +27,13 @@ const chatSlice = createSlice({
removeHistory: (state, action) => {
state.history = state.history.filter((item) => item.id !== (action.payload as number));
},
addHistory: (state, action) => {
const name = action.payload.message as string;
const id = Math.max(...state.history.map((item) => item.id)) + 1;
state.history = insertStart(state.history, {id, name, message: []});
state.current = id;
action.payload.hook(id);
},
setMessages: (state, action) => {
state.messages = action.payload as Message[];
},
@ -47,7 +55,7 @@ const chatSlice = createSlice({
}
});
export const {setHistory, removeHistory, setCurrent, setMessages, setGPT4, setWeb, addMessage, setMessage} = chatSlice.actions;
export const {setHistory, removeHistory, addHistory, setCurrent, setMessages, setGPT4, setWeb, addMessage, setMessage} = chatSlice.actions;
export const selectHistory = (state: any) => state.chat.history;
export const selectMessages = (state: any) => state.chat.messages;
export const selectGPT4 = (state: any) => state.chat.gpt4;

View File

@ -22,6 +22,47 @@ export function useAnimation(ref: React.MutableRefObject<any>, cls: string, min?
}
}
export function useShared<T>(): { hook: (v: T) => void, useHook: () => Promise<T> } {
let value: T | undefined = undefined;
return {
hook: (v: T) => {
value = v;
},
useHook: () => {
return new Promise<T>((resolve) => {
if (value) return resolve(value);
const interval = setInterval(() => {
if (value) {
clearInterval(interval);
resolve(value);
}
}, 50);
});
}
}
}
export function insert<T>(arr: T[], idx: number, value: T): T[] {
return [...arr.slice(0, idx), value, ...arr.slice(idx)];
}
export function insertStart<T>(arr: T[], value: T): T[] {
return [value, ...arr];
}
export function remove<T>(arr: T[], idx: number): T[] {
return [...arr.slice(0, idx), ...arr.slice(idx + 1)];
}
export function replace<T>(arr: T[], idx: number, value: T): T[] {
return [...arr.slice(0, idx), value, ...arr.slice(idx + 1)];
}
export function move<T>(arr: T[], from: number, to: number): T[] {
const value = arr[from];
return insert(remove(arr, from), to, value);
}
window.addEventListener("resize", () => {
mobile = (window.innerWidth <= 468 || window.innerHeight <= 468 || navigator.userAgent.includes("Mobile"));
});