♻️ refactor: move utils to separate package (#8889)

* move utils

* move utils

* move utils

* update

* update

* update

* update

* update

* refactor to clean the tests

* fix release workflow

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* fix tests

* try to fix client db migration issue

* fix tests
This commit is contained in:
Arvin Xu
2025-08-22 14:05:01 +08:00
committed by GitHub
parent af1f71572f
commit 8dedc2d3e1
147 changed files with 247 additions and 176 deletions

View File

@@ -11,7 +11,7 @@ jobs:
services:
postgres:
image: pgvector/pgvector:pg17
image: paradedb/paradedb:latest
env:
POSTGRES_PASSWORD: postgres
options: >-
@@ -38,8 +38,10 @@ jobs:
- name: Lint
run: bun run lint
- name: Test Server Coverage
run: bun run test-server:coverage
- uses: pnpm/action-setup@v4
- name: Test Database Coverage
run: pnpm --filter @lobechat/database test
env:
DATABASE_TEST_URL: postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_DRIVER: node
@@ -48,8 +50,8 @@ jobs:
S3_PUBLIC_DOMAIN: https://example.com
APP_URL: https://home.com
- name: Test App Coverage
run: bun run test-app:coverage
- name: Test App
run: bun run test-app
- name: Release
run: bun run release

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
package: [file-loaders, prompts, model-runtime, web-crawler, electron-server-ipc]
package: [file-loaders, prompts, model-runtime, web-crawler, electron-server-ipc, utils]
name: Test package ${{ matrix.package }}

View File

@@ -104,6 +104,19 @@ table ai_providers {
}
}
table api_keys {
id integer [pk, not null]
name varchar(256) [not null]
key varchar(256) [not null, unique]
enabled boolean [default: true]
expires_at "timestamp with time zone"
last_used_at "timestamp with time zone"
user_id text [not null]
accessed_at "timestamp with time zone" [not null, default: `now()`]
created_at "timestamp with time zone" [not null, default: `now()`]
updated_at "timestamp with time zone" [not null, default: `now()`]
}
table async_tasks {
id uuid [pk, not null, default: `gen_random_uuid()`]
type text
@@ -702,6 +715,7 @@ table rbac_roles {
description text
is_system boolean [not null, default: false]
is_active boolean [not null, default: true]
metadata jsonb [default: `{}`]
accessed_at "timestamp with time zone" [not null, default: `now()`]
created_at "timestamp with time zone" [not null, default: `now()`]
updated_at "timestamp with time zone" [not null, default: `now()`]

View File

@@ -139,12 +139,14 @@
"@icons-pack/react-simple-icons": "9.6.0",
"@khmyznikov/pwa-install": "0.3.9",
"@langchain/community": "^0.3.50",
"@lobechat/const": "workspace:*",
"@lobechat/database": "workspace:*",
"@lobechat/electron-client-ipc": "workspace:*",
"@lobechat/electron-server-ipc": "workspace:*",
"@lobechat/file-loaders": "workspace:*",
"@lobechat/model-runtime": "workspace:*",
"@lobechat/prompts": "workspace:*",
"@lobechat/utils": "workspace:*",
"@lobechat/web-crawler": "workspace:*",
"@lobehub/analytics": "^1.6.0",
"@lobehub/charts": "^2.0.0",
@@ -360,8 +362,7 @@
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"vite": "^5.4.19",
"vitest": "^3.2.4",
"vitest-canvas-mock": "^0.3.3"
"vitest": "^3.2.4"
},
"packageManager": "pnpm@10.14.0",
"publishConfig": {

View File

@@ -1,2 +1,4 @@
export * from './locale';
export * from './message';
export * from './settings';
export * from './version';

View File

@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
import { ModelProviderCard } from '@/types/llm';
import { ModelProviderCard } from '@/types/index';
import { genUserLLMConfig } from './genUserLLMConfig';

View File

@@ -1,8 +1,8 @@
import { ModelProvider } from '@lobechat/model-runtime';
import { UserModelProviderConfig } from '@lobechat/types';
import * as ProviderCards from '@/config/modelProviders';
import { ModelProviderCard } from '@/types/llm';
import { UserModelProviderConfig } from '@/types/user/settings';
export const genUserLLMConfig = (specificConfig: Record<any, any>): UserModelProviderConfig => {
return Object.keys(ModelProvider).reduce((config, providerKey) => {

View File

@@ -9,8 +9,6 @@ import { DEFAULT_SYSTEM_AGENT_CONFIG } from './systemAgent';
import { DEFAULT_TOOL_CONFIG } from './tool';
import { DEFAULT_TTS_CONFIG } from './tts';
export const COOKIE_CACHE_DAYS = 30;
export * from './agent';
export * from './hotkey';
export * from './llm';

View File

@@ -1,6 +1,6 @@
import { ModelProvider } from '@lobechat/model-runtime';
import { genUserLLMConfig } from '@/utils/genUserLLMConfig';
import { genUserLLMConfig } from './genUserLLMConfig';
export const DEFAULT_LLM_CONFIG = genUserLLMConfig({
lmstudio: {

View File

@@ -1,4 +1,4 @@
CREATE TABLE "oauth_handoffs" (
CREATE TABLE IF NOT EXISTS "oauth_handoffs" (
"id" text PRIMARY KEY NOT NULL,
"client" varchar(50) NOT NULL,
"payload" jsonb NOT NULL,

View File

@@ -12,5 +12,5 @@ CREATE TABLE IF NOT EXISTS "api_keys" (
CONSTRAINT "api_keys_key_unique" UNIQUE("key")
);
--> statement-breakpoint
ALTER TABLE "rbac_roles" ADD COLUMN "metadata" jsonb DEFAULT '{}'::jsonb;--> statement-breakpoint
ALTER TABLE "rbac_roles" ADD COLUMN IF NOT EXISTS "metadata" jsonb DEFAULT '{}'::jsonb;--> statement-breakpoint
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;

View File

@@ -12,6 +12,7 @@
"@electric-sql/pglite": "^0.2.17",
"@lobechat/const": "workspace:*",
"@lobechat/types": "workspace:*",
"@lobechat/utils": "workspace:*",
"dayjs": "^1.11.13",
"drizzle-orm": "^0.44.4",
"nanoid": "^5.1.5",

View File

@@ -544,10 +544,20 @@
},
{
"sql": [
"CREATE TABLE \"oauth_handoffs\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"client\" varchar(50) NOT NULL,\n\t\"payload\" jsonb NOT NULL,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL\n);\n"
"CREATE TABLE IF NOT EXISTS \"oauth_handoffs\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"client\" varchar(50) NOT NULL,\n\t\"payload\" jsonb NOT NULL,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL\n);\n"
],
"bps": true,
"folderMillis": 1752567402506,
"hash": "8ba3ae52ed72e8aad1623dbcf47ca26a8406ebffc6d5284abff94ea994b59c04"
"hash": "83c410b18ef5c8667b4bdfd7880ef7db4c3278d826e6e87e9d3e05dde67fe8e1"
},
{
"sql": [
"CREATE TABLE IF NOT EXISTS \"api_keys\" (\n\t\"id\" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name \"api_keys_id_seq\" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t\"name\" varchar(256) NOT NULL,\n\t\"key\" varchar(256) NOT NULL,\n\t\"enabled\" boolean DEFAULT true,\n\t\"expires_at\" timestamp with time zone,\n\t\"last_used_at\" timestamp with time zone,\n\t\"user_id\" text NOT NULL,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\tCONSTRAINT \"api_keys_key_unique\" UNIQUE(\"key\")\n);\n",
"\nALTER TABLE \"rbac_roles\" ADD COLUMN IF NOT EXISTS \"metadata\" jsonb DEFAULT '{}'::jsonb;",
"\nALTER TABLE \"api_keys\" ADD CONSTRAINT \"api_keys_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;\n"
],
"bps": true,
"folderMillis": 1753201379817,
"hash": "fe5c0d7c2768189771c42ef93693fc1d58586b468c4bdde7fb6f2dc58cc9931c"
}
]

View File

@@ -23,7 +23,7 @@ describe('TableViewerRepo', () => {
it('should return all tables with counts', async () => {
const result = await repo.getAllTables();
expect(result.length).toEqual(59);
expect(result.length).toEqual(60);
expect(result[0]).toEqual({ name: 'agents', count: 0, type: 'BASE TABLE' });
});

View File

@@ -6,6 +6,8 @@ export default defineConfig({
alias: {
/* eslint-disable sort-keys-fix/sort-keys-fix */
'@/const': resolve(__dirname, '../const/src'),
'@/utils/errorResponse': resolve(__dirname, '../../src/utils/errorResponse'),
'@/utils': resolve(__dirname, '../utils/src'),
'@/database': resolve(__dirname, '../database/src'),
'@/types': resolve(__dirname, '../types/src'),
'@': resolve(__dirname, '../../src'),

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.862.0",
"@lobechat/types": "workspace:*",
"@lobechat/utils": "workspace:*",
"debug": "^4.4.1",
"openai": "^4.104.0"
}

View File

@@ -7,7 +7,7 @@ import { createBflImage } from './createImage';
import { BflStatusResponse } from './types';
// Mock external dependencies
vi.mock('@/utils/imageToBase64', () => ({
vi.mock('@lobechat/utils', () => ({
imageUrlToBase64: vi.fn(),
}));
@@ -188,7 +188,7 @@ describe('createBflImage', () => {
it('should convert single imageUrl to image_prompt base64', async () => {
// Arrange
const { parseDataUri } = await import('../utils/uriParser');
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
const { imageUrlToBase64 } = await import('@lobechat/utils');
const { asyncifyPolling } = await import('../utils/asyncifyPolling');
const mockParseDataUri = vi.mocked(parseDataUri);
@@ -291,7 +291,7 @@ describe('createBflImage', () => {
it('should convert multiple imageUrls for Kontext models', async () => {
// Arrange
const { parseDataUri } = await import('../utils/uriParser');
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
const { imageUrlToBase64 } = await import('@lobechat/utils');
const { asyncifyPolling } = await import('../utils/asyncifyPolling');
const mockParseDataUri = vi.mocked(parseDataUri);
@@ -351,7 +351,7 @@ describe('createBflImage', () => {
it('should limit imageUrls to maximum 4 images', async () => {
// Arrange
const { parseDataUri } = await import('../utils/uriParser');
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
const { imageUrlToBase64 } = await import('@lobechat/utils');
const { asyncifyPolling } = await import('../utils/asyncifyPolling');
const mockParseDataUri = vi.mocked(parseDataUri);

View File

@@ -1,7 +1,7 @@
import { imageUrlToBase64 } from '@lobechat/utils';
import createDebug from 'debug';
import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/index';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { AgentRuntimeErrorType } from '../error';
import { CreateImagePayload, CreateImageResponse } from '../types/image';

View File

@@ -1,12 +1,12 @@
// @vitest-environment edge-runtime
import { GenerateContentResponse, Tool } from '@google/genai';
import * as imageToBase64Module from '@lobechat/utils';
import OpenAI from 'openai';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { OpenAIChatMessage } from '@/libs/model-runtime';
import { CreateImagePayload } from '@/libs/model-runtime/types/image';
import { ChatStreamPayload } from '@/types/openai/chat';
import * as imageToBase64Module from '@/utils/imageToBase64';
import * as debugStreamModule from '../utils/debugStream';
import { LobeGoogleAI } from './index';

View File

@@ -9,9 +9,7 @@ import {
Type as SchemaType,
ThinkingConfig,
} from '@google/genai';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { safeParseJSON } from '@/utils/safeParseJSON';
import { imageUrlToBase64, safeParseJSON } from '@lobechat/utils';
import { LobeRuntimeAI } from '../BaseAI';
import { AgentRuntimeErrorType } from '../error';

View File

@@ -1,8 +1,7 @@
import { ChatModelCard } from '@lobechat/types';
import { Ollama, Tool } from 'ollama/browser';
import { ClientOptions } from 'openai';
import { ModelRequestOptions, OpenAIChatMessage } from '@/libs/model-runtime';
import { ChatModelCard } from '@/types/llm';
import { createErrorResponse } from '@/utils/errorResponse';
import { LobeRuntimeAI } from '../BaseAI';
@@ -13,6 +12,8 @@ import {
Embeddings,
EmbeddingsPayload,
ModelProvider,
ModelRequestOptions,
OpenAIChatMessage,
PullModelParams,
} from '../types';
import { AgentRuntimeError } from '../utils/createError';

View File

@@ -1,8 +1,7 @@
import { imageUrlToBase64 } from '@lobechat/utils';
import { OpenAI } from 'openai';
import { describe, expect, it, vi } from 'vitest';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { OpenAIChatMessage, UserMessageContentPart } from '../types/chat';
import {
buildAnthropicBlock,
@@ -20,7 +19,7 @@ vi.mock('./uriParser', () => ({
type: 'base64',
}),
}));
vi.mock('@/utils/imageToBase64');
vi.mock('@lobechat/utils');
describe('anthropicHelpers', () => {
describe('buildAnthropicBlock', () => {

View File

@@ -1,8 +1,7 @@
import Anthropic from '@anthropic-ai/sdk';
import { imageUrlToBase64 } from '@lobechat/utils';
import OpenAI from 'openai';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { OpenAIChatMessage, UserMessageContentPart } from '../types';
import { parseDataUri } from './uriParser';

View File

@@ -1,3 +1,4 @@
import { getModelPropertyWithFallback } from '@lobechat/utils';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import createDebug from 'debug';
@@ -7,7 +8,6 @@ import { Stream } from 'openai/streaming';
import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
import { RuntimeImageGenParamsValue } from '@/libs/standard-parameters/index';
import type { ChatModelCard } from '@/types/llm';
import { getModelPropertyWithFallback } from '@/utils/getFallbackModelProperty';
import { LobeRuntimeAI } from '../../BaseAI';
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../error';

View File

@@ -1,8 +1,7 @@
import { imageUrlToBase64 } from '@lobechat/utils';
import OpenAI from 'openai';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import {
convertImageUrlToFile,
convertMessageContent,
@@ -12,7 +11,7 @@ import {
import { parseDataUri } from './uriParser';
// 模拟依赖
vi.mock('@/utils/imageToBase64');
vi.mock('@lobechat/utils');
vi.mock('./uriParser');
describe('convertMessageContent', () => {

View File

@@ -1,8 +1,8 @@
import { imageUrlToBase64 } from '@lobechat/utils';
import OpenAI, { toFile } from 'openai';
import { disableStreamModels, systemToUserModels } from '@/const/models';
import { ChatStreamPayload, OpenAIChatMessage } from '@/libs/model-runtime';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
import { parseDataUri } from './uriParser';
@@ -103,7 +103,7 @@ export const convertOpenAIResponseInputs = async (
export const pruneReasoningPayload = (payload: ChatStreamPayload) => {
const shouldStream = !disableStreamModels.has(payload.model);
const { stream_options, ...cleanedPayload } = payload as any;
return {
...cleanedPayload,
frequency_penalty: 0,

View File

@@ -1,6 +1,5 @@
import { InvokeModelWithResponseStreamResponse } from '@aws-sdk/client-bedrock-runtime';
import { nanoid } from '@/utils/uuid';
import { nanoid } from '@lobechat/utils';
import { ChatStreamCallbacks } from '../../../types';
import { transformAnthropicStream } from '../anthropic';

View File

@@ -1,9 +1,8 @@
import { InvokeModelWithResponseStreamResponse } from '@aws-sdk/client-bedrock-runtime';
import * as uuidModule from '@lobechat/utils';
import { Readable } from 'stream';
import { describe, expect, it, vi } from 'vitest';
import * as uuidModule from '@/utils/uuid';
import { AWSBedrockLlamaStream } from './llama';
describe('AWSBedrockLlamaStream', () => {

View File

@@ -1,6 +1,5 @@
import { InvokeModelWithResponseStreamResponse } from '@aws-sdk/client-bedrock-runtime';
import { nanoid } from '@/utils/uuid';
import { nanoid } from '@lobechat/utils';
import { ChatStreamCallbacks } from '../../../types';
import {

View File

@@ -1,8 +1,7 @@
import { GenerateContentResponse } from '@google/genai';
import * as uuidModule from '@lobechat/utils';
import { describe, expect, it, vi } from 'vitest';
import * as uuidModule from '@/utils/uuid';
import { GoogleGenerativeAIStream } from './google-ai';
describe('GoogleGenerativeAIStream', () => {

View File

@@ -1,9 +1,9 @@
import { GenerateContentResponse } from '@google/genai';
import { nanoid } from '@lobechat/utils';
import errorLocale from '@/locales/default/error';
import { ModelTokensUsage } from '@/types/message';
import { GroundingSearch } from '@/types/search';
import { nanoid } from '@/utils/uuid';
import { ChatStreamCallbacks } from '../../types';
import {

View File

@@ -1,8 +1,7 @@
import * as uuidModule from '@lobechat/utils';
import { ChatResponse } from 'ollama/browser';
import { describe, expect, it, vi } from 'vitest';
import * as uuidModule from '@/utils/uuid';
import { OllamaStream } from './ollama';
describe('OllamaStream', () => {

View File

@@ -1,7 +1,7 @@
import { nanoid } from '@lobechat/utils';
import { ChatResponse } from 'ollama/browser';
import { ChatStreamCallbacks } from '@/libs/model-runtime';
import { nanoid } from '@/utils/uuid';
import {
StreamContext,

View File

@@ -1,6 +1,6 @@
import { nanoid, safeParseJSON } from '@lobechat/utils';
import { CitationItem, ModelSpeed, ModelTokensUsage } from '@/types/message';
import { safeParseJSON } from '@/utils/safeParseJSON';
import { nanoid } from '@/utils/uuid';
import { AgentRuntimeErrorType } from '../../error';
import { parseToolCalls } from '../../helpers';

View File

@@ -1,7 +1,6 @@
import * as uuidModule from '@lobechat/utils';
import { describe, expect, it, vi } from 'vitest';
import * as uuidModule from '@/utils/uuid';
import { VertexAIStream } from './vertex-ai';
describe('VertexAIStream', () => {

View File

@@ -1,8 +1,8 @@
import { GenerateContentResponse } from '@google/genai';
import { nanoid } from '@lobechat/utils';
import { ModelTokensUsage } from '@/types/message';
import { GroundingSearch } from '@/types/search';
import { nanoid } from '@/utils/uuid';
import { type GoogleAIStreamOptions } from './google-ai';
import {

View File

@@ -7,6 +7,8 @@ export default defineConfig({
/* eslint-disable sort-keys-fix/sort-keys-fix */
'@/libs/model-runtime': resolve(__dirname, './src'),
'@/types': resolve(__dirname, '../types/src'),
'@/utils/errorResponse': resolve(__dirname, '../../src/utils/errorResponse'),
'@/utils': resolve(__dirname, '../utils/src'),
'@/const': resolve(__dirname, '../const/src'),
'@': resolve(__dirname, '../../src'),
/* eslint-enable */

View File

@@ -4,8 +4,12 @@ export * from './clientDB';
export * from './eval';
export * from './fetch';
export * from './knowledgeBase';
export * from './llm';
export * from './message';
export * from './user';
export * from './user/settings';
// FIXME: I think we need a refactor for the "openai" types
// it more likes the UI message payload
export * from './openai/chat';
export * from './trace';
export * from './zustand';

View File

@@ -0,0 +1,17 @@
{
"name": "@lobechat/utils",
"version": "1.0.0",
"private": true,
"main": "./src/index.ts",
"scripts": {
"test": "vitest",
"test:coverage": "vitest --coverage"
},
"dependencies": {
"@lobechat/const": "workspace:*",
"@lobechat/types": "workspace:*"
},
"devDependencies": {
"vitest-canvas-mock": "^0.3.3"
}
}

View File

@@ -1,9 +1,7 @@
import dayjs from 'dayjs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { COOKIE_CACHE_DAYS } from '@/const/settings';
import { setCookie } from './cookie';
import { COOKIE_CACHE_DAYS, setCookie } from './cookie';
describe('setCookie', () => {
// Mock document.cookie since we're in a test environment

View File

@@ -1,6 +1,6 @@
import dayjs from 'dayjs';
import { COOKIE_CACHE_DAYS } from '@/const/settings';
export const COOKIE_CACHE_DAYS = 30;
export const setCookie = (
key: string,

View File

@@ -1,9 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { VARIABLE_GENERATORS, parsePlaceholderVariablesMessages } from './parserPlaceholder';
import { parsePlaceholderVariablesMessages } from './parserPlaceholder';
// Mock dependencies
vi.mock('@/utils/uuid', () => ({
vi.mock('../uuid', () => ({
uuid: () => 'mocked-uuid-12345',
}));

View File

@@ -1,35 +1,35 @@
import { template } from 'lodash-es';
import { uuid } from '@/utils/uuid';
import { useUserStore } from '@/store/user';
import { userProfileSelectors } from '@/store/user/selectors';
import { uuid } from '../uuid';
const placeholderVariablesRegex = /{{(.*?)}}/g;
/* eslint-disable sort-keys-fix/sort-keys-fix */
export const VARIABLE_GENERATORS = {
/**
*
*
* | Value | Example |
* |-------|---------|
* | `{{date}}` | 12/25/2023 |
* | `{{datetime}}` | 12/25/2023, 2:30:45 PM |
* | `{{day}}` | 25 |
* | `{{hour}}` | 14 |
* | `{{iso}}` | 2023-12-25T14:30:45.123Z |
* | `{{locale}}` | zh-CN |
* | `{{minute}}` | 30 |
* | `{{month}}` | 12 |
* | `{{second}}` | 45 |
* | `{{time}}` | 2:30:45 PM |
* | `{{timestamp}}` | 1703538645123 |
* | `{{timezone}}` | America/New_York |
* | `{{weekday}}` | Monday |
* | `{{year}}` | 2023 |
*
*/
*
*
* | Value | Example |
* |-------|---------|
* | `{{date}}` | 12/25/2023 |
* | `{{datetime}}` | 12/25/2023, 2:30:45 PM |
* | `{{day}}` | 25 |
* | `{{hour}}` | 14 |
* | `{{iso}}` | 2023-12-25T14:30:45.123Z |
* | `{{locale}}` | zh-CN |
* | `{{minute}}` | 30 |
* | `{{month}}` | 12 |
* | `{{second}}` | 45 |
* | `{{time}}` | 2:30:45 PM |
* | `{{timestamp}}` | 1703538645123 |
* | `{{timezone}}` | America/New_York |
* | `{{weekday}}` | Monday |
* | `{{year}}` | 2023 |
*
*/
date: () => new Date().toLocaleDateString(),
datetime: () => new Date().toLocaleString(),
day: () => new Date().getDate().toString().padStart(2, '0'),
@@ -46,65 +46,71 @@ export const VARIABLE_GENERATORS = {
year: () => new Date().getFullYear().toString(),
/**
*
*
* | Value | Example |
* |-------|---------|
* | `{{email}}` | demo@lobehub.com |
* | `{{nickname}}` | |
* | `{{username}}` | LobeChat |
*
*/
*
*
* | Value | Example |
* |-------|---------|
* | `{{email}}` | demo@lobehub.com |
* | `{{nickname}}` | |
* | `{{username}}` | LobeChat |
*
*/
email: () => userProfileSelectors.email(useUserStore.getState()) ?? '',
nickname: () => userProfileSelectors.nickName(useUserStore.getState()) ?? '',
username: () => userProfileSelectors.displayUserName(useUserStore.getState()) ?? userProfileSelectors.fullName(useUserStore.getState()) ?? '',
username: () =>
userProfileSelectors.displayUserName(useUserStore.getState()) ??
userProfileSelectors.fullName(useUserStore.getState()) ??
'',
/**
*
*
* | Value | Example |
* |-------|---------|
* | `{{random}}` | 100041 |
* | `{{random_bool}}` | true |
* | `{{random_float}}` | 76.02 |
* | `{{random_hex}}` | de0dbd |
* | `{{random_int}}` | 68 |
* | `{{random_string}}` | wqn9zfrqe7h |
*
*/
*
*
* | Value | Example |
* |-------|---------|
* | `{{random}}` | 100041 |
* | `{{random_bool}}` | true |
* | `{{random_float}}` | 76.02 |
* | `{{random_hex}}` | de0dbd |
* | `{{random_int}}` | 68 |
* | `{{random_string}}` | wqn9zfrqe7h |
*
*/
random: () => Math.floor(Math.random() * 1_000_000 + 1).toString(),
random_bool: () => (Math.random() > 0.5 ? 'true' : 'false'),
random_float: () => (Math.random() * 100).toFixed(2),
random_hex: () => Math.floor(Math.random() * 16_777_215).toString(16).padStart(6, '0'),
random_hex: () =>
Math.floor(Math.random() * 16_777_215)
.toString(16)
.padStart(6, '0'),
random_int: () => Math.floor(Math.random() * 100 + 1).toString(),
random_string: () => Math.random().toString(36).slice(2, 15),
random_digit: () => Math.floor(Math.random() * 10).toString(),
/**
* UUID
*
* | Value | Example |
* |-------|---------|
* | `{{uuid}}` | dd90b35-669f-4e87-beb8-ac6877f6995d |
* | `{{uuid_short}}` | dd90b35 |
*
*/
* UUID
*
* | Value | Example |
* |-------|---------|
* | `{{uuid}}` | dd90b35-669f-4e87-beb8-ac6877f6995d |
* | `{{uuid_short}}` | dd90b35 |
*
*/
uuid: () => uuid(),
uuid_short: () => uuid().split('-')[0],
/**
*
*
* | Value | Example |
* |-------|---------|
* | `{{language}}` | zh-CN |
* | `{{platform}}` | MacIntel |
* | `{{user_agent}}` | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0 |
*
*/
language: () => typeof navigator !== 'undefined' ? navigator.language : '',
platform: () => typeof navigator !== 'undefined' ? navigator.platform : '',
user_agent: () => typeof navigator !== 'undefined' ? navigator.userAgent : '',
*
*
* | Value | Example |
* |-------|---------|
* | `{{language}}` | zh-CN |
* | `{{platform}}` | MacIntel |
* | `{{user_agent}}` | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0 |
*
*/
language: () => (typeof navigator !== 'undefined' ? navigator.language : ''),
platform: () => (typeof navigator !== 'undefined' ? navigator.platform : ''),
user_agent: () => (typeof navigator !== 'undefined' ? navigator.userAgent : ''),
} as Record<string, () => string>;
/**
@@ -114,7 +120,7 @@ export const VARIABLE_GENERATORS = {
*/
const extractPlaceholderVariables = (text: string): string[] => {
const matches = [...text.matchAll(placeholderVariablesRegex)];
return matches.map(m => m[1].trim());
return matches.map((m) => m[1].trim());
};
/**
@@ -132,7 +138,7 @@ export const parsePlaceholderVariables = (text: string, depth = 2): string => {
const variables = Object.fromEntries(
extractPlaceholderVariables(result)
.map((key) => [key, VARIABLE_GENERATORS[key]?.()])
.filter(([, value]) => value !== undefined)
.filter(([, value]) => value !== undefined),
);
const replaced = template(result, { interpolate: placeholderVariablesRegex })(variables);
@@ -153,7 +159,7 @@ export const parsePlaceholderVariables = (text: string, depth = 2): string => {
* @returns
*/
export const parsePlaceholderVariablesMessages = (messages: any[]): any[] =>
messages.map(message => {
messages.map((message) => {
if (!message?.content) return message;
const { content } = message;
@@ -167,11 +173,9 @@ export const parsePlaceholderVariablesMessages = (messages: any[]): any[] =>
if (Array.isArray(content)) {
return {
...message,
content: content.map(item =>
item?.type === 'text'
? { ...item, text: parsePlaceholderVariables(item.text) }
: item
)
content: content.map((item) =>
item?.type === 'text' ? { ...item, text: parsePlaceholderVariables(item.text) } : item,
),
};
}

View File

@@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import { beforeAll, describe, expect, it } from 'vitest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { ChatTopic } from '@/types/topic';

View File

@@ -1,3 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import compressImage from './compressImage';
const getContextSpy = vi.spyOn(global.HTMLCanvasElement.prototype, 'getContext');

View File

@@ -1,9 +1,8 @@
import { MESSAGE_CANCEL_FLAT } from '@lobechat/const';
import { ChatMessageError } from '@lobechat/types';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
import { ChatMessageError } from '@/types/message';
import { sleep } from '@/utils/sleep';
import { sleep } from '../../sleep';
import { FetchEventSourceInit } from '../fetchEventSource';
import { fetchEventSource } from '../fetchEventSource';
import { fetchSSE } from '../fetchSSE';

View File

@@ -15,8 +15,8 @@ import {
} from '@/types/message';
import { ChatImageChunk } from '@/types/message/image';
import { GroundingSearch } from '@/types/search';
import { nanoid } from '@/utils/uuid';
import { nanoid } from '../uuid';
import { fetchEventSource } from './fetchEventSource';
import { getMessageError } from './parseError';

View File

@@ -1,4 +1,4 @@
import { vi } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { getModelPropertyWithFallback } from './getFallbackModelProperty';

View File

@@ -0,0 +1,5 @@
export * from './client/cookie';
export * from './getFallbackModelProperty';
export * from './imageToBase64';
export * from './safeParseJSON';
export * from './uuid';

View File

@@ -2,7 +2,8 @@ import { resolveAcceptLanguage } from 'resolve-accept-language';
import { DEFAULT_LANG } from '@/const/locale';
import { Locales, locales, normalizeLocale } from '@/locales/resources';
import { RouteVariants } from '@/utils/server/routeVariants';
import { RouteVariants } from './server/routeVariants';
export const getAntdLocale = async (lang?: string) => {
let normalLang: any = normalizeLocale(lang);

View File

@@ -1,6 +1,4 @@
import { expect } from 'vitest';
import { AIChatModelCard } from '@/types/aiModel';
import { describe, expect, it } from 'vitest';
import { mergeArrayById } from './merge';

View File

@@ -1,8 +1,9 @@
import { produce } from 'immer';
import { AiFullModelCard, AiModelType } from '@/types/aiModel';
import { getModelPropertyWithFallback } from '@/utils/getFallbackModelProperty';
import { merge } from '@/utils/merge';
import { getModelPropertyWithFallback } from './getFallbackModelProperty';
import { merge } from './merge';
/**
* Parse model string to add or remove models.

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { isArc, isSonomaOrLaterSafari } from './platform';

Some files were not shown because too many files have changed in this diff Show More