generated from template/astro-template
	update add mark
This commit is contained in:
		@@ -36,6 +36,7 @@
 | 
			
		||||
    "clsx": "^2.1.1",
 | 
			
		||||
    "cmdk": "^1.1.1",
 | 
			
		||||
    "dayjs": "^1.11.13",
 | 
			
		||||
    "fuse.js": "^7.1.0",
 | 
			
		||||
    "highlight.js": "^11.11.1",
 | 
			
		||||
    "i18next": "^25.2.0",
 | 
			
		||||
    "i18next-browser-languagedetector": "^8.1.0",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -65,6 +65,9 @@ importers:
 | 
			
		||||
      dayjs:
 | 
			
		||||
        specifier: ^1.11.13
 | 
			
		||||
        version: 1.11.13
 | 
			
		||||
      fuse.js:
 | 
			
		||||
        specifier: ^7.1.0
 | 
			
		||||
        version: 7.1.0
 | 
			
		||||
      highlight.js:
 | 
			
		||||
        specifier: ^11.11.1
 | 
			
		||||
        version: 11.11.1
 | 
			
		||||
@@ -2069,6 +2072,10 @@ packages:
 | 
			
		||||
  function-bind@1.1.2:
 | 
			
		||||
    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
 | 
			
		||||
 | 
			
		||||
  fuse.js@7.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==}
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
 | 
			
		||||
  gensync@1.0.0-beta.2:
 | 
			
		||||
    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
 | 
			
		||||
    engines: {node: '>=6.9.0'}
 | 
			
		||||
@@ -5342,6 +5349,8 @@ snapshots:
 | 
			
		||||
 | 
			
		||||
  function-bind@1.1.2: {}
 | 
			
		||||
 | 
			
		||||
  fuse.js@7.1.0: {}
 | 
			
		||||
 | 
			
		||||
  gensync@1.0.0-beta.2: {}
 | 
			
		||||
 | 
			
		||||
  get-east-asian-width@1.3.0: {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
};
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										112
									
								
								src/apps/ai-mark/index.tsx
									
									
									
									
									
										Normal 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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								src/apps/assistant-home/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/apps/assistant-home/index.tsx
									
									
									
									
									
										Normal 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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										11
									
								
								src/data/blogs/logger-life/0003-ai-token.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/data/blogs/logger-life/0003-ai-token.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
---
 | 
			
		||||
share: private
 | 
			
		||||
title: "AI Token 记录"
 | 
			
		||||
date: 2025-05-25 01:30:00
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
当使用 AI 的时候,如果需要做什么任务的话,其实感觉 token 的消耗数是非常的大的。如果想要消耗的不那么大,就必须要精致化的功能模块,代码就必须要细致。
 | 
			
		||||
 | 
			
		||||
比如指令和任务匹配,比如文本需要抽取。
 | 
			
		||||
 | 
			
		||||
为什么我会觉得消耗大呢,假如用户有一个 agent 任务,首先要分析用户的意图,要把所有要调用的函数都列出来给到ai,当ai返回结果的时候,还要把结果返回给用户,用户调用对应的函数,生成的结果又给到ai,ai再返回给用户。在n多次的循环中,就有很多的消耗。
 | 
			
		||||
@@ -19,3 +19,8 @@ export const randomId = (number: number) => {
 | 
			
		||||
  const _letter = uuid(1);
 | 
			
		||||
  return `${_letter}${nanoid(number)}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const randomLetter = (number: number = 8, opts?: { before?: string; after?: string }) => {
 | 
			
		||||
  const { before = '', after = '' } = opts || {};
 | 
			
		||||
  return `${before}${uuid(number)}${after}`;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,30 @@
 | 
			
		||||
import { toast } from 'react-toastify';
 | 
			
		||||
import { Query, ClientQuery } from '@kevisual/query/query';
 | 
			
		||||
 | 
			
		||||
import { QueryLoginBrowser } from '@/query/query-login/query-login-browser';
 | 
			
		||||
import { toastLogin } from './toast/ToastLogin';
 | 
			
		||||
export const query = new Query();
 | 
			
		||||
 | 
			
		||||
export const clientQuery = new ClientQuery();
 | 
			
		||||
 | 
			
		||||
export const queryLogin = new QueryLoginBrowser({
 | 
			
		||||
  query: query as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
query.beforeRequest = queryLogin.beforeRequest.bind(queryLogin);
 | 
			
		||||
query.afterResponse = async (res, ctx) => {
 | 
			
		||||
  const newRes = await queryLogin.run401Action(res, ctx, {
 | 
			
		||||
    afterAlso401: () => {
 | 
			
		||||
      toastLogin();
 | 
			
		||||
    },
 | 
			
		||||
    afterCheck: (res) => {
 | 
			
		||||
      console.log('afterCheck', res);
 | 
			
		||||
      if (res.code === 200) {
 | 
			
		||||
        toast.success('刷新登陆信息');
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          window.location.reload();
 | 
			
		||||
        }, 1000);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  return newRes as any;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
---
 | 
			
		||||
import '../styles/global.css';
 | 
			
		||||
import '@/styles/global.css';
 | 
			
		||||
import Blank from '@/components/html/blank.astro';
 | 
			
		||||
import { App as AIChat } from '@/apps/ai-chat/index.tsx';
 | 
			
		||||
import { App as AssistantHome } from '@/apps/assistant-home/index.tsx';
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<Blank>
 | 
			
		||||
  <AIChat client:only />
 | 
			
		||||
  <AssistantHome client:only />
 | 
			
		||||
</Blank>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								src/pages/mark/ai-chat.astro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/pages/mark/ai-chat.astro
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
---
 | 
			
		||||
import '@/styles/global.css';
 | 
			
		||||
import Blank from '@/components/html/blank.astro';
 | 
			
		||||
import { App as AIChat } from '@/apps/ai-chat/index.tsx';
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<Blank>
 | 
			
		||||
  <AIChat client:only />
 | 
			
		||||
</Blank>
 | 
			
		||||
							
								
								
									
										9
									
								
								src/pages/mark/ai-mark.astro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/pages/mark/ai-mark.astro
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
---
 | 
			
		||||
import '@/styles/global.css';
 | 
			
		||||
import Blank from '@/components/html/blank.astro';
 | 
			
		||||
import { App } from '@/apps/ai-mark';
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<Blank>
 | 
			
		||||
  <App client:only />
 | 
			
		||||
</Blank>
 | 
			
		||||
							
								
								
									
										14
									
								
								src/pages/test/2025/01-get-query.astro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/pages/test/2025/01-get-query.astro
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
---
 | 
			
		||||
import '@/styles/global.css';
 | 
			
		||||
import Blank from '@/components/html/blank.astro';
 | 
			
		||||
import { QueryMark } from '@/query/query-mark/query-mark';
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<Blank >
 | 
			
		||||
  <div>
 | 
			
		||||
    <h1>Get Query</h1>
 | 
			
		||||
    <script type='module'>
 | 
			
		||||
      console.log('load');
 | 
			
		||||
    </script>
 | 
			
		||||
  </div>
 | 
			
		||||
</Blank>
 | 
			
		||||
@@ -12,6 +12,27 @@ export type PostChat = {
 | 
			
		||||
  user?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const appDefine = QueryUtil.create({
 | 
			
		||||
  chat: {
 | 
			
		||||
    path: 'ai',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { appDefine } from './defines/ai.ts';
 | 
			
		||||
import { PostChat } from './defines/ai.ts';
 | 
			
		||||
import { PostChat, ChatOpts, ChatDataOpts } from './defines/ai.ts';
 | 
			
		||||
 | 
			
		||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
 | 
			
		||||
 | 
			
		||||
@@ -22,4 +22,80 @@ export class QueryApp<T extends Query = Query> extends BaseQuery<T, typeof appDe
 | 
			
		||||
  postChat(data: PostChat, opts?: DataOpts) {
 | 
			
		||||
    return this.chain('chat').post(data, 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,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ export class QueryLogin extends BaseQuery {
 | 
			
		||||
    super({
 | 
			
		||||
      query: opts?.query || new Query(),
 | 
			
		||||
    });
 | 
			
		||||
    this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts.cache });
 | 
			
		||||
    this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
 | 
			
		||||
    this.isBrowser = opts?.isBrowser ?? true;
 | 
			
		||||
    this.init();
 | 
			
		||||
    this.onLoad = opts?.onLoad;
 | 
			
		||||
@@ -271,7 +271,7 @@ export class QueryLogin extends BaseQuery {
 | 
			
		||||
            config.headers['Authorization'] = `Bearer ${_token}`;
 | 
			
		||||
          }
 | 
			
		||||
          if (!_token) {
 | 
			
		||||
            // TODO: 取消请求,因为没有登陆
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
          return config;
 | 
			
		||||
        },
 | 
			
		||||
@@ -303,6 +303,21 @@ export class QueryLogin extends BaseQuery {
 | 
			
		||||
    const token = this.storage.getItem('token');
 | 
			
		||||
    return !!token;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * 检查本地用户列表
 | 
			
		||||
   * @returns
 | 
			
		||||
   */
 | 
			
		||||
  async getToken() {
 | 
			
		||||
    const token = this.storage.getItem('token');
 | 
			
		||||
    return token || '';
 | 
			
		||||
  }
 | 
			
		||||
  async beforeRequest(opts: any = {}) {
 | 
			
		||||
    const token = this.storage.getItem('token');
 | 
			
		||||
    if (token) {
 | 
			
		||||
      opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
 | 
			
		||||
    }
 | 
			
		||||
    return opts;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * 请求更新,切换用户, 使用switchUser
 | 
			
		||||
   * @param username
 | 
			
		||||
@@ -318,7 +333,7 @@ export class QueryLogin extends BaseQuery {
 | 
			
		||||
   */
 | 
			
		||||
  async switchUser(username: string) {
 | 
			
		||||
    const localUserList = await this.cacheStore.getCurrentUserList();
 | 
			
		||||
    const user = localUserList.find((userItem) => userItem.user.username === username);
 | 
			
		||||
    const user = localUserList.find((userItem) => userItem.user!.username === username);
 | 
			
		||||
    if (user) {
 | 
			
		||||
      this.storage.setItem('token', user.accessToken || '');
 | 
			
		||||
      await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ export class QueryMark extends QueryMarkBase<SimpleObject> {
 | 
			
		||||
    this.markType = opts?.markType || 'simple';
 | 
			
		||||
  }
 | 
			
		||||
  async getMarkList(search?: SearchOpts, opts?: DataOpts) {
 | 
			
		||||
    return this.post({ key: 'list', ...search, markType: this.markType }, opts);
 | 
			
		||||
    return this.post<Result<ResultMarkList>>({ key: 'list', ...search, markType: this.markType }, opts);
 | 
			
		||||
  }
 | 
			
		||||
  async updateMark(data: any, opts?: DataOpts) {
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user