This commit is contained in:
2025-05-28 19:39:50 +08:00
parent 22cb48b9f3
commit 26be3b85e8
24 changed files with 356 additions and 301 deletions

View File

@@ -1,4 +1,4 @@
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
import { useChatStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { Button } from '@/components/a/button.tsx';
@@ -68,6 +68,7 @@ export const ChatContextDialog = () => {
setShowContext(false);
};
const [role, setRole] = useState<string>('user');
const dragSize = useDragSize();
return (
<DragModal
focus={modelId === 'chat-context'}
@@ -111,14 +112,8 @@ export const ChatContextDialog = () => {
</div>
</div>
}
defaultSize={{
width: 600,
height: 400,
}}
style={{
left: computedHeight.width / 2 - 300,
top: computedHeight.height / 2 - 200,
}}
defaultSize={dragSize.defaultSize}
style={dragSize.style}
/>
);
};

View File

@@ -1,4 +1,4 @@
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
import { useChatStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { ChatCopyList } from './List';
@@ -16,6 +16,7 @@ export const ChatCopyDialog = () => {
if (!store.showCopy) {
return null;
}
const dragSize = useDragSize();
return (
<DragModal
focus={store.modelId === 'chat-copy'}
@@ -33,14 +34,8 @@ export const ChatCopyDialog = () => {
<ChatCopyList />
</div>
}
defaultSize={{
width: 600,
height: 400,
}}
style={{
left: computedHeight.width / 2 - 300,
top: computedHeight.height / 2 - 200,
}}
defaultSize={dragSize.defaultSize}
style={dragSize.style}
/>
);
};

View File

@@ -1,16 +1,17 @@
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
import { useChatStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { ChatHistoryList } from './List';
export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => {
const { showList, setShowList, setModelId, modelId } = useChatStore(
useShallow((state) => ({ showList: state.showList, setShowList: state.setShowList, setModelId: state.setModelId, modelId: state.modelId })),
useShallow((state) => ({ showList: state.showList, setShowList: state.setShowList, setModelId: state.setModelId, modelId: state.modelId })),
);
const computedHeight = getComputedHeight();
if (!showList) {
return null;
}
const dragSize = useDragSize();
return (
<DragModal
focus={modelId === 'chat-history'}
@@ -28,14 +29,7 @@ export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => {
<ChatHistoryList storeId={storeId} />
</div>
}
defaultSize={{
width: 600,
height: 400,
}}
style={{
left: computedHeight.width / 2 - 300,
top: computedHeight.height / 2 - 200,
}}
{...dragSize}
/>
);
};

View File

@@ -1,4 +1,4 @@
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
import { useChatStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { ChatSettingList } from './List';
@@ -16,6 +16,7 @@ export const ChatModelSettingDialog = () => {
if (!showChatSetting) {
return null;
}
const dragSize = useDragSize();
return (
<DragModal
focus={modelId === 'chat-model-setting'}
@@ -33,14 +34,7 @@ export const ChatModelSettingDialog = () => {
<ChatSettingList />
</div>
}
defaultSize={{
width: 600,
height: 400,
}}
style={{
left: computedHeight.width / 2 - 300,
top: computedHeight.height / 2 - 200,
}}
{...dragSize}
/>
);
};

View File

@@ -1,4 +1,4 @@
import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx';
import { DragModal, DragModalTitle, getComputedHeight, useDragSize } from '@/components/a/drag-modal/index.tsx';
import { useChatStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { ChatSettingList } from './List';
@@ -16,6 +16,7 @@ export const ChatSettingDialog = () => {
if (!showSetting) {
return null;
}
const dragSize = useDragSize();
return (
<DragModal
focus={modelId === 'chat-setting'}
@@ -33,14 +34,7 @@ export const ChatSettingDialog = () => {
<ChatSettingList />
</div>
}
defaultSize={{
width: 600,
height: 400,
}}
style={{
left: computedHeight.width / 2 - 300,
top: computedHeight.height / 2 - 200,
}}
{...dragSize}
/>
);
};

View File

@@ -75,7 +75,7 @@ export const Chat = ({ storeId }: { storeId: string }) => {
}, []);
return (
<div className='w-full h-full relative'>
<div className='w-full h-full relative flex flex-col lg:flex-row'>
{!id && (
<div className='w-full h-full flex flex-col justify-center items-center'>
<ChatHistoryList storeId={storeId} />

View File

@@ -41,7 +41,7 @@ export const ModelNav = () => {
return (
<div className='flex gap-2 items-center'>
<div className='hidden lg:block font-bold text-gray-600'></div>
<div className='flex gap-2 items-center' onClick={() => setShowChatSetting(true)}>
{/* <div className='flex gap-2 items-center' onClick={() => setShowChatSetting(true)}>
{currentUserModel && (
<Tooltip
title={
@@ -58,7 +58,7 @@ export const ModelNav = () => {
</Tooltip>
)}
{!currentUserModel && <div className='text-sm text-gray-500 cursor-pointer'>Not Selected Model</div>}
</div>
</div> */}
<div className='flex gap-2 items-center'>
{/* <Tooltip title='设置'>
<IconButton

View File

@@ -0,0 +1,18 @@
import { ToastProvider } from '@/modules/toast/Provider';
export const App = () => {
return (
<ToastProvider>
<AIEditor />
</ToastProvider>
);
};
export const AIEditor = () => {
return (
<div className='flex h-full w-full flex-col items-center justify-center'>
<div className='text-2xl font-bold'>AI Editor</div>
<div className='mt-4 text-gray-500'>This is a placeholder for the AI Editor application.</div>
</div>
);
};

View File

@@ -5,14 +5,35 @@ import { Tooltip } from '@/components/a/tooltip';
export const queryMark = new QueryMark({ query: query, markType: 'md' });
import Fuse from 'fuse.js';
import { debounce } from 'lodash-es';
import { SquareArrowOutUpRight } from 'lucide-react';
import { Plus, SquareArrowOutUpRight } from 'lucide-react';
import { Pencil, Trash2 } from 'lucide-react';
import { Button } from '@/components/a/button';
import { Confirm } from '@/components/a/confirm';
import { toast } from 'react-toastify';
import { ToastProvider } from '@/modules/toast/Provider';
import { Modal } from '@/components/a/modal';
import { useForm, Controller } from 'react-hook-form'; // 添加此行
export const App = () => {
return (
<ToastProvider>
<AiMark />
</ToastProvider>
);
};
const AiMark = () => {
const [list, setList] = useState<Mark[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState<Mark[]>([]);
const fuseRef = useRef<Fuse<Mark>>(null);
const [open, setOpen] = useState(false);
const [formData, setFormData] = useState<Mark>();
const onOpenEdit = (data: Mark) => {
setOpen(true);
setFormData(data);
};
// 实际执行搜索的函数
const performSearch = (term: string) => {
if (!term.trim()) {
@@ -47,6 +68,7 @@ export const App = () => {
if (res.code === 200) {
const list = res.data?.list || [];
setList(list!);
const fuse = new Fuse(list, {
keys: ['title', 'description', 'tags', 'summary'],
});
@@ -65,13 +87,23 @@ export const App = () => {
const link = item.link;
window.open(link);
};
const refresh = () => {
init();
};
// 确定要显示的列表:有搜索内容时显示搜索结果,否则显示全部
const displayList = searchTerm.trim() ? searchResults : list;
return (
<div className='w-full h-full overflow-auto'>
<div className='sticky top-0 p-4 bg-white z-10 shadow-sm'>
<div className='w-full h-full overflow-auto scrollbar'>
<div className='sticky top-0 p-4 bg-white z-10 shadow-sm flex gap-2'>
<Button
size='icon'
onClick={() => {
setFormData(undefined);
setOpen(true);
}}>
<Plus />
</Button>
<input
type='text'
value={searchTerm}
@@ -102,11 +134,141 @@ export const App = () => {
</span>
))}
</div>
<Action data={item} onEdit={onOpenEdit} refresh={refresh} />
</div>
);
})
)}
</div>
<Modal open={open} setOpen={setOpen}>
<MarkForm
data={formData}
onCancel={() => setOpen(false)}
onSuccess={() => {
setOpen(false);
init();
}}
/>
</Modal>
</div>
);
};
export const MarkForm = (props?: { data?: Mark; onSuccess?: () => void; onCancel?: () => void }) => {
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm<Mark>({
defaultValues: props?.data || {
title: '',
summary: '',
link: '',
tags: [],
},
});
const onSubmit = async (data: Mark) => {
try {
const res = await queryMark.updateMark(data);
if (res.code === 200) {
toast.success(data.id ? '更新成功' : '创建成功');
props?.onSuccess?.();
} else {
toast.error(res?.message || '操作失败');
}
} catch (error) {
toast.error('操作失败');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4 p-2'>
{props?.data?.id && <input type='hidden' {...register('id')} />}
<div>
<label className='block text-sm font-medium mb-1'></label>
<input
type='text'
{...register('title', { required: '标题是必填项' })}
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
/>
{errors.title && <p className='text-red-500 text-xs mt-1'>{errors.title.message}</p>}
</div>
<div>
<label className='block text-sm font-medium mb-1'></label>
<input
type='text'
{...register('link', { required: '链接是必填项' })}
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
/>
{errors.link && <p className='text-red-500 text-xs mt-1'>{errors.link.message}</p>}
</div>
<div>
<label className='block text-sm font-medium mb-1'></label>
<textarea {...register('summary')} rows={3} className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300' />
</div>
<div>
<label className='block text-sm font-medium mb-1'></label>
<Controller
name='tags'
control={control}
render={({ field }) => (
<input
type='text'
value={field.value?.join(', ') || ''}
onChange={(e) => {
const value = e.target.value;
field.onChange(
value
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag !== ''),
);
}}
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
placeholder='用逗号分隔多个标签'
/>
)}
/>
</div>
<div className='flex justify-end space-x-2 mt-4'>
<Button onClick={props?.onCancel} type='button'>
</Button>
<Button type='submit'></Button>
</div>
</form>
);
};
export const Action = (props: { data: Mark; onEdit?: (data: Mark) => any; refresh?: any }) => {
const onDelete = async () => {
const res = await queryMark.deleteMark(props.data.id);
if (res.code === 200) {
toast.success('删除成功');
props?.refresh?.();
} else {
toast.error(res?.message || '请求失败');
}
};
return (
<div className='flex gap-2 mt-2'>
<Button className='flex items-center gap-1 transition' onClick={() => props?.onEdit?.(props.data)} title='编辑'>
<Pencil className='w-4 h-4' />
</Button>
<Confirm onOk={onDelete}>
<Button className='flex items-center gap-1 text-red-600 transition' title='删除'>
<Trash2 className='w-4 h-4' />
</Button>
</Confirm>
</div>
);
};

View File

@@ -0,0 +1,13 @@
type LayoutProps = {
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
export const Layout = (props: LayoutProps) => {
const { children, className, style } = props;
return (
<div className={className} style={style}>
{children}
</div>
);
};