feat: add ai-html

This commit is contained in:
2025-06-02 13:22:04 +08:00
parent a43cfb4b5f
commit a309faead0
46 changed files with 11067 additions and 416 deletions

View File

@@ -0,0 +1,215 @@
import { StoreManager } from '@kevisual/store';
import { useContextKey } from '@kevisual/store/context';
import { StateCreator, StoreApi, UseBoundStore } from 'zustand';
import { queryMark } from '../modules/query';
import { useStore, BoundStore } from '@kevisual/store/react';
import { createStore, set as setCache, get as getCache } from 'idb-keyval';
import { OrderedExcalidrawElement } from '@excalidraw/excalidraw/element/types';
import { toast } from 'react-toastify';
import { BinaryFileData, ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types';
export const cacheStore = createStore('excalidraw-store', 'excalidraw');
export const store = useContextKey('store', () => {
return new StoreManager();
});
type MarkStore = {
id: string;
setId: (id: string) => void;
mark: any;
setMark: (mark: any) => void;
info: any;
setInfo: (info: any) => void;
getList: () => Promise<void>;
list: any[];
setList: (list: any[]) => void;
getMark: (markId: string) => Promise<void>;
updateMark: () => Promise<void>;
getCache: (
id: string,
updateApiData?: boolean,
) => Promise<
| {
elements: OrderedExcalidrawElement[];
filesObject: Record<string, any>;
}
| undefined
>;
setCache: (cache: any, version?: number) => Promise<void>;
loading: boolean;
setLoading: (loading: boolean) => void;
// excalidraw
api: ExcalidrawImperativeAPI | null;
setApi: (api: ExcalidrawImperativeAPI) => void;
};
export const createMarkStore: StateCreator<MarkStore, [], [], MarkStore> = (set, get, store) => {
return {
id: '',
setId: (id: string) => set(() => ({ id })),
mark: null,
setMark: (mark: any) => set(() => ({ mark })),
loading: true,
setLoading: (loading: boolean) => set(() => ({ loading })),
info: null,
setCache: async (cache: any, version?: number) => {
const { id, mark } = get();
console.log('cacheData setCache ,id', cache, id);
if (!id) {
return;
}
const cacheData = (await getCache(`${id}`, cacheStore)) || {};
await setCache(
`${id}`,
{
...cacheData,
...cache,
data: {
...cacheData?.data,
...cache?.data,
},
version: version || mark?.version || 0,
},
cacheStore,
);
},
updateMark: async () => {
const { id } = get();
if (!id) {
return;
}
const cacheData = await getCache(id, cacheStore);
let mark = cacheData || {};
if (!mark) {
return;
}
const { data } = mark;
const { elements, filesObject } = data;
console.log('updateMark', elements, filesObject);
const res = await queryMark.updateMark({ id, data });
if (res.code === 200) {
set(() => ({ mark: res.data }));
toast.success('更新成功');
get().setCache({}, res.data!.version);
}
},
getCache: async (id: string, updateApiData?: boolean) => {
if (!id) {
return;
}
// 获取缓存
let cacheData = (await getCache(`${id}`, cacheStore)) || { data: { elements: [], filesObject: {} } };
console.log('getCache', id, cacheData);
if (cacheData) {
if (updateApiData) {
const api = get().api;
if (api) {
const files = Object.values(cacheData.data.filesObject || {}) as BinaryFileData[];
api.addFiles(files || []);
api.updateScene({
elements: [...(cacheData.data?.elements || [])],
appState: {},
});
}
}
}
return {
elements: cacheData.data.elements || [],
filesObject: cacheData.data.filesObject || {},
};
},
setInfo: (info: any) => set(() => ({ info })),
getList: async () => {
const res = await queryMark.getMarkList({ page: 1, pageSize: 10 });
console.log(res);
},
list: [],
setList: (list: any[]) => set(() => ({ list })),
getMark: async (markId: string) => {
set(() => ({ loading: true, id: markId }));
const toastId = toast.loading(`获取数据中...`);
const now = new Date().getTime();
const cacheData = await getCache(markId, cacheStore);
const checkVersion = await queryMark.checkVersion(markId, cacheData?.version);
if (checkVersion) {
const res = await queryMark.getMark(markId);
if (res.code === 200) {
set(() => ({
mark: res.data,
id: markId,
}));
const mark = res.data!;
const excalidrawData = mark.data || {};
await get().setCache({
data: {
elements: excalidrawData.elements,
filesObject: excalidrawData.filesObject,
},
version: mark.version,
});
get().getCache(markId, true);
} else {
toast.error(res.message || '获取数据失败');
}
}
const end = new Date().getTime();
const getTime = end - now;
if (getTime < 2 * 1000) {
await new Promise((resolve) => setTimeout(resolve, 2 * 1000 - getTime));
}
toast.dismiss(toastId);
set(() => ({ loading: false }));
},
api: null,
setApi: (api: ExcalidrawImperativeAPI) => set(() => ({ api })),
};
};
export const useMarkStore = useStore as BoundStore<MarkStore>;
export const fileDemo = {
abc: {
dataURL: 'https://kevisual.xiongxiao.me/root/center/panda.png' as any,
// @ts-ignore
id: 'abc',
name: 'test2.png',
type: 'image/png',
},
};
export const demoElements: OrderedExcalidrawElement[] = [
{
id: '1',
type: 'image',
x: 100,
y: 100,
width: 100,
height: 100,
fileId: 'abc' as any,
version: 2,
versionNonce: 28180243,
index: 'a0' as any,
isDeleted: false,
fillStyle: 'solid',
strokeWidth: 2,
strokeStyle: 'solid',
roughness: 1,
opacity: 100,
angle: 0,
strokeColor: '#1e1e1e',
backgroundColor: 'transparent',
seed: 1,
groupIds: [],
frameId: null,
roundness: null,
boundElements: [],
updated: 1743219351869,
link: null,
locked: false,
status: 'pending',
scale: [1, 1],
crop: null,
},
];