🐛 fix: fix open chat page with float link modal (#9235)

* refactor @lobechat/database

* move config/file and llm to envs

* move config/auth to envs

* refactor

* fix tests

* fix tests

* upgrade
This commit is contained in:
Arvin Xu
2025-09-13 17:03:10 +08:00
committed by GitHub
parent a1f7bff302
commit 2c677e597a
62 changed files with 176 additions and 170 deletions
+95 -93
View File
@@ -2,117 +2,115 @@
globs: *.tsx
alwaysApply: false
---
# LobeChat 国际化指南
# LobeChat Internationalization Guide
## 架构概览
## Key Points
LobeChat 使用 react-i18next 进行国际化,采用良好的命名空间架构:
- Default language: Chinese (zh-CN) as the source language
- Supported languages: 18 languages including English, Japanese, Korean, Arabic, etc.
- Framework: react-i18next with Next.js app router
- Translation automation: @lobehub/i18n-cli for automatic translation, config file: .i18nrc.js
- Never manually modify any json file. You can only modify files in `default` folder
- 默认语言:中文(zh-CN),作为源语言
- 支持语言:18 种语言,包括英语、日语、韩语、阿拉伯语等
- 框架:react-i18next 配合 Next.js app router
- 翻译自动化:@lobehub/i18n-cli 用于自动翻译,配置文件:.i18nrc.js
## 目录结构
## Directory Structure
```
src/locales/
├── default/ # 源语言文件(zh-CN
│ ├── index.ts # 命名空间导出
│ ├── common.ts # 通用翻译
│ ├── chat.ts # 聊天相关翻译
│ ├── setting.ts # 设置翻译
│ └── ... # 其他命名空间文件
└── resources.ts # 类型定义和语言配置
├── default/ # Source language files (zh-CN)
│ ├── index.ts # Namespace exports
│ ├── common.ts # Common translations
│ ├── chat.ts # Chat-related translations
│ ├── setting.ts # Settings translations
│ └── ... # Other namespace files
└── resources.ts # Type definitions and language configuration
locales/ # 翻译文件
├── en-US/ # 英语翻译
│ ├── common.json # 通用翻译
│ ├── chat.json # 聊天翻译
│ ├── setting.json # 设置翻译
│ └── ... # 其他命名空间 JSON 文件
├── ja-JP/ # 日语翻译
locales/ # Translation files
├── en-US/ # English translations
│ ├── common.json # Common translations
│ ├── chat.json # Chat translations
│ ├── setting.json # Settings translations
│ └── ... # Other namespace JSON files
├── ja-JP/ # Japanese translations
│ ├── common.json
│ ├── chat.json
│ └── ...
└── ... # 其他语言文件夹
└── ... # Other language folders
```
## 添加新翻译的工作流程
## Workflow for Adding New Translations
### 1. 添加新的翻译键
### 1. Adding New Translation Keys
第一步:在 src/locales/default 目录下的相应命名空间文件中添加翻译键
Step 1: Add translation keys in the corresponding namespace files under src/locales/default directory
```typescript
// 示例:src/locales/default/common.ts
// Example: src/locales/default/common.ts
export default {
// ... 现有键
newFeature: {
title: "新功能标题",
description: "功能描述文案",
button: "操作按钮",
},
// ... existing keys
newFeature: {
title: '新功能标题',
description: '功能描述文案',
button: '操作按钮',
},
};
```
第二步:如果创建新命名空间,需要在 src/locales/default/index.ts 中导出
Step 2: If creating a new namespace, export it in src/locales/default/index.ts
```typescript
import newNamespace from "./newNamespace";
import newNamespace from './newNamespace';
const resources = {
// ... 现有命名空间
newNamespace,
// ... existing namespaces
newNamespace,
} as const;
```
### 2. 翻译过程
### 2. Translation Process
开发模式:
Development mode:
一般情况下不需要你帮我跑自动翻译工具,跑一次很久,需要的时候我会自己跑。
但是为了立马能看到效果,还是需要先翻译 `locales/zh-CN/namespace.json`,不需要翻译其它语言。
Generally, you don't need to help me run the automatic translation tool as it takes a long time. I'll run it myself when needed. However, to see immediate results, you still need to translate `locales/zh-CN/namespace.json` first, no need to translate other languages.
生产模式:
Production mode:
```bash
# 为所有语言生成翻译
# Generate translations for all languages
npm run i18n
```
## 在组件中使用
## Usage in Components
### 基本用法
### Basic Usage
```tsx
import { useTranslation } from "react-i18next";
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation("common");
const { t } = useTranslation('common');
return (
<div>
<h1>{t("newFeature.title")}</h1>
<p>{t("newFeature.description")}</p>
<button>{t("newFeature.button")}</button>
</div>
);
return (
<div>
<h1>{t('newFeature.title')}</h1>
<p>{t('newFeature.description')}</p>
<button>{t('newFeature.button')}</button>
</div>
);
};
```
### 带参数的用法
### Usage with Parameters
```tsx
const { t } = useTranslation("common");
const { t } = useTranslation('common');
<p>{t("welcome.message", { name: "John" })}</p>;
<p>{t('welcome.message', { name: 'John' })}</p>;
// 对应的语言文件:
// welcome: { message: '欢迎 {{name}} 使用!' }
// Corresponding language file:
// welcome: { message: 'Welcome {{name}}!' }
```
### 多个命名空间
### Multiple Namespaces
```tsx
const { t } = useTranslation(['common', 'chat']);
@@ -121,59 +119,63 @@ const { t } = useTranslation(['common', 'chat']);
<span>{t('chat:typing')}</span>
```
## 类型安全
## Type Safety
项目使用 TypeScript 实现类型安全的翻译,类型从 src/locales/resources.ts 自动生成:
The project uses TypeScript to implement type-safe translations, with types automatically generated from src/locales/resources.ts:
```typescript
import type { DefaultResources, NS, Locales } from "@/locales/resources";
import type { DefaultResources, Locales, NS } from '@/locales/resources';
// 可用类型:
// - NS: 可用命名空间键 ('common' | 'chat' | 'setting' | ...)
// - Locales: 支持的语言代码 ('en-US' | 'zh-CN' | 'ja-JP' | ...)
// Available types:
// - NS: Available namespace keys ('common' | 'chat' | 'setting' | ...)
// - Locales: Supported language codes ('en-US' | 'zh-CN' | 'ja-JP' | ...)
const namespace: NS = "common";
const locale: Locales = "en-US";
const namespace: NS = 'common';
const locale: Locales = 'en-US';
```
## 最佳实践
## Best Practices
### 1. 命名空间组织
### 1. Namespace Organization
- common: 共享 UI 元素(按钮、标签、操作)
- chat: 聊天特定功能
- setting: 配置和设置
- error: 错误消息和处理
- [feature]: 功能特定或页面特定的命名空间
- components: 可复用组件文案
- common: Shared UI elements (buttons, labels, actions)
- chat: Chat-specific functionality
- setting: Configuration and settings
- error: Error messages and handling
- [feature]: Feature-specific or page-specific namespaces
- components: Reusable component text
### 2. 键命名约定
### 2. Key Naming Conventions
```typescript
// ✅ 好:层次结构
// ✅ Good: Hierarchical structure
export default {
modal: {
confirm: {
title: "确认操作",
message: "确定要执行此操作吗?",
actions: {
confirm: "确认",
cancel: "取消",
},
},
modal: {
confirm: {
title: '确认操作',
message: '确定要执行此操作吗?',
actions: {
confirm: '确认',
cancel: '取消',
},
},
},
};
// ❌ 避免:扁平结构
// ❌ Avoid: Flat structure
export default {
modalConfirmTitle: "确认操作",
modalConfirmMessage: "确定要执行此操作吗?",
modalConfirmTitle: '确认操作',
modalConfirmMessage: '确定要执行此操作吗?',
};
```
## 故障排除
## Troubleshooting
### 缺少翻译键
### Missing Translation Keys
- Check if the key exists in src/locales/default/namespace.ts
- Ensure the namespace is correctly imported in the component
- Ensure new namespaces are exported in src/locales/default/index.ts
- 检查键是否存在于 src/locales/default/namespace.ts 中
- 确保在组件中正确导入命名空间
+1 -1
View File
@@ -157,7 +157,7 @@
"@lobehub/charts": "^2.1.2",
"@lobehub/chat-plugin-sdk": "^1.32.4",
"@lobehub/chat-plugins-gateway": "^1.9.0",
"@lobehub/editor": "^1.8.0",
"@lobehub/editor": "^1.8.5",
"@lobehub/icons": "^2.32.2",
"@lobehub/market-sdk": "^0.22.7",
"@lobehub/tts": "^2.0.1",
+1 -1
View File
@@ -1,4 +1,4 @@
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
export const enableClerk = authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH;
export const enableNextAuth = authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
+4 -2
View File
@@ -2,8 +2,10 @@
"name": "@lobechat/database",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./schemas": "./src/schemas/index.ts"
},
"scripts": {
"test": "npm run test:client-db && npm run test:server-db",
"test:client-db": "vitest run",
+1
View File
@@ -0,0 +1 @@
export * from './type';
@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { clientDB, initializeDB } from '@/database/client/db';
import {
agents,
agentsKnowledgeBases,
@@ -16,9 +17,8 @@ import {
topics,
userSettings,
users,
} from '@/database/schemas';
import { LobeChatDatabase } from '@/database/type';
} from '../../schemas';
import { LobeChatDatabase } from '../../type';
import { DATA_EXPORT_CONFIG, DataExporterRepos } from './index';
let db = clientDB as LobeChatDatabase;
@@ -2,7 +2,7 @@ import { ChatErrorType } from '@lobechat/types';
import OpenAI, { ClientOptions } from 'openai';
import urlJoin from 'url-join';
import { getLLMConfig } from '@/config/llm';
import { getLLMConfig } from '@/envs/llm';
// create Azure OpenAI Instance
export const createAzureOpenai = (params: {
@@ -1,7 +1,7 @@
import { ChatErrorType } from '@lobechat/types';
import OpenAI from 'openai';
import { getLLMConfig } from '@/config/llm';
import { getLLMConfig } from '@/envs/llm';
// create OpenAI instance
export const createOpenai = (userApiKey: string | null, endpoint?: string | null) => {
@@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import { authEnv } from '@/config/auth';
import { serverDB } from '@/database/server';
import { authEnv } from '@/envs/auth';
import { pino } from '@/libs/logger';
import { NextAuthUserService } from '@/server/services/nextAuthUser';
@@ -1,6 +1,6 @@
import { headers } from 'next/headers';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
export type CasdoorUserEntity = {
avatar?: string;
@@ -1,8 +1,8 @@
import { NextResponse } from 'next/server';
import { authEnv } from '@/config/auth';
import { isServerMode } from '@/const/version';
import { serverDB } from '@/database/server';
import { authEnv } from '@/envs/auth';
import { pino } from '@/libs/logger';
import { UserService } from '@/server/services/user';
@@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import { authEnv } from '@/config/auth';
import { serverDB } from '@/database/server';
import { authEnv } from '@/envs/auth';
import { pino } from '@/libs/logger';
import { NextAuthUserService } from '@/server/services/nextAuthUser';
@@ -1,7 +1,7 @@
import { headers } from 'next/headers';
import { createHmac } from 'node:crypto';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
export type LogtToUserEntity = {
applicationId?: string;
@@ -1,7 +1,7 @@
// @vitest-environment node
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { analyticsEnv, getAnalyticsConfig } from '../../envs/analytics';
import { analyticsEnv, getAnalyticsConfig } from '../analytics';
beforeEach(() => {
// 在每个测试用例之前,清除所有的 console.warn mock
@@ -1,7 +1,7 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getAppConfig } from '../../envs/app';
import { getAppConfig } from '../app';
// Stub the global process object to safely mock environment variables
vi.stubGlobal('process', {
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
import { getDebugConfig } from '../../envs/debug';
import { getDebugConfig } from '../debug';
// 测试前重置 process.env
vi.stubGlobal('process', {
+1 -1
View File
@@ -1,6 +1,6 @@
import { PropsWithChildren } from 'react';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import Clerk from './Clerk';
import NextAuth from './NextAuth';
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NextAuthConfig } from 'next-auth';
import { getAuthConfig } from '@/config/auth';
import { getServerDBConfig } from '@/config/db';
import { getAuthConfig } from '@/envs/auth';
import { LobeNextAuthDbAdapter } from './adapter';
import { ssoProviders } from './sso-providers';
+1 -1
View File
@@ -1,6 +1,6 @@
import Auth0 from 'next-auth/providers/auth0';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { OIDCConfig } from '@auth/core/providers';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
@@ -1,6 +1,6 @@
import Authentik from 'next-auth/providers/authentik';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
+1 -1
View File
@@ -1,6 +1,6 @@
import AzureAD from 'next-auth/providers/azure-ad';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { getMicrosoftEntraIdIssuer } from './microsoft-entra-id-helper';
import { CommonProviderConfig } from './sso.config';
@@ -1,6 +1,6 @@
import type { OIDCConfig } from '@auth/core/providers';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
@@ -1,6 +1,6 @@
import type { OIDCConfig } from '@auth/core/providers';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
+1 -1
View File
@@ -1,6 +1,6 @@
import GitHub from 'next-auth/providers/github';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
+1 -1
View File
@@ -1,6 +1,6 @@
import { OIDCConfig, OIDCUserConfig } from '@auth/core/providers';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
import { CommonProviderConfig } from './sso.config';
@@ -1,4 +1,4 @@
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
function getTenantId() {
return (
+1 -1
View File
@@ -1,6 +1,6 @@
import Zitadel from 'next-auth/providers/zitadel';
import { authEnv } from '@/config/auth';
import { authEnv } from '@/envs/auth';
const provider = {
id: 'zitadel',
+4 -5
View File
@@ -1,6 +1,4 @@
import debug from 'debug';
import { eq, sql } from 'drizzle-orm';
import { LobeChatDatabase } from '@lobechat/database';
import {
oidcAccessTokens,
oidcAuthorizationCodes,
@@ -10,8 +8,9 @@ import {
oidcInteractions,
oidcRefreshTokens,
oidcSessions,
} from '@/database/schemas/oidc';
import { LobeChatDatabase } from '@/database/type';
} from '@lobechat/database/schemas';
import debug from 'debug';
import { eq, sql } from 'drizzle-orm';
// 创建 adapter 日志命名空间
const log = debug('lobe-oidc:adapter');
+1 -1
View File
@@ -1,10 +1,10 @@
import { LobeChatDatabase } from '@lobechat/database';
import debug from 'debug';
import Provider, { Configuration, KoaContextWithOIDC, errors } from 'oidc-provider';
import urlJoin from 'url-join';
import { serverDBEnv } from '@/config/db';
import { UserModel } from '@/database/models/user';
import { LobeChatDatabase } from '@/database/type';
import { appEnv } from '@/envs/app';
import { getJWKS } from '@/libs/oidc-provider/jwt';
+1 -1
View File
@@ -1,9 +1,9 @@
import { LobeChatDatabase } from '@lobechat/database';
import { TRPCError } from '@trpc/server';
import debug from 'debug';
import { serverDBEnv } from '@/config/db';
import { UserModel } from '@/database/models/user';
import { LobeChatDatabase } from '@/database/type';
import { asyncTrpc } from './init';
+1 -1
View File
@@ -1,9 +1,9 @@
import { LobeChatDatabase } from '@lobechat/database';
import { ClientSecretPayload } from '@lobechat/types';
import debug from 'debug';
import { NextRequest } from 'next/server';
import { LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
import { LobeChatDatabase } from '@/database/type';
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
const log = debug('lobe-async:context');
+1 -1
View File
@@ -5,11 +5,11 @@ import { NextRequest, NextResponse } from 'next/server';
import { UAParser } from 'ua-parser-js';
import urlJoin from 'url-join';
import { authEnv } from '@/config/auth';
import { OAUTH_AUTHORIZED } from '@/const/auth';
import { LOBE_LOCALE_COOKIE } from '@/const/locale';
import { LOBE_THEME_APPEARANCE } from '@/const/theme';
import { appEnv } from '@/envs/app';
import { authEnv } from '@/envs/auth';
import NextAuth from '@/libs/next-auth';
import { Locales } from '@/locales/resources';
+1 -1
View File
@@ -25,7 +25,7 @@ vi.mock('@/config/modelProviders', () => ({
}));
// Mock LLM config
vi.mock('@/config/llm', () => ({
vi.mock('@/envs/llm', () => ({
getLLMConfig: () => ({
ENABLED_AZURE_OPENAI: true,
ENABLED_AWS_BEDROCK: true,
+1 -1
View File
@@ -1,7 +1,7 @@
import { ModelProvider } from '@lobechat/model-runtime';
import { getLLMConfig } from '@/config/llm';
import * as ProviderCards from '@/config/modelProviders';
import { getLLMConfig } from '@/envs/llm';
import { ModelProviderCard } from '@/types/llm';
import { extractEnabledModels, transformToChatModelCards } from '@/utils/_deprecated/parseModels';
@@ -11,7 +11,7 @@ vi.mock('model-bank', async (importOriginal) => {
};
});
vi.mock('@/config/llm', () => ({
vi.mock('@/envs/llm', () => ({
getLLMConfig: vi.fn(() => ({
ENABLED_OPENAI: true,
ENABLED_ANTHROPIC: false,
@@ -83,7 +83,7 @@ describe('genServerAiProvidersConfig', () => {
};
// Mock the LLM config to include our custom key
const { getLLMConfig } = vi.mocked(await import('@/config/llm'));
const { getLLMConfig } = vi.mocked(await import('@/envs/llm'));
getLLMConfig.mockReturnValue({
ENABLED_OPENAI: true,
ENABLED_ANTHROPIC: false,
@@ -4,7 +4,7 @@ import { extractEnabledModels, transformToAiModelList } from '@lobechat/utils';
import * as AiModels from 'model-bank';
import { AiFullModelCard } from 'model-bank';
import { getLLMConfig } from '@/config/llm';
import { getLLMConfig } from '@/envs/llm';
interface ProviderSpecificConfig {
enabled?: boolean;
+2 -2
View File
@@ -1,8 +1,8 @@
import { authEnv } from '@/config/auth';
import { fileEnv } from '@/config/file';
import { enableNextAuth } from '@/const/auth';
import { isDesktop } from '@/const/version';
import { appEnv, getAppConfig } from '@/envs/app';
import { authEnv } from '@/envs/auth';
import { fileEnv } from '@/envs/file';
import { knowledgeEnv } from '@/envs/knowledge';
import { langfuseEnv } from '@/envs/langfuse';
import { parseSystemAgent } from '@/server/globalConfig/parseSystemAgent';
@@ -1,4 +1,4 @@
import { getLLMConfig } from '@/config/llm';
import { getLLMConfig } from '@/envs/llm';
interface KeyStore {
index: number;
@@ -27,7 +27,7 @@ import { describe, expect, it, vi } from 'vitest';
import { initModelRuntimeWithUserPayload } from './index';
// 模拟依赖项
vi.mock('@/config/llm', () => ({
vi.mock('@/envs/llm', () => ({
getLLMConfig: vi.fn(() => ({
// 确保为每个provider提供必要的配置信息
OPENAI_API_KEY: 'test-openai-key',
+1 -1
View File
@@ -1,7 +1,7 @@
import { ModelProvider, ModelRuntime } from '@lobechat/model-runtime';
import { ClientSecretPayload } from '@lobechat/types';
import { getLLMConfig } from '@/config/llm';
import { getLLMConfig } from '@/envs/llm';
import apiKeyManager from './apiKeyManager';
+1 -1
View File
@@ -8,7 +8,7 @@ import {
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { z } from 'zod';
import { fileEnv } from '@/config/file';
import { fileEnv } from '@/envs/file';
import { YEAR } from '@/utils/units';
import { inferContentTypeFromImageUrl } from '@/utils/url';
+1 -1
View File
@@ -4,13 +4,13 @@ import pMap from 'p-map';
import { z } from 'zod';
import { serverDBEnv } from '@/config/db';
import { fileEnv } from '@/config/file';
import { DEFAULT_FILE_EMBEDDING_MODEL_ITEM } from '@/const/settings/knowledge';
import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '@/database/models/asyncTask';
import { ChunkModel } from '@/database/models/chunk';
import { EmbeddingModel } from '@/database/models/embedding';
import { FileModel } from '@/database/models/file';
import { NewChunkItem, NewEmbeddingsItem } from '@/database/schemas';
import { fileEnv } from '@/envs/file';
import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async';
import { getServerDefaultFilesConfig } from '@/server/globalConfig';
import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
+2 -1
View File
@@ -1,5 +1,6 @@
import { LobeChatDatabase } from '@lobechat/database';
import { SessionModel } from '@/database/models/session';
import { LobeChatDatabase } from '@/database/type';
import { getServerDefaultAgentConfig } from '@/server/globalConfig';
export class AgentService {
+1 -1
View File
@@ -1,8 +1,8 @@
import { LobeChatDatabase } from '@lobechat/database';
import { describe, expect, it, vi } from 'vitest';
import { MessageModel } from '@/database/models/message';
import { TopicModel } from '@/database/models/topic';
import { LobeChatDatabase } from '@/database/type';
import { FileService } from '@/server/services/file';
import { AiChatService } from '.';
+2 -1
View File
@@ -1,6 +1,7 @@
import { LobeChatDatabase } from '@lobechat/database';
import { MessageModel } from '@/database/models/message';
import { TopicModel } from '@/database/models/topic';
import { LobeChatDatabase } from '@/database/type';
import { FileService } from '@/server/services/file';
export class AiChatService {
+1 -1
View File
@@ -1,8 +1,8 @@
import { LobeChatDatabase } from '@lobechat/database';
import { ClientSecretPayload } from '@lobechat/types';
import { AsyncTaskModel } from '@/database/models/asyncTask';
import { FileModel } from '@/database/models/file';
import { LobeChatDatabase } from '@/database/type';
import { ChunkContentParams, ContentChunk } from '@/server/modules/ContentChunk';
import { createAsyncCaller } from '@/server/routers/async';
import {
+1 -1
View File
@@ -1,9 +1,9 @@
import { LobeChatDatabase } from '@lobechat/database';
import { loadFile } from '@lobechat/file-loaders';
import debug from 'debug';
import { DocumentModel } from '@/database/models/document';
import { FileModel } from '@/database/models/file';
import { LobeChatDatabase } from '@/database/type';
import { LobeDocument } from '@/types/document';
import { FileService } from '../file';
+13 -13
View File
@@ -10,7 +10,7 @@ const config = {
};
// 模拟 fileEnv
vi.mock('@/config/file', () => ({
vi.mock('@/envs/file', () => ({
get fileEnv() {
return config;
},
@@ -71,12 +71,12 @@ describe('S3StaticFileImpl', () => {
it('should handle full URL input by extracting key (S3_SET_ACL=false)', async () => {
config.S3_SET_ACL = false;
const fullUrl = 'https://s3.example.com/bucket/path/to/file.jpg?X-Amz-Signature=expired';
// Mock getKeyFromFullUrl to return the extracted key
vi.spyOn(fileService, 'getKeyFromFullUrl').mockReturnValue('path/to/file.jpg');
const result = await fileService.getFullFileUrl(fullUrl);
expect(fileService.getKeyFromFullUrl).toHaveBeenCalledWith(fullUrl);
expect(result).toBe('https://presigned.example.com/test.jpg');
config.S3_SET_ACL = true;
@@ -84,33 +84,33 @@ describe('S3StaticFileImpl', () => {
it('should handle full URL input by extracting key (S3_SET_ACL=true)', async () => {
const fullUrl = 'https://s3.example.com/bucket/path/to/file.jpg';
vi.spyOn(fileService, 'getKeyFromFullUrl').mockReturnValue('path/to/file.jpg');
const result = await fileService.getFullFileUrl(fullUrl);
expect(fileService.getKeyFromFullUrl).toHaveBeenCalledWith(fullUrl);
expect(result).toBe('https://example.com/path/to/file.jpg');
});
it('should handle normal key input without extraction', async () => {
const key = 'path/to/file.jpg';
const spy = vi.spyOn(fileService, 'getKeyFromFullUrl');
const result = await fileService.getFullFileUrl(key);
expect(spy).not.toHaveBeenCalled();
expect(result).toBe('https://example.com/path/to/file.jpg');
});
it('should handle http:// URLs for legacy compatibility', async () => {
const httpUrl = 'http://s3.example.com/bucket/path/to/file.jpg';
vi.spyOn(fileService, 'getKeyFromFullUrl').mockReturnValue('path/to/file.jpg');
const result = await fileService.getFullFileUrl(httpUrl);
expect(fileService.getKeyFromFullUrl).toHaveBeenCalledWith(httpUrl);
expect(result).toBe('https://example.com/path/to/file.jpg');
});
+1 -1
View File
@@ -1,6 +1,6 @@
import urlJoin from 'url-join';
import { fileEnv } from '@/config/file';
import { fileEnv } from '@/envs/file';
import { S3 } from '@/server/modules/S3';
import { FileServiceImpl } from './type';
+1 -1
View File
@@ -1,9 +1,9 @@
import { LobeChatDatabase } from '@lobechat/database';
import { TRPCError } from '@trpc/server';
import { serverDBEnv } from '@/config/db';
import { FileModel } from '@/database/models/file';
import { FileItem } from '@/database/schemas';
import { LobeChatDatabase } from '@/database/type';
import { TempFileManager } from '@/server/utils/tempFileManager';
import { nanoid } from '@/utils/uuid';
+1 -1
View File
@@ -1,3 +1,4 @@
import { LobeChatDatabase } from '@lobechat/database';
import { parseDataUri } from '@lobechat/model-runtime';
import debug from 'debug';
import { sha256 } from 'js-sha256';
@@ -6,7 +7,6 @@ import { IMAGE_GENERATION_CONFIG } from 'model-bank';
import { nanoid } from 'nanoid';
import sharp from 'sharp';
import { LobeChatDatabase } from '@/database/type';
import { FileService } from '@/server/services/file';
import { calculateThumbnailDimensions } from '@/utils/number';
import { getYYYYmmddHHMMss } from '@/utils/time';
+1 -1
View File
@@ -1,3 +1,4 @@
import { LobeChatDatabase } from '@lobechat/database';
import { and, eq } from 'drizzle-orm';
import { Adapter, AdapterAccount } from 'next-auth/adapters';
import { NextResponse } from 'next/server';
@@ -11,7 +12,6 @@ import {
nextauthVerificationTokens,
users,
} from '@/database/schemas';
import { LobeChatDatabase } from '@/database/type';
import { pino } from '@/libs/logger';
import { merge } from '@/utils/merge';
+1 -1
View File
@@ -1,9 +1,9 @@
import { UserJSON } from '@clerk/backend';
import { LobeChatDatabase } from '@lobechat/database';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { UserModel } from '@/database/models/user';
import { UserItem } from '@/database/schemas';
import { LobeChatDatabase } from '@/database/type';
import { pino } from '@/libs/logger';
import { AgentService } from '@/server/services/agent';
+1 -1
View File
@@ -1,7 +1,7 @@
import { UserJSON } from '@clerk/backend';
import { LobeChatDatabase } from '@lobechat/database';
import { UserModel } from '@/database/models/user';
import { LobeChatDatabase } from '@/database/type';
import { initializeServerAnalytics } from '@/libs/analytics';
import { pino } from '@/libs/logger';
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
+1 -1
View File
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fileEnv } from '@/config/file';
import { fileEnv } from '@/envs/file';
import { edgeClient } from '@/libs/trpc/client';
import { API_ENDPOINTS } from '@/services/_url';
import { clientS3Storage } from '@/services/file/ClientS3';
+1 -1
View File
@@ -1,8 +1,8 @@
import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { fileEnv } from '@/config/file';
import { FileModel } from '@/database/_deprecated/models/file';
import { DB_File } from '@/database/_deprecated/schemas/files';
import { fileEnv } from '@/envs/file';
import { clientS3Storage } from '@/services/file/ClientS3';
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
import { createServerConfigStore } from '@/store/serverConfig/store';
+1 -1
View File
@@ -4,7 +4,7 @@ import { uuid } from '@lobechat/utils';
import dayjs from 'dayjs';
import { sha256 } from 'js-sha256';
import { fileEnv } from '@/config/file';
import { fileEnv } from '@/envs/file';
import { edgeClient } from '@/libs/trpc/client';
import { API_ENDPOINTS } from '@/services/_url';
import { clientS3Storage } from '@/services/file/ClientS3';