mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-13 19:20:04 +00:00
✅ test: add unit tests for @lobechat/prompts (#8967)
* add test and refactor * update * move * fix test * improve config * refactor utils server import * move * refactor @/utils/server to @lobechat/utils/server * improve config * fix tests
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- [ ] 💄 style
|
||||
- [ ] 👷 build
|
||||
- [ ] ⚡️ perf
|
||||
- [ ] ✅ test
|
||||
- [ ] 📝 docs
|
||||
- [ ] 🔨 chore
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './image';
|
||||
export * from './locale';
|
||||
export * from './message';
|
||||
export * from './settings';
|
||||
export * from './version';
|
||||
|
||||
@@ -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 = '😀';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,9 @@ export default defineConfig({
|
||||
'@': resolve(__dirname, '../../src'),
|
||||
/* eslint-enable */
|
||||
},
|
||||
coverage: {
|
||||
reporter: ['text', 'json', 'lcov', 'text-summary'],
|
||||
},
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"test:coverage": "vitest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lobechat/const": "workspace:*",
|
||||
"@lobechat/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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('<Context>');
|
||||
expect(result.messages![0].content).toContain('</Context>');
|
||||
});
|
||||
|
||||
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(
|
||||
'<Context>\nFirst passage\nSecond passage\n</Context>',
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -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('<chatHistory>');
|
||||
expect(result.messages![0].content).toContain('User: Hello');
|
||||
expect(result.messages![0].content).toContain('Assistant: Hi there');
|
||||
expect(result.messages![0].content).toContain('</chatHistory>');
|
||||
});
|
||||
|
||||
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('<chatHistory>\n\n</chatHistory>');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DEFAULT_MODEL } from '@lobechat/const';
|
||||
import { ChatStreamPayload } from '@lobechat/types';
|
||||
|
||||
export const chainAbstractChunkText = (text: string): Partial<ChatStreamPayload> => {
|
||||
@@ -14,6 +13,5 @@ export const chainAbstractChunkText = (text: string): Partial<ChatStreamPayload>
|
||||
role: 'user',
|
||||
},
|
||||
],
|
||||
model: DEFAULT_MODEL,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('<chat_history_summary>');
|
||||
expect(result).toContain(
|
||||
'<docstring>Users may have lots of chat messages, here is the summary of the history:</docstring>',
|
||||
);
|
||||
expect(result).toContain('<summary>User discussed AI topics previously</summary>');
|
||||
expect(result).toContain('</chat_history_summary>');
|
||||
expect(result.trim()).toMatch(/^<chat_history_summary>[\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('<chat_history_summary>');
|
||||
expect(result).toContain('Previous conversation summary');
|
||||
expect(result).toContain('</chat_history_summary>');
|
||||
|
||||
// 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('<chat_history_summary>');
|
||||
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('<chat_history_summary>');
|
||||
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('<chat_history_summary>');
|
||||
expect(result).toContain('<summary>Valid history</summary>');
|
||||
expect(result).toContain('</chat_history_summary>');
|
||||
});
|
||||
|
||||
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('<chat_history_summary>');
|
||||
expect(result).toContain(
|
||||
'<docstring>Users may have lots of chat messages, here is the summary of the history:</docstring>',
|
||||
);
|
||||
expect(result).toContain('<summary>User asked about weather and traffic</summary>');
|
||||
expect(result).toContain('</chat_history_summary>');
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
reporter: ['text', 'json', 'lcov', 'text-summary'],
|
||||
},
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './auth';
|
||||
export * from './correctOIDCUrl';
|
||||
export * from './geo';
|
||||
export * from './responsive';
|
||||
export * from './xor';
|
||||
@@ -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'),
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
+3
-3
@@ -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');
|
||||
|
||||
@@ -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' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
+1
-1
@@ -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/*"],
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user