diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b312728655..a125efaa1b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,6 +8,7 @@ - [ ] πŸ’„ style - [ ] πŸ‘· build - [ ] ⚑️ perf +- [ ] βœ… test - [ ] πŸ“ docs - [ ] πŸ”¨ chore diff --git a/packages/const/src/index.ts b/packages/const/src/index.ts index bf422390ab..916b48205b 100644 --- a/packages/const/src/index.ts +++ b/packages/const/src/index.ts @@ -1,5 +1,4 @@ export * from './image'; -export * from './locale'; export * from './message'; export * from './settings'; export * from './version'; diff --git a/packages/const/src/meta.ts b/packages/const/src/meta.ts index 4a6d7cc55b..97b1b7f1fc 100644 --- a/packages/const/src/meta.ts +++ b/packages/const/src/meta.ts @@ -1,5 +1,6 @@ -import { BRANDING_LOGO_URL } from '@/const/branding'; -import { MetaData } from '@/types/meta'; +import { MetaData } from '@lobechat/types'; + +import { BRANDING_LOGO_URL } from './branding'; export const DEFAULT_AVATAR = 'πŸ€–'; export const DEFAULT_USER_AVATAR = 'πŸ˜€'; diff --git a/packages/const/src/settings/agent.ts b/packages/const/src/settings/agent.ts index 2911fa9a84..8b5d6be3b6 100644 --- a/packages/const/src/settings/agent.ts +++ b/packages/const/src/settings/agent.ts @@ -1,7 +1,12 @@ -import { DEFAULT_AGENT_META } from '@/const/meta'; -import { DEFAULT_MODEL, DEFAULT_PROVIDER } from '@/const/settings/llm'; -import { LobeAgentChatConfig, LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent'; -import { UserDefaultAgent } from '@/types/user/settings'; +import { + LobeAgentChatConfig, + LobeAgentConfig, + LobeAgentTTSConfig, + UserDefaultAgent, +} from '@lobechat/types'; + +import { DEFAULT_AGENT_META } from '../meta'; +import { DEFAULT_MODEL, DEFAULT_PROVIDER } from './llm'; export const DEFAUTT_AGENT_TTS_CONFIG: LobeAgentTTSConfig = { showAllLocaleVoice: false, diff --git a/packages/const/src/settings/systemAgent.ts b/packages/const/src/settings/systemAgent.ts index 043e8d21cc..56317ecfa1 100644 --- a/packages/const/src/settings/systemAgent.ts +++ b/packages/const/src/settings/systemAgent.ts @@ -6,9 +6,6 @@ import { import { DEFAULT_MODEL, DEFAULT_PROVIDER } from './llm'; -export const DEFAULT_REWRITE_QUERY = - 'Given the following conversation and a follow-up question, rephrase the follow up question to be a standalone question, in its original language. Keep as much details as possible from previous messages. Keep entity names and all.'; - export const DEFAULT_SYSTEM_AGENT_ITEM: SystemAgentItem = { model: DEFAULT_MODEL, provider: DEFAULT_PROVIDER, diff --git a/packages/file-loaders/vitest.config.mts b/packages/file-loaders/vitest.config.mts index 9e974995dc..940870a78e 100644 --- a/packages/file-loaders/vitest.config.mts +++ b/packages/file-loaders/vitest.config.mts @@ -2,13 +2,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - // coverage: { - // all: false, - // provider: 'v8', - // reporter: ['text', 'json', 'lcov', 'text-summary'], - // reportsDirectory: './coverage/app', - // }, + coverage: { + reporter: ['text', 'json', 'lcov', 'text-summary'], + }, environment: 'happy-dom', - // setupFiles: join(__dirname, './test/setup.ts'), }, }); diff --git a/packages/model-runtime/vitest.config.mts b/packages/model-runtime/vitest.config.mts index fd57324ecb..3eb8f5374c 100644 --- a/packages/model-runtime/vitest.config.mts +++ b/packages/model-runtime/vitest.config.mts @@ -13,6 +13,9 @@ export default defineConfig({ '@': resolve(__dirname, '../../src'), /* eslint-enable */ }, + coverage: { + reporter: ['text', 'json', 'lcov', 'text-summary'], + }, environment: 'happy-dom', }, }); diff --git a/packages/prompts/package.json b/packages/prompts/package.json index bcc2e958a6..a84aae07ad 100644 --- a/packages/prompts/package.json +++ b/packages/prompts/package.json @@ -8,7 +8,6 @@ "test:coverage": "vitest --coverage" }, "dependencies": { - "@lobechat/const": "workspace:*", "@lobechat/types": "workspace:*" } } diff --git a/packages/prompts/src/chains/__tests__/abstractChunk.test.ts b/packages/prompts/src/chains/__tests__/abstractChunk.test.ts new file mode 100644 index 0000000000..77d4de24c6 --- /dev/null +++ b/packages/prompts/src/chains/__tests__/abstractChunk.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { chainAbstractChunkText } from '../abstractChunk'; + +describe('chainAbstractChunkText', () => { + it('should generate correct chat payload for chunk text', () => { + const testText = 'This is a sample chunk of text that needs to be summarized.'; + + const result = chainAbstractChunkText(testText); + + expect(result).toEqual({ + messages: [ + { + content: + 'δ½ ζ˜―δΈ€εζ“…ι•Ώδ»Ž chunk δΈ­ζε–ζ‘˜θ¦ηš„εŠ©η†οΌŒδ½ ιœ€θ¦ε°†η”¨ζˆ·ηš„δΌšθ―ζ€»η»“δΈΊ 1~2 ε₯θ―ηš„ζ‘˜θ¦οΌŒθΎ“ε‡Ίζˆ chunk ζ‰€δ½Ώη”¨ηš„θ―­η§', + role: 'system', + }, + { + content: `chunk: ${testText}`, + role: 'user', + }, + ], + }); + }); + + it('should handle empty text', () => { + const result = chainAbstractChunkText(''); + + expect(result.messages).toHaveLength(2); + expect(result.messages![1].content).toBe('chunk: '); + }); + + it('should handle text with special characters', () => { + const testText = 'Text with special chars: @#$%^&*()'; + + const result = chainAbstractChunkText(testText); + + expect(result.messages![1].content).toBe(`chunk: ${testText}`); + }); + + it('should always use system role for first message', () => { + const result = chainAbstractChunkText('test'); + + expect(result.messages![0].role).toBe('system'); + }); + + it('should always use user role for second message', () => { + const result = chainAbstractChunkText('test'); + + expect(result.messages![1].role).toBe('user'); + }); +}); diff --git a/packages/prompts/src/chains/__tests__/answerWithContext.test.ts b/packages/prompts/src/chains/__tests__/answerWithContext.test.ts new file mode 100644 index 0000000000..000cc722fa --- /dev/null +++ b/packages/prompts/src/chains/__tests__/answerWithContext.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from 'vitest'; + +import { chainAnswerWithContext } from '../answerWithContext'; + +describe('chainAnswerWithContext', () => { + it('should generate correct chat payload with context and knowledge', () => { + const testParams = { + context: ['Context passage 1', 'Context passage 2'], + knowledge: ['AI', 'Machine Learning'], + question: 'What is artificial intelligence?', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages).toHaveLength(1); + expect(result.messages![0].role).toBe('user'); + expect(result.messages![0].content).toContain('AI/Machine Learning'); + expect(result.messages![0].content).toContain('Context passage 1'); + expect(result.messages![0].content).toContain('Context passage 2'); + expect(result.messages![0].content).toContain('What is artificial intelligence?'); + }); + + it('should handle single knowledge area', () => { + const testParams = { + context: ['Single context'], + knowledge: ['Technology'], + question: 'How does it work?', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages![0].content).toContain('Technology'); + }); + + it('should handle multiple knowledge areas', () => { + const testParams = { + context: ['Context'], + knowledge: ['AI', 'ML', 'NLP', 'Computer Vision'], + question: 'Tell me about these fields', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages![0].content).toContain('AI/ML/NLP/Computer Vision'); + }); + + it('should handle empty context array', () => { + const testParams = { + context: [], + knowledge: ['AI'], + question: 'What is AI?', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages![0].content).toContain(''); + expect(result.messages![0].content).toContain(''); + }); + + it('should include proper context formatting', () => { + const testParams = { + context: ['First passage', 'Second passage'], + knowledge: ['Test'], + question: 'Test question', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages![0].content).toContain( + '\nFirst passage\nSecond passage\n', + ); + }); + + it('should include proper instructions about using passages', () => { + const testParams = { + context: ['Context'], + knowledge: ['Knowledge'], + question: 'Question', + }; + + const result = chainAnswerWithContext(testParams); + const content = result.messages![0].content; + + expect(content).toContain('passages might not be relevant'); + expect(content).toContain('please only use the passages that are relevant'); + expect(content).toContain('answer using your knowledge'); + }); + + it('should include markdown formatting instruction', () => { + const testParams = { + context: ['Context'], + knowledge: ['Knowledge'], + question: 'Question', + }; + + const result = chainAnswerWithContext(testParams); + + expect(result.messages![0].content).toContain('follow markdown syntax'); + }); +}); diff --git a/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts b/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts new file mode 100644 index 0000000000..bb4bd50348 --- /dev/null +++ b/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it, vi } from 'vitest'; + +// Mock DEFAULT_REWRITE_QUERY + +import { DEFAULT_REWRITE_QUERY, chainRewriteQuery } from '../rewriteQuery'; + +describe('chainRewriteQuery', () => { + it('should generate correct chat payload with default instruction', () => { + const query = 'What about the weather?'; + const context = ['Previous message 1', 'Previous message 2']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages).toHaveLength(2); + expect(result.messages![0].role).toBe('system'); + expect(result.messages![1].role).toBe('user'); + expect(result.messages![0].content).toContain(DEFAULT_REWRITE_QUERY); + expect(result.messages![1].content).toContain(query); + }); + + it('should include chat history in system message', () => { + const query = 'Follow up question'; + const context = ['User: Hello', 'Assistant: Hi there']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![0].content).toContain(''); + expect(result.messages![0].content).toContain('User: Hello'); + expect(result.messages![0].content).toContain('Assistant: Hi there'); + expect(result.messages![0].content).toContain(''); + }); + + it('should use custom instruction when provided', () => { + const query = 'Test query'; + const context = ['Context']; + const customInstruction = 'Custom rewrite instruction'; + + const result = chainRewriteQuery(query, context, customInstruction); + + expect(result.messages![0].content).toContain(customInstruction); + expect(result.messages![0].content).not.toContain(DEFAULT_REWRITE_QUERY); + }); + + it('should format user message correctly', () => { + const query = 'What is the status?'; + const context = ['Previous context']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![1].content).toBe(`Follow Up Input: ${query}, it's standalone query:`); + }); + + it('should handle empty context array', () => { + const query = 'Empty context query'; + const context: string[] = []; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![0].content).toContain('\n\n'); + }); + + it('should handle single context item', () => { + const query = 'Single context query'; + const context = ['Only one message']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![0].content).toContain('Only one message'); + }); + + it('should join multiple context items with newlines', () => { + const query = 'Multi context query'; + const context = ['Message 1', 'Message 2', 'Message 3']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![0].content).toContain('Message 1\nMessage 2\nMessage 3'); + }); + + it('should handle special characters in query', () => { + const query = 'Query with special chars: @#$%^&*()'; + const context = ['Context']; + + const result = chainRewriteQuery(query, context); + + expect(result.messages![1].content).toContain(query); + }); +}); diff --git a/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts b/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts new file mode 100644 index 0000000000..07f53b0641 --- /dev/null +++ b/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from 'vitest'; + +import { chainSummaryGenerationTitle } from '../summaryGenerationTitle'; + +describe('chainSummaryGenerationTitle', () => { + it('should generate correct chat payload for image modal', () => { + const prompts = ['A beautiful sunset', 'Mountain landscape']; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + expect(result.messages).toHaveLength(2); + expect(result.messages![0].role).toBe('system'); + expect(result.messages![1].role).toBe('user'); + expect(result.messages![0].content).toContain('AI image prompt'); + expect(result.messages![0].content).toContain(locale); + }); + + it('should generate correct chat payload for video modal', () => { + const prompts = ['Dancing in the rain']; + const modal = 'video' as const; + const locale = 'en-US'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + expect(result.messages![0].content).toContain('AI video prompt'); + expect(result.messages![0].content).toContain(locale); + }); + + it('should format single prompt correctly', () => { + const prompts = ['Single prompt']; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + expect(result.messages![1].content).toContain('1. Single prompt'); + }); + + it('should format multiple prompts with numbering', () => { + const prompts = ['First prompt', 'Second prompt', 'Third prompt']; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + const userMessage = result.messages![1].content; + expect(userMessage).toContain('1. First prompt'); + expect(userMessage).toContain('2. Second prompt'); + expect(userMessage).toContain('3. Third prompt'); + }); + + it('should include system instructions about title requirements', () => { + const prompts = ['Test prompt']; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + const systemMessage = result.messages![0].content; + expect(systemMessage).toContain('θ΅„ζ·±ηš„ AI θ‰Ίζœ―εˆ›δ½œθ€…'); + expect(systemMessage).toContain('10δΈͺε­—δ»₯ε†…'); + expect(systemMessage).toContain('δΈιœ€θ¦εŒ…ε«ζ ‡η‚Ήη¬¦ε·'); + }); + + it('should handle empty prompts array', () => { + const prompts: string[] = []; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + expect(result.messages![1].content).toContain('提瀺词:\n'); + }); + + it('should handle different locales', () => { + const prompts = ['Test']; + const modal = 'image' as const; + const customLocale = 'ja-JP'; + + const result = chainSummaryGenerationTitle(prompts, modal, customLocale); + + expect(result.messages![0].content).toContain(customLocale); + }); + + it('should differentiate between image and video modals in instructions', () => { + const prompts = ['Test prompt']; + const locale = 'zh-CN'; + + const imageResult = chainSummaryGenerationTitle(prompts, 'image', locale); + const videoResult = chainSummaryGenerationTitle(prompts, 'video', locale); + + expect(imageResult.messages![0].content).toContain('AI image prompt'); + expect(videoResult.messages![0].content).toContain('AI video prompt'); + }); + + it('should format prompts with newlines between them', () => { + const prompts = ['Prompt one', 'Prompt two']; + const modal = 'image' as const; + const locale = 'zh-CN'; + + const result = chainSummaryGenerationTitle(prompts, modal, locale); + + expect(result.messages![1].content).toContain('1. Prompt one\n2. Prompt two'); + }); +}); diff --git a/packages/prompts/src/chains/abstractChunk.ts b/packages/prompts/src/chains/abstractChunk.ts index 73050738f1..4b0be07cd6 100644 --- a/packages/prompts/src/chains/abstractChunk.ts +++ b/packages/prompts/src/chains/abstractChunk.ts @@ -1,4 +1,3 @@ -import { DEFAULT_MODEL } from '@lobechat/const'; import { ChatStreamPayload } from '@lobechat/types'; export const chainAbstractChunkText = (text: string): Partial => { @@ -14,6 +13,5 @@ export const chainAbstractChunkText = (text: string): Partial role: 'user', }, ], - model: DEFAULT_MODEL, }; }; diff --git a/packages/prompts/src/chains/rewriteQuery.ts b/packages/prompts/src/chains/rewriteQuery.ts index 11c341a565..f2f46dcb39 100644 --- a/packages/prompts/src/chains/rewriteQuery.ts +++ b/packages/prompts/src/chains/rewriteQuery.ts @@ -1,6 +1,8 @@ -import { DEFAULT_REWRITE_QUERY } from '@lobechat/const'; import { ChatStreamPayload } from '@lobechat/types'; +export const DEFAULT_REWRITE_QUERY = + 'Given the following conversation and a follow-up question, rephrase the follow up question to be a standalone question, in its original language. Keep as much details as possible from previous messages. Keep entity names and all.'; + export const chainRewriteQuery = ( query: string, context: string[], diff --git a/packages/prompts/src/index.test.ts b/packages/prompts/src/index.test.ts new file mode 100644 index 0000000000..92bd9cbe5d --- /dev/null +++ b/packages/prompts/src/index.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest'; + +import * as chains from './chains'; +import * as mainExports from './index'; +import * as prompts from './prompts'; + +// Mock the problematic dependency +vi.mock('@/locales/resources', () => ({ + supportLocales: ['en-US', 'zh-CN'], +})); + +describe('Main Index Export', () => { + it('should export all chains', () => { + expect(mainExports).toEqual(expect.objectContaining(chains)); + }); + + it('should export all prompts', () => { + expect(mainExports).toEqual(expect.objectContaining(prompts)); + }); + + it('should have all expected chain exports', () => { + const chainExports = [ + 'chainAbstractChunkText', + 'chainAnswerWithContext', + 'chainLangDetect', + 'chainPickEmoji', + 'chainRewriteQuery', + 'chainSummaryAgentName', + 'chainSummaryDescription', + 'chainSummaryGenerationTitle', + 'chainSummaryHistory', + 'chainSummaryTags', + 'chainSummaryTitle', + 'chainTranslate', + ]; + + chainExports.forEach((exportName) => { + expect(mainExports).toHaveProperty(exportName); + }); + }); +}); diff --git a/packages/prompts/src/prompts/systemRole/index.test.ts b/packages/prompts/src/prompts/systemRole/index.test.ts new file mode 100644 index 0000000000..32cff26286 --- /dev/null +++ b/packages/prompts/src/prompts/systemRole/index.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it } from 'vitest'; + +import { BuiltinSystemRolePrompts } from './index'; + +describe('BuiltinSystemRolePrompts', () => { + it('should return welcome message only when only welcome is provided', () => { + const result = BuiltinSystemRolePrompts({ + welcome: 'Welcome to the assistant!', + }); + + expect(result).toBe('Welcome to the assistant!'); + }); + + it('should return plugins message only when only plugins is provided', () => { + const result = BuiltinSystemRolePrompts({ + plugins: 'Available plugins: calculator, weather', + }); + + expect(result).toBe('Available plugins: calculator, weather'); + }); + + it('should return history summary when only history is provided', () => { + const result = BuiltinSystemRolePrompts({ + historySummary: 'User discussed AI topics previously', + }); + + expect(result).toContain(''); + expect(result).toContain( + 'Users may have lots of chat messages, here is the summary of the history:', + ); + expect(result).toContain('User discussed AI topics previously'); + expect(result).toContain(''); + expect(result.trim()).toMatch(/^[\s\S]*<\/chat_history_summary>$/); + }); + + it('should combine all three parts when all are provided', () => { + const result = BuiltinSystemRolePrompts({ + welcome: 'Welcome!', + plugins: 'Plugins available', + historySummary: 'Previous conversation summary', + }); + + expect(result).toContain('Welcome!'); + expect(result).toContain('Plugins available'); + expect(result).toContain(''); + expect(result).toContain('Previous conversation summary'); + expect(result).toContain(''); + + // Should be joined with double newlines + const parts = result.split('\n\n'); + expect(parts).toHaveLength(3); + }); + + it('should combine welcome and plugins when no history is provided', () => { + const result = BuiltinSystemRolePrompts({ + welcome: 'Hello user!', + plugins: 'Available tools', + }); + + expect(result).toBe('Hello user!\n\nAvailable tools'); + }); + + it('should combine welcome and history when no plugins provided', () => { + const result = BuiltinSystemRolePrompts({ + welcome: 'Greetings!', + historySummary: 'Chat history here', + }); + + expect(result).toContain('Greetings!'); + expect(result).toContain(''); + expect(result).toContain('Chat history here'); + }); + + it('should combine plugins and history when no welcome provided', () => { + const result = BuiltinSystemRolePrompts({ + plugins: 'Tool list', + historySummary: 'Summary of previous chats', + }); + + expect(result).toContain('Tool list'); + expect(result).toContain(''); + expect(result).toContain('Summary of previous chats'); + }); + + it('should return empty string when no parameters provided', () => { + const result = BuiltinSystemRolePrompts({}); + + expect(result).toBe(''); + }); + + it('should filter out falsy values', () => { + const result = BuiltinSystemRolePrompts({ + welcome: '', + plugins: 'Valid plugins', + historySummary: undefined, + }); + + expect(result).toBe('Valid plugins'); + }); + + it('should handle null and undefined values gracefully', () => { + const result = BuiltinSystemRolePrompts({ + welcome: undefined, + plugins: null as any, + historySummary: 'Valid history', + }); + + expect(result).toContain(''); + expect(result).toContain('Valid history'); + expect(result).toContain(''); + }); + + it('should preserve whitespace in individual components', () => { + const result = BuiltinSystemRolePrompts({ + welcome: 'Welcome\nwith newlines', + plugins: 'Plugins\twith tabs', + }); + + expect(result).toContain('Welcome\nwith newlines'); + expect(result).toContain('Plugins\twith tabs'); + }); + + it('should format history summary with proper XML structure', () => { + const historySummary = 'User asked about weather and traffic'; + const result = BuiltinSystemRolePrompts({ + historySummary, + }); + + expect(result).toContain(''); + expect(result).toContain( + 'Users may have lots of chat messages, here is the summary of the history:', + ); + expect(result).toContain('User asked about weather and traffic'); + expect(result).toContain(''); + }); +}); diff --git a/packages/prompts/vitest.config.mts b/packages/prompts/vitest.config.mts index a9f5456ffd..940870a78e 100644 --- a/packages/prompts/vitest.config.mts +++ b/packages/prompts/vitest.config.mts @@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + coverage: { + reporter: ['text', 'json', 'lcov', 'text-summary'], + }, environment: 'happy-dom', }, }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 723d925b18..44895d8e6d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,3 +1,4 @@ +export * from './agent'; export * from './artifact'; export * from './chunk'; export * from './clientDB'; @@ -6,6 +7,7 @@ export * from './fetch'; export * from './knowledgeBase'; export * from './llm'; export * from './message'; +export * from './meta'; export * from './user'; export * from './user/settings'; // FIXME: I think we need a refactor for the "openai" types diff --git a/packages/utils/package.json b/packages/utils/package.json index a5eb0b6ea0..b1424eb836 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,7 +2,11 @@ "name": "@lobechat/utils", "version": "1.0.0", "private": true, - "main": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./server": "./src/server/index.ts", + "./client": "./src/client/index.ts" + }, "scripts": { "test": "vitest", "test:coverage": "vitest --coverage" diff --git a/packages/utils/src/server/index.ts b/packages/utils/src/server/index.ts new file mode 100644 index 0000000000..ad406733e8 --- /dev/null +++ b/packages/utils/src/server/index.ts @@ -0,0 +1,5 @@ +export * from './auth'; +export * from './correctOIDCUrl'; +export * from './geo'; +export * from './responsive'; +export * from './xor'; diff --git a/packages/utils/vitest.config.mts b/packages/utils/vitest.config.mts index 8b33852d6e..89d01f885f 100644 --- a/packages/utils/vitest.config.mts +++ b/packages/utils/vitest.config.mts @@ -10,6 +10,9 @@ export default defineConfig({ '@': resolve(__dirname, '../../src'), /* eslint-enable */ }, + coverage: { + reporter: ['text', 'json', 'lcov', 'text-summary'], + }, environment: 'happy-dom', setupFiles: join(__dirname, './tests/setup.ts'), }, diff --git a/src/app/(backend)/middleware/auth/index.test.ts b/src/app/(backend)/middleware/auth/index.test.ts index f031413f6b..e7dbeec24a 100644 --- a/src/app/(backend)/middleware/auth/index.test.ts +++ b/src/app/(backend)/middleware/auth/index.test.ts @@ -1,9 +1,9 @@ import { AgentRuntimeError } from '@lobechat/model-runtime'; import { ChatErrorType } from '@lobechat/types'; +import { getXorPayload } from '@lobechat/utils/server'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createErrorResponse } from '@/utils/errorResponse'; -import { getXorPayload } from '@/utils/server/xor'; import { RequestHandler, checkAuth } from './index'; import { checkAuthMethod } from './utils'; @@ -20,7 +20,7 @@ vi.mock('./utils', () => ({ checkAuthMethod: vi.fn(), })); -vi.mock('@/utils/server/xor', () => ({ +vi.mock('@lobechat/utils/server', () => ({ getXorPayload: vi.fn(), })); diff --git a/src/app/(backend)/middleware/auth/index.ts b/src/app/(backend)/middleware/auth/index.ts index 424393b6c4..efc3a3b986 100644 --- a/src/app/(backend)/middleware/auth/index.ts +++ b/src/app/(backend)/middleware/auth/index.ts @@ -5,6 +5,7 @@ import { ModelRuntime, } from '@lobechat/model-runtime'; import { ChatErrorType } from '@lobechat/types'; +import { getXorPayload } from '@lobechat/utils/server'; import { NextRequest } from 'next/server'; import { @@ -17,7 +18,6 @@ import { import { ClerkAuth } from '@/libs/clerk-auth'; import { validateOIDCJWT } from '@/libs/oidc-provider/jwt'; import { createErrorResponse } from '@/utils/errorResponse'; -import { getXorPayload } from '@/utils/server/xor'; import { checkAuthMethod } from './utils'; diff --git a/src/app/(backend)/oidc/consent/route.ts b/src/app/(backend)/oidc/consent/route.ts index 21e5ca85ed..4166f27932 100644 --- a/src/app/(backend)/oidc/consent/route.ts +++ b/src/app/(backend)/oidc/consent/route.ts @@ -1,9 +1,8 @@ +import { correctOIDCUrl, getUserAuth } from '@lobechat/utils/server'; import debug from 'debug'; import { NextRequest, NextResponse } from 'next/server'; import { OIDCService } from '@/server/services/oidc'; -import { getUserAuth } from '@/utils/server/auth'; -import { correctOIDCUrl } from '@/utils/server/correctOIDCUrl'; const log = debug('lobe-oidc:consent'); diff --git a/src/app/(backend)/webapi/chat/[provider]/route.test.ts b/src/app/(backend)/webapi/chat/[provider]/route.test.ts index f0fdfe33b6..31d6cda8d1 100644 --- a/src/app/(backend)/webapi/chat/[provider]/route.test.ts +++ b/src/app/(backend)/webapi/chat/[provider]/route.test.ts @@ -2,11 +2,11 @@ import { getAuth } from '@clerk/nextjs/server'; import { LobeRuntimeAI, ModelRuntime } from '@lobechat/model-runtime'; import { ChatErrorType } from '@lobechat/types'; +import { getXorPayload } from '@lobechat/utils/server'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { checkAuthMethod } from '@/app/(backend)/middleware/auth/utils'; import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth'; -import { getXorPayload } from '@/utils/server/xor'; import { POST } from './route'; @@ -18,7 +18,7 @@ vi.mock('@/app/(backend)/middleware/auth/utils', () => ({ checkAuthMethod: vi.fn(), })); -vi.mock('@/utils/server/xor', () => ({ +vi.mock('@lobechat/utils/server', () => ({ getXorPayload: vi.fn(), })); diff --git a/src/app/(backend)/webapi/plugin/gateway/route.ts b/src/app/(backend)/webapi/plugin/gateway/route.ts index 8b23693b8b..23ba98f3e0 100644 --- a/src/app/(backend)/webapi/plugin/gateway/route.ts +++ b/src/app/(backend)/webapi/plugin/gateway/route.ts @@ -1,5 +1,6 @@ import { AgentRuntimeError } from '@lobechat/model-runtime'; import { ChatErrorType, ErrorType, TraceNameMap } from '@lobechat/types'; +import { getXorPayload } from '@lobechat/utils/server'; import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk'; import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway'; @@ -8,7 +9,6 @@ import { LOBE_CHAT_TRACE_ID } from '@/const/trace'; import { getAppConfig } from '@/envs/app'; import { TraceClient } from '@/libs/traces'; import { createErrorResponse } from '@/utils/errorResponse'; -import { getXorPayload } from '@/utils/server/xor'; import { getTracePayload } from '@/utils/trace'; import { parserPluginSettings } from './settings'; diff --git a/src/app/[variants]/(main)/files/[id]/page.tsx b/src/app/[variants]/(main)/files/[id]/page.tsx index 3fdfc0f449..75b6cb24e2 100644 --- a/src/app/[variants]/(main)/files/[id]/page.tsx +++ b/src/app/[variants]/(main)/files/[id]/page.tsx @@ -1,3 +1,4 @@ +import { getUserAuth } from '@lobechat/utils/server'; import { notFound } from 'next/navigation'; import { Flexbox } from 'react-layout-kit'; @@ -5,7 +6,6 @@ import FileViewer from '@/features/FileViewer'; import { createCallerFactory } from '@/libs/trpc/lambda'; import { lambdaRouter } from '@/server/routers/lambda'; import { PagePropsWithId } from '@/types/next'; -import { getUserAuth } from '@/utils/server/auth'; import FileDetail from '../features/FileDetail'; import Header from './Header'; diff --git a/src/app/[variants]/(main)/settings/sync/page.tsx b/src/app/[variants]/(main)/settings/sync/page.tsx index 72ab30e11d..705a062241 100644 --- a/src/app/[variants]/(main)/settings/sync/page.tsx +++ b/src/app/[variants]/(main)/settings/sync/page.tsx @@ -1,10 +1,10 @@ +import { gerServerDeviceInfo } from '@lobechat/utils/server'; import { notFound } from 'next/navigation'; import { serverFeatureFlags } from '@/config/featureFlags'; import { metadataModule } from '@/server/metadata'; import { translation } from '@/server/translation'; import { DynamicLayoutProps } from '@/types/next'; -import { gerServerDeviceInfo } from '@/utils/server/responsive'; import { RouteVariants } from '@/utils/server/routeVariants'; import Page from './index'; diff --git a/src/app/[variants]/(main)/settings/system-agent/index.tsx b/src/app/[variants]/(main)/settings/system-agent/index.tsx index 5ffe9e34fe..71dabb41df 100644 --- a/src/app/[variants]/(main)/settings/system-agent/index.tsx +++ b/src/app/[variants]/(main)/settings/system-agent/index.tsx @@ -1,6 +1,7 @@ 'use client'; -import { DEFAULT_REWRITE_QUERY } from '@/const/settings'; +import { DEFAULT_REWRITE_QUERY } from '@lobechat/prompts'; + import { isServerMode } from '@/const/version'; import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig'; diff --git a/packages/const/src/locale.ts b/src/const/locale.ts similarity index 100% rename from packages/const/src/locale.ts rename to src/const/locale.ts diff --git a/src/libs/oidc-provider/adapter.ts b/src/libs/oidc-provider/adapter.ts index 165b8bf0c3..9a52b57b67 100644 --- a/src/libs/oidc-provider/adapter.ts +++ b/src/libs/oidc-provider/adapter.ts @@ -164,7 +164,7 @@ class OIDCAdapter { log('[%s] Setting userId: %s', this.name, payload.accountId); } else { try { - const { getUserAuth } = await import('@/utils/server/auth'); + const { getUserAuth } = await import('@lobechat/utils/server'); try { const { userId } = await getUserAuth(); if (userId) { diff --git a/src/libs/trpc/edge/middleware/jwtPayload.test.ts b/src/libs/trpc/edge/middleware/jwtPayload.test.ts index 432dda1497..41a83100bb 100644 --- a/src/libs/trpc/edge/middleware/jwtPayload.test.ts +++ b/src/libs/trpc/edge/middleware/jwtPayload.test.ts @@ -1,11 +1,11 @@ // @vitest-environment node +import * as utils from '@lobechat/utils/server'; import { TRPCError } from '@trpc/server'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createCallerFactory } from '@/libs/trpc/edge'; import { AuthContext, createContextInner } from '@/libs/trpc/edge/context'; import { edgeTrpc as trpc } from '@/libs/trpc/edge/init'; -import * as utils from '@/utils/server/xor'; import { jwtPayloadChecker } from './jwtPayload'; diff --git a/src/libs/trpc/edge/middleware/jwtPayload.ts b/src/libs/trpc/edge/middleware/jwtPayload.ts index 8c7a847500..82b3c4b27f 100644 --- a/src/libs/trpc/edge/middleware/jwtPayload.ts +++ b/src/libs/trpc/edge/middleware/jwtPayload.ts @@ -1,7 +1,6 @@ +import { getXorPayload } from '@lobechat/utils/server'; import { TRPCError } from '@trpc/server'; -import { getXorPayload } from '@/utils/server/xor'; - import { edgeTrpc } from '../init'; export const jwtPayloadChecker = edgeTrpc.middleware(async (opts) => { diff --git a/src/libs/trpc/lambda/middleware/keyVaults.ts b/src/libs/trpc/lambda/middleware/keyVaults.ts index a928ffda86..54f0714564 100644 --- a/src/libs/trpc/lambda/middleware/keyVaults.ts +++ b/src/libs/trpc/lambda/middleware/keyVaults.ts @@ -1,7 +1,6 @@ +import { getXorPayload } from '@lobechat/utils/server'; import { TRPCError } from '@trpc/server'; -import { getXorPayload } from '@/utils/server/xor'; - import { trpc } from '../init'; export const keyVaults = trpc.middleware(async (opts) => { diff --git a/src/middleware.ts b/src/middleware.ts index 88a85f5dcc..854f058da9 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,4 +1,5 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; +import { parseDefaultThemeFromCountry } from '@lobechat/utils/server'; import debug from 'debug'; import { NextRequest, NextResponse } from 'next/server'; import { UAParser } from 'ua-parser-js'; @@ -11,11 +12,10 @@ import { LOBE_THEME_APPEARANCE } from '@/const/theme'; import { appEnv } from '@/envs/app'; import NextAuthEdge from '@/libs/next-auth/edge'; import { Locales } from '@/locales/resources'; -import { parseBrowserLanguage } from '@/utils/locale'; -import { parseDefaultThemeFromCountry } from '@/utils/server/geo'; -import { RouteVariants } from '@/utils/server/routeVariants'; import { oidcEnv } from './envs/oidc'; +import { parseBrowserLanguage } from './utils/locale'; +import { RouteVariants } from './utils/server/routeVariants'; // Create debug logger instances const logDefault = debug('middleware:default'); diff --git a/src/server/routers/tools/search.test.ts b/src/server/routers/tools/search.test.ts index 8c96a7428d..d61110b019 100644 --- a/src/server/routers/tools/search.test.ts +++ b/src/server/routers/tools/search.test.ts @@ -9,7 +9,7 @@ import { SEARCH_SEARXNG_NOT_CONFIG } from '@/types/tool/search'; import { searchRouter } from './search'; // Mock JWT verification -vi.mock('@/utils/server/xor', () => ({ +vi.mock('@lobechat/utils/server', () => ({ getXorPayload: vi.fn().mockReturnValue({ userId: '1' }), })); diff --git a/src/utils/client/switchLang.ts b/src/utils/client/switchLang.ts index 34e7739adb..2a8f1377cf 100644 --- a/src/utils/client/switchLang.ts +++ b/src/utils/client/switchLang.ts @@ -1,7 +1,7 @@ -import { LOBE_LOCALE_COOKIE } from '@lobechat/const'; import { setCookie } from '@lobechat/utils'; import { changeLanguage } from 'i18next'; +import { LOBE_LOCALE_COOKIE } from '@/const/locale'; import { LocaleMode } from '@/types/locale'; export const switchLang = (locale: LocaleMode) => { diff --git a/packages/utils/src/locale.test.ts b/src/utils/locale.test.ts similarity index 100% rename from packages/utils/src/locale.test.ts rename to src/utils/locale.test.ts diff --git a/packages/utils/src/locale.ts b/src/utils/locale.ts similarity index 100% rename from packages/utils/src/locale.ts rename to src/utils/locale.ts diff --git a/packages/utils/src/server/pageProps.ts b/src/utils/server/pageProps.ts similarity index 85% rename from packages/utils/src/server/pageProps.ts rename to src/utils/server/pageProps.ts index a8a0813afc..a751c4c2ae 100644 --- a/packages/utils/src/server/pageProps.ts +++ b/src/utils/server/pageProps.ts @@ -1,6 +1,7 @@ import { translation } from '@/server/translation'; import { DynamicLayoutProps } from '@/types/next'; -import { RouteVariants } from '@/utils/server/routeVariants'; + +import { RouteVariants } from './routeVariants'; export const parsePageMetaProps = async (props: DynamicLayoutProps) => { const { locale: hl, isMobile } = await RouteVariants.getVariantsFromProps(props); diff --git a/packages/utils/src/server/routeVariants.ts b/src/utils/server/routeVariants.ts similarity index 100% rename from packages/utils/src/server/routeVariants.ts rename to src/utils/server/routeVariants.ts diff --git a/tsconfig.json b/tsconfig.json index d7acc0a795..982f275d52 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ "@/libs/model-runtime": ["./packages/model-runtime/src/index.ts"], "@/libs/model-runtime/*": ["./packages/model-runtime/src/*"], "@/database/*": ["./packages/database/src/*", "./src/database/*"], - "@/const/*": ["./packages/const/src/*"], + "@/const/*": ["./packages/const/src/*", "./src/const/*"], "@/utils/*": ["./packages/utils/src/*", "./src/utils/*"], "@/types/*": ["./packages/types/src/*", "./src/types/*"], "@/*": ["./src/*"], diff --git a/vitest.config.mts b/vitest.config.mts index e66fc00a9a..5870fbc037 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -13,6 +13,7 @@ export default defineConfig({ '@/database/_deprecated': resolve(__dirname, './src/database/_deprecated'), '@/database': resolve(__dirname, './packages/database/src'), '@/utils/client/switchLang': resolve(__dirname, './src/utils/client/switchLang'), + '@/const/locale': resolve(__dirname, './src/const/locale'), // TODO: after refactor the errorResponse, we can remove it '@/utils/errorResponse': resolve(__dirname, './src/utils/errorResponse'), '@/utils': resolve(__dirname, './packages/utils/src'),