mirror of
https://github.com/coaidev/coai.git
synced 2025-05-30 02:10:25 +09:00
fix redux dispatch context error
This commit is contained in:
parent
7ab622c6e9
commit
9b4155cf75
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}}}
|
||||
/>
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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"));
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user