update add mark

This commit is contained in:
2025-05-26 02:57:33 +08:00
parent e38b8df9f0
commit 22cb48b9f3
17 changed files with 351 additions and 164 deletions

View File

@@ -1,153 +0,0 @@
import { query } from '@/modules/query';
import { Query } from '@kevisual/query';
import { DataOpts } from '@kevisual/query/query';
export class QueryChat {
query: Query;
constructor({ query }: { query: Query }) {
this.query = query;
}
getChatList(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-chat-list',
},
opts,
);
}
getChat(id: string, opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-chat',
data: {
id,
},
},
opts,
);
}
updateChat(data: any, opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'update-chat',
data,
},
opts,
);
}
deleteChat(id: string, opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'delete-chat',
data: {
id,
},
},
opts,
);
}
/**
* 获取模型列表
* @param opts
* @returns
*/
getModelList(data?: { usernames?: string[] }, opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-model-list',
data,
},
opts,
);
}
/**
* 聊天对话模型
* @param data
* @param chatOpts
* @param opts
* @returns
*/
chat(data: ChatDataOpts, chatOpts: ChatOpts, opts?: DataOpts) {
const { username, model, group, getFull = true } = chatOpts;
if (!username || !model || !group) {
throw new Error('username, model, group is required');
}
return this.query.post(
{
path: 'ai',
key: 'chat',
...chatOpts,
getFull,
data,
},
opts,
);
}
clearConfigCache(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'clear-cache',
},
opts,
);
}
/**
* 获取聊天使用情况
* @param opts
* @returns
*/
getChatUsage(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-chat-usage',
},
opts,
);
}
/**
* 清除当前用户模型自己的统计
* @param opts
* @returns
*/
clearSelfUsage(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'clear-chat-limit',
},
opts,
);
}
}
export type ChatDataOpts = {
id?: string;
title?: string;
messages?: any[];
data?: any;
type?: 'temp' | 'keep' | string;
};
export type ChatOpts = {
username: string;
model: string;
/**
* 获取完整消息回复
*/
getFull?: boolean;
group: string;
/**
* openai的参数
*/
options?: any;
};

View File

@@ -5,11 +5,11 @@ import { query } from '@/modules/query';
import { useStore, BoundStore } from '@kevisual/store/react';
import { toast } from 'react-toastify';
import { ChastHistoryMessage } from './type';
import { QueryChat } from '../query/chat';
import { CacheStore } from '@kevisual/cache/cache-store';
import { QueryMark } from '@/query/query-mark/query-mark';
import { QueryApp as QueryAi } from '@/query/query-ai/query-ai';
export const queryMark = new QueryMark({ query: query, markType: 'chat' });
export const queryChat = new QueryChat({ query });
export const queryChat = new QueryAi({ query });
const dbName = 'chat';
export const store = useContextKey('store', () => {

112
src/apps/ai-mark/index.tsx Normal file
View File

@@ -0,0 +1,112 @@
import { query } from '@/modules/query';
import { QueryMark, Mark } from '@/query/query-mark/query-mark';
import { useEffect, useRef, useState, useCallback } from 'react';
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';
export const App = () => {
const [list, setList] = useState<Mark[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState<Mark[]>([]);
const fuseRef = useRef<Fuse<Mark>>(null);
// 实际执行搜索的函数
const performSearch = (term: string) => {
if (!term.trim()) {
setSearchResults([]);
return;
}
if (fuseRef.current) {
const results = fuseRef.current.search(term);
setSearchResults(results.map((result) => result.item));
}
};
// 使用 useRef 和 useCallback 创建防抖搜索函数,确保它在组件生命周期中只创建一次
const debouncedSearchRef = useRef(
debounce((term: string) => {
performSearch(term);
}, 1000), // 300毫秒的防抖延迟
);
useEffect(() => {
init();
// 组件卸载时取消待执行的防抖函数
return () => {
debouncedSearchRef.current.cancel();
};
}, []);
const init = async () => {
const res = await queryMark.getMarkList();
if (res.code === 200) {
const list = res.data?.list || [];
setList(list!);
const fuse = new Fuse(list, {
keys: ['title', 'description', 'tags', 'summary'],
});
fuseRef.current = fuse;
const result = fuse.search('苏生不惑的博客');
console.log(result);
}
};
const handleSearch = (term: string) => {
setSearchTerm(term); // 立即更新输入框的值
debouncedSearchRef.current(term); // 使用防抖函数进行搜索
};
const onOpen = (item: Mark) => {
const link = item.link;
window.open(link);
};
// 确定要显示的列表:有搜索内容时显示搜索结果,否则显示全部
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'>
<input
type='text'
value={searchTerm}
onChange={(e) => handleSearch(e.target.value)}
placeholder='搜索书签...'
className='w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300'
/>
</div>
<div className='p-4'>
{displayList.length === 0 && searchTerm.trim() ? (
<div className='text-center text-gray-500 my-8'></div>
) : (
displayList.map((item) => {
return (
<div key={item.id} className='p-4 mb-3 border rounded-md hover:shadow-md transition-shadow' title={item.link}>
<Tooltip title={item.link}>
<h3 className='text-lg font-semibold mb-1 flex gap-2 items-center'>
{item.title || '-'}
<SquareArrowOutUpRight className='h-5 w-5 cursor-pointer' onClick={() => onOpen(item)} />
</h3>
</Tooltip>
<p className='text-sm text-gray-600 mb-2'>{item.summary}</p>
<div className='flex flex-wrap gap-1'>
{item.tags.map((tag, index) => (
<span key={index} className='px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-md'>
{tag}
</span>
))}
</div>
</div>
);
})
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,33 @@
import { Button } from '@/components/a/button.tsx';
import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts';
import { Select } from '@/components/a/select.tsx';
import { useEffect, useRef, useState } from 'react';
import { getSuggestionItems } from '../ai-chat/editor/suggestion/item';
import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts';
import { chatId } from '../ai-chat/utils/uuid';
import '../ai-chat/index.css';
export const App = (props: any) => {
const ref = useRef<HTMLDivElement>(null);
const editorRef = useRef<TextEditor | null>(null);
useEffect(() => {
if (ref.current) {
editorRef.current = new TextEditor();
editorRef.current.createEditor(ref.current, {
items: getSuggestionItems(),
});
}
return () => {
if (ref.current && editorRef.current) {
editorRef.current?.destroy?.();
}
};
}, []);
return (
<div className='w-full h-full flex flex-col'>
<div className='w-[600px] h-[400px] border border-gray-300 flex flex-col mx-auto'>
<div className='w-full scrollbar' style={{ height: 'calc(100% - 90px)' }} ref={ref}></div>
</div>
<Button></Button>
</div>
);
};