udpate
This commit is contained in:
15
src/apps/home/chat.ts
Normal file
15
src/apps/home/chat.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { query } from '@/modules/query';
|
||||
import { toast } from 'react-toastify';
|
||||
export const postChat = async (question: string) => {
|
||||
const res = await query.post({
|
||||
path: 'noco-life',
|
||||
key: 'chat',
|
||||
payload: { question },
|
||||
});
|
||||
if (res.code === 200) {
|
||||
return res.data?.content;
|
||||
} else {
|
||||
toast.error(res.message || 'Failed to get chat response');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -2,9 +2,12 @@ import { app } from '../ai';
|
||||
|
||||
|
||||
import { Sender, XProvider } from '@ant-design/x';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { postChat } from './chat';
|
||||
import { Nav } from '../nav';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useHomeStore } from './store';
|
||||
|
||||
const useFocus = () => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -31,17 +34,29 @@ const useFocus = () => {
|
||||
return inputRef;
|
||||
}
|
||||
|
||||
|
||||
export const App = () => {
|
||||
const inputRef = useFocus();
|
||||
const [content, setContent] = useState<string>('');
|
||||
const { inputValue, setInputValue, isLoading, setLoading } = useHomeStore();
|
||||
|
||||
|
||||
|
||||
return <div className='container mx-auto p-4'>
|
||||
<div className='fixed bottom-8 w-1/2 justify-self-center' ref={inputRef}>
|
||||
<Sender allowSpeech onSubmit={() => {
|
||||
console.log('Submitted');
|
||||
}} />
|
||||
return <div className='container mx-auto px-4 py-4 md:p-4'>
|
||||
<div className='md:top-20 left-0 mb-4 right-0 w-full mx-auto px-4 md:px-0' ref={inputRef}>
|
||||
<Sender
|
||||
allowSpeech
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
loading={isLoading}
|
||||
onSubmit={async (message) => {
|
||||
console.log('Submitted', message);
|
||||
setLoading(true);
|
||||
const res = await postChat(message);
|
||||
setContent(res || ' ');
|
||||
setLoading(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='mb-20 md:mb-16 px-2 md:px-0'>
|
||||
{content}
|
||||
</div>
|
||||
</div >;
|
||||
}
|
||||
@@ -66,5 +81,6 @@ export const AppProvider = () => {
|
||||
>
|
||||
<Nav />
|
||||
<App />
|
||||
<ToastContainer />
|
||||
</XProvider>;
|
||||
}
|
||||
27
src/apps/home/store.ts
Normal file
27
src/apps/home/store.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @title Home Store
|
||||
* @description 管理 home 页面的输入框数据和加载状态
|
||||
* @tags zustand, state-management, input, loading
|
||||
* @createdAt 2025-12-04
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface HomeState {
|
||||
// 输入框内容
|
||||
inputValue: string;
|
||||
// 加载状态
|
||||
isLoading: boolean;
|
||||
|
||||
// Actions
|
||||
setInputValue: (value: string) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
export const useHomeStore = create<HomeState>((set) => ({
|
||||
inputValue: '',
|
||||
isLoading: false,
|
||||
|
||||
setInputValue: (value) => set({ inputValue: value }),
|
||||
setLoading: (loading) => set({ isLoading: loading }),
|
||||
}));
|
||||
@@ -12,6 +12,7 @@ export const LoginComponent = ({ onLoginSuccess }: { onLoginSuccess: () => void
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('监听到登录成功事件,关闭弹窗');
|
||||
onLoginSuccess();
|
||||
|
||||
};
|
||||
const loginEmitter = useContextKey('login-emitter')
|
||||
console.log('KvLogin Types:', loginEmitter);
|
||||
@@ -28,23 +29,24 @@ export const LoginComponent = ({ onLoginSuccess }: { onLoginSuccess: () => void
|
||||
return (<kv-login><div id="weixinLogin"></div></kv-login>)
|
||||
}
|
||||
export const Nav = () => {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const store = useUserStore(useShallow((state) => ({
|
||||
user: state.user,
|
||||
open: state.open,
|
||||
setOpen: state.setOpen,
|
||||
setUser: state.setUser,
|
||||
clearUser: state.clearUser,
|
||||
queryUser: state.queryUser
|
||||
queryUser: state.queryUser,
|
||||
init: state.init,
|
||||
})));
|
||||
|
||||
useEffect(() => {
|
||||
store.queryUser();
|
||||
store.init();
|
||||
}, []);
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
// 关闭弹窗
|
||||
setIsDialogOpen(false);
|
||||
// 重新查询用户信息
|
||||
store.setOpen(false);
|
||||
store.queryUser();
|
||||
};
|
||||
return <header>
|
||||
<nav className="bg-black p-4 text-white flex justify-between">
|
||||
@@ -62,7 +64,7 @@ export const Nav = () => {
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<Dialog open={store.open} onOpenChange={store.setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<button className="bg-gray-700 text-white px-3 py-1 rounded hover:bg-gray-600 transition-colors">
|
||||
登录
|
||||
|
||||
@@ -11,23 +11,50 @@ interface UserState {
|
||||
type?: string;
|
||||
username?: string;
|
||||
} | null;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
setUser: (user: UserState['user']) => void;
|
||||
clearUser: () => void;
|
||||
clearUser: () => Promise<void>;
|
||||
queryUser: () => void;
|
||||
queryMe: () => void;
|
||||
queryMe: (token?: string) => void;
|
||||
init: () => void;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserState>((set) => ({
|
||||
export const useUserStore = create<UserState>((set, get) => ({
|
||||
user: null,
|
||||
open: false,
|
||||
setOpen: (open) => set({ open }),
|
||||
setUser: (user) => set({ user }),
|
||||
clearUser: () => set({ user: null }),
|
||||
clearUser: async () => {
|
||||
await queryLogin.logout()
|
||||
set({ user: null });
|
||||
},
|
||||
queryUser: async () => {
|
||||
const user = await queryLogin.checkLocalUser();
|
||||
set({ user });
|
||||
console.log('查询到的用户信息:', user);
|
||||
if (!user) {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
get().queryMe(token);
|
||||
}
|
||||
} else {
|
||||
set({ user });
|
||||
}
|
||||
},
|
||||
queryMe: async () => {
|
||||
const user = await queryLogin.getMe();
|
||||
set({ user });
|
||||
queryMe: async (token?: string) => {
|
||||
const res = await queryLogin.getMe(token);
|
||||
console.log('获取到的用户信息:', res);
|
||||
if (res.code === 200) {
|
||||
set({ user: res.data || null });
|
||||
}
|
||||
},
|
||||
init: () => {
|
||||
const url = new URL(window.location.href);
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
if (code && state) {
|
||||
set({ open: true })
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Query } from "@kevisual/query";
|
||||
import { QueryClient } from "@kevisual/query";
|
||||
import { QueryLoginBrowser } from '@kevisual/query-login';
|
||||
|
||||
export const query = new Query();
|
||||
export const query = new QueryClient();
|
||||
|
||||
export const queryLogin = new QueryLoginBrowser({
|
||||
query
|
||||
})
|
||||
export const local = new Query({
|
||||
export const local = new QueryClient({
|
||||
url: '/client/router'
|
||||
});
|
||||
Reference in New Issue
Block a user