@lobehub/chat 1.127.2 → 1.127.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/docker-compose/local/docker-compose.yml +83 -1
- package/docker-compose/local/grafana/.env.example +42 -0
- package/docker-compose/local/grafana/.env.zh-CN.example +42 -0
- package/docker-compose/local/grafana/docker-compose.yml +251 -0
- package/docker-compose/local/grafana/grafana/dashboards/.gitkeep +0 -0
- package/docker-compose/local/grafana/grafana/datasources/datasource-prometheus.yaml +15 -0
- package/docker-compose/local/grafana/grafana/datasources/datasource-tempo.yaml +16 -0
- package/docker-compose/local/grafana/otel-collector/collector-config.yaml +45 -0
- package/docker-compose/local/grafana/prometheus/prometheus.yml +11 -0
- package/docker-compose/local/grafana/tempo/tempo.yaml +58 -0
- package/docker-compose/production/grafana/.env.example +44 -0
- package/docker-compose/production/grafana/.env.zh-CN.example +42 -0
- package/docker-compose/production/grafana/docker-compose.yml +249 -0
- package/docker-compose/production/grafana/grafana/dashboards/.gitkeep +0 -0
- package/docker-compose/production/grafana/grafana/datasources/datasource-prometheus.yaml +15 -0
- package/docker-compose/production/grafana/grafana/datasources/datasource-tempo.yaml +20 -0
- package/docker-compose/production/grafana/otel-collector/collector-config.yaml +45 -0
- package/docker-compose/production/grafana/prometheus/prometheus.yml +11 -0
- package/docker-compose/production/grafana/tempo/tempo.yaml +58 -0
- package/docker-compose.development.yml +44 -0
- package/docs/self-hosting/advanced/observability/grafana.mdx +71 -0
- package/docs/self-hosting/advanced/observability/grafana.zh-CN.mdx +70 -0
- package/package.json +2 -2
- package/packages/model-runtime/package.json +2 -1
- package/packages/model-runtime/src/const/modelProvider.ts +65 -0
- package/packages/model-runtime/src/const/type.test.ts +9 -0
- package/packages/model-runtime/src/{BaseAI.ts → core/BaseAI.ts} +3 -4
- package/packages/model-runtime/src/{ModelRuntime.test.ts → core/ModelRuntime.test.ts} +3 -3
- package/packages/model-runtime/src/{ModelRuntime.ts → core/ModelRuntime.ts} +9 -8
- package/packages/model-runtime/src/core/RouterRuntime/baseRuntimeMap.ts +17 -0
- package/packages/model-runtime/src/{RouterRuntime → core/RouterRuntime}/createRuntime.ts +9 -7
- package/packages/model-runtime/src/{RouterRuntime → core/RouterRuntime}/index.ts +1 -0
- package/packages/model-runtime/src/{utils → core}/openaiCompatibleFactory/createImage.ts +3 -3
- package/packages/model-runtime/src/{utils → core}/openaiCompatibleFactory/index.test.ts +2 -2
- package/packages/model-runtime/src/{utils → core}/openaiCompatibleFactory/index.ts +9 -9
- package/packages/model-runtime/src/{utils → core}/streams/bedrock/claude.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/bedrock/llama.test.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/bedrock/llama.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/google-ai.test.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/google-ai.ts +1 -1
- package/packages/model-runtime/src/core/streams/model.test.ts +268 -0
- package/packages/model-runtime/src/{utils → core}/streams/ollama.test.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/ollama.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/openai/openai.test.ts +39 -6
- package/packages/model-runtime/src/{utils → core}/streams/openai/openai.ts +4 -3
- package/packages/model-runtime/src/{utils → core}/streams/openai/responsesStream.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/protocol.ts +2 -2
- package/packages/model-runtime/src/{utils → core}/streams/qwen.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/spark.ts +1 -1
- package/packages/model-runtime/src/core/streams/utils.test.ts +164 -0
- package/packages/model-runtime/src/{utils → core}/streams/vertex-ai.test.ts +1 -1
- package/packages/model-runtime/src/{utils → core}/streams/vertex-ai.ts +1 -1
- package/packages/model-runtime/src/index.ts +29 -27
- package/packages/model-runtime/src/providerTestUtils.ts +1 -1
- package/packages/model-runtime/src/{ai21 → providers/ai21}/index.test.ts +1 -1
- package/packages/model-runtime/src/{ai21 → providers/ai21}/index.ts +2 -2
- package/packages/model-runtime/src/{ai302 → providers/ai302}/index.ts +4 -4
- package/packages/model-runtime/src/{ai360 → providers/ai360}/index.test.ts +1 -1
- package/packages/model-runtime/src/{ai360 → providers/ai360}/index.ts +2 -2
- package/packages/model-runtime/src/{aihubmix → providers/aihubmix}/index.ts +5 -5
- package/packages/model-runtime/src/{akashchat → providers/akashchat}/index.test.ts +1 -1
- package/packages/model-runtime/src/{akashchat → providers/akashchat}/index.ts +5 -4
- package/packages/model-runtime/src/{anthropic → providers/anthropic}/index.test.ts +2 -2
- package/packages/model-runtime/src/{anthropic → providers/anthropic}/index.ts +10 -10
- package/packages/model-runtime/src/{azureOpenai → providers/azureOpenai}/index.test.ts +4 -4
- package/packages/model-runtime/src/{azureOpenai → providers/azureOpenai}/index.ts +11 -11
- package/packages/model-runtime/src/{azureai → providers/azureai}/index.ts +9 -9
- package/packages/model-runtime/src/{baichuan → providers/baichuan}/index.test.ts +1 -1
- package/packages/model-runtime/src/{baichuan → providers/baichuan}/index.ts +2 -2
- package/packages/model-runtime/src/{bedrock → providers/bedrock}/index.test.ts +1 -1
- package/packages/model-runtime/src/{bedrock → providers/bedrock}/index.ts +12 -12
- package/packages/model-runtime/src/{bfl → providers/bfl}/createImage.test.ts +25 -25
- package/packages/model-runtime/src/{bfl → providers/bfl}/createImage.ts +6 -6
- package/packages/model-runtime/src/{bfl → providers/bfl}/index.test.ts +1 -1
- package/packages/model-runtime/src/{bfl → providers/bfl}/index.ts +4 -4
- package/packages/model-runtime/src/{cloudflare → providers/cloudflare}/index.test.ts +1 -1
- package/packages/model-runtime/src/{cloudflare → providers/cloudflare}/index.ts +8 -8
- package/packages/model-runtime/src/{cohere → providers/cohere}/index.test.ts +1 -1
- package/packages/model-runtime/src/{cohere → providers/cohere}/index.ts +2 -2
- package/packages/model-runtime/src/providers/cometapi/index.test.ts +12 -0
- package/packages/model-runtime/src/{cometapi → providers/cometapi}/index.ts +3 -3
- package/packages/model-runtime/src/{deepseek → providers/deepseek}/index.test.ts +1 -1
- package/packages/model-runtime/src/{deepseek → providers/deepseek}/index.ts +3 -5
- package/packages/model-runtime/src/{fal → providers/fal}/index.test.ts +1 -1
- package/packages/model-runtime/src/{fal → providers/fal}/index.ts +4 -4
- package/packages/model-runtime/src/{fireworksai → providers/fireworksai}/index.test.ts +1 -1
- package/packages/model-runtime/src/{fireworksai → providers/fireworksai}/index.ts +2 -2
- package/packages/model-runtime/src/{giteeai → providers/giteeai}/index.test.ts +1 -1
- package/packages/model-runtime/src/{giteeai → providers/giteeai}/index.ts +3 -3
- package/packages/model-runtime/src/{github → providers/github}/index.test.ts +1 -1
- package/packages/model-runtime/src/{github → providers/github}/index.ts +5 -5
- package/packages/model-runtime/src/{google → providers/google}/createImage.test.ts +2 -2
- package/packages/model-runtime/src/{google → providers/google}/createImage.ts +5 -5
- package/packages/model-runtime/src/{google → providers/google}/index.test.ts +2 -2
- package/packages/model-runtime/src/{google → providers/google}/index.ts +13 -13
- package/packages/model-runtime/src/{groq → providers/groq}/index.test.ts +1 -1
- package/packages/model-runtime/src/{groq → providers/groq}/index.ts +3 -3
- package/packages/model-runtime/src/{higress → providers/higress}/index.ts +6 -5
- package/packages/model-runtime/src/{huggingface → providers/huggingface}/index.ts +4 -4
- package/packages/model-runtime/src/{hunyuan → providers/hunyuan}/index.test.ts +1 -1
- package/packages/model-runtime/src/{hunyuan → providers/hunyuan}/index.ts +2 -2
- package/packages/model-runtime/src/{infiniai → providers/infiniai}/index.ts +3 -3
- package/packages/model-runtime/src/{internlm → providers/internlm}/index.test.ts +1 -1
- package/packages/model-runtime/src/{internlm → providers/internlm}/index.ts +2 -2
- package/packages/model-runtime/src/{jina → providers/jina}/index.test.ts +1 -1
- package/packages/model-runtime/src/{jina → providers/jina}/index.ts +2 -2
- package/packages/model-runtime/src/{lmstudio → providers/lmstudio}/index.test.ts +1 -1
- package/packages/model-runtime/src/{lmstudio → providers/lmstudio}/index.ts +2 -2
- package/packages/model-runtime/src/{minimax → providers/minimax}/createImage.test.ts +2 -2
- package/packages/model-runtime/src/{minimax → providers/minimax}/createImage.ts +3 -3
- package/packages/model-runtime/src/{minimax → providers/minimax}/index.test.ts +1 -1
- package/packages/model-runtime/src/{minimax → providers/minimax}/index.ts +2 -2
- package/packages/model-runtime/src/{mistral → providers/mistral}/index.test.ts +1 -1
- package/packages/model-runtime/src/{mistral → providers/mistral}/index.ts +2 -2
- package/packages/model-runtime/src/{modelscope → providers/modelscope}/index.test.ts +1 -1
- package/packages/model-runtime/src/{modelscope → providers/modelscope}/index.ts +3 -3
- package/packages/model-runtime/src/{moonshot → providers/moonshot}/index.test.ts +1 -1
- package/packages/model-runtime/src/{moonshot → providers/moonshot}/index.ts +3 -3
- package/packages/model-runtime/src/{nebius → providers/nebius}/index.test.ts +1 -1
- package/packages/model-runtime/src/{nebius → providers/nebius}/index.ts +3 -3
- package/packages/model-runtime/src/{newapi → providers/newapi}/index.test.ts +4 -4
- package/packages/model-runtime/src/{newapi → providers/newapi}/index.ts +5 -5
- package/packages/model-runtime/src/{novita → providers/novita}/index.test.ts +1 -1
- package/packages/model-runtime/src/{novita → providers/novita}/index.ts +3 -3
- package/packages/model-runtime/src/{nvidia → providers/nvidia}/index.test.ts +1 -1
- package/packages/model-runtime/src/{nvidia → providers/nvidia}/index.ts +3 -3
- package/packages/model-runtime/src/{ollama → providers/ollama}/index.test.ts +3 -3
- package/packages/model-runtime/src/{ollama → providers/ollama}/index.ts +11 -10
- package/packages/model-runtime/src/{openai → providers/openai}/index.test.ts +1 -1
- package/packages/model-runtime/src/{openai → providers/openai}/index.ts +6 -5
- package/packages/model-runtime/src/{openrouter → providers/openrouter}/index.test.ts +1 -1
- package/packages/model-runtime/src/{openrouter → providers/openrouter}/index.ts +3 -3
- package/packages/model-runtime/src/{perplexity → providers/perplexity}/index.test.ts +1 -1
- package/packages/model-runtime/src/{perplexity → providers/perplexity}/index.ts +2 -2
- package/packages/model-runtime/src/{ppio → providers/ppio}/index.test.ts +1 -1
- package/packages/model-runtime/src/{ppio → providers/ppio}/index.ts +2 -2
- package/packages/model-runtime/src/{qiniu → providers/qiniu}/index.test.ts +2 -2
- package/packages/model-runtime/src/{qiniu → providers/qiniu}/index.ts +3 -3
- package/packages/model-runtime/src/{qwen → providers/qwen}/createImage.test.ts +2 -2
- package/packages/model-runtime/src/{qwen → providers/qwen}/createImage.ts +4 -4
- package/packages/model-runtime/src/{qwen → providers/qwen}/index.test.ts +1 -1
- package/packages/model-runtime/src/{qwen → providers/qwen}/index.ts +4 -4
- package/packages/model-runtime/src/{sambanova → providers/sambanova}/index.test.ts +1 -1
- package/packages/model-runtime/src/{sambanova → providers/sambanova}/index.ts +2 -2
- package/packages/model-runtime/src/{search1api → providers/search1api}/index.test.ts +1 -1
- package/packages/model-runtime/src/{search1api → providers/search1api}/index.ts +2 -2
- package/packages/model-runtime/src/{sensenova → providers/sensenova}/index.test.ts +1 -1
- package/packages/model-runtime/src/{sensenova → providers/sensenova}/index.ts +3 -3
- package/packages/model-runtime/src/{siliconcloud → providers/siliconcloud}/index.ts +4 -4
- package/packages/model-runtime/src/{spark → providers/spark}/index.test.ts +1 -1
- package/packages/model-runtime/src/{spark → providers/spark}/index.ts +3 -3
- package/packages/model-runtime/src/{stepfun → providers/stepfun}/index.test.ts +1 -1
- package/packages/model-runtime/src/{stepfun → providers/stepfun}/index.ts +2 -2
- package/packages/model-runtime/src/{taichu → providers/taichu}/index.test.ts +2 -2
- package/packages/model-runtime/src/{taichu → providers/taichu}/index.ts +2 -2
- package/packages/model-runtime/src/{tencentcloud → providers/tencentcloud}/index.test.ts +1 -1
- package/packages/model-runtime/src/{tencentcloud → providers/tencentcloud}/index.ts +2 -2
- package/packages/model-runtime/src/{togetherai → providers/togetherai}/index.test.ts +1 -1
- package/packages/model-runtime/src/{togetherai → providers/togetherai}/index.ts +2 -2
- package/packages/model-runtime/src/{upstage → providers/upstage}/index.test.ts +1 -1
- package/packages/model-runtime/src/{upstage → providers/upstage}/index.ts +2 -2
- package/packages/model-runtime/src/{v0 → providers/v0}/index.ts +3 -3
- package/packages/model-runtime/src/providers/vertexai/index.test.ts +54 -0
- package/packages/model-runtime/src/{vertexai → providers/vertexai}/index.ts +2 -2
- package/packages/model-runtime/src/providers/vllm/index.test.ts +12 -0
- package/packages/model-runtime/src/{vllm → providers/vllm}/index.ts +2 -2
- package/packages/model-runtime/src/{volcengine → providers/volcengine}/createImage.test.ts +2 -2
- package/packages/model-runtime/src/{volcengine → providers/volcengine}/createImage.ts +2 -2
- package/packages/model-runtime/src/{volcengine → providers/volcengine}/index.ts +3 -3
- package/packages/model-runtime/src/{wenxin → providers/wenxin}/index.test.ts +1 -1
- package/packages/model-runtime/src/{wenxin → providers/wenxin}/index.ts +2 -2
- package/packages/model-runtime/src/{xai → providers/xai}/index.test.ts +1 -1
- package/packages/model-runtime/src/{xai → providers/xai}/index.ts +3 -3
- package/packages/model-runtime/src/providers/xinference/index.test.ts +12 -0
- package/packages/model-runtime/src/{xinference → providers/xinference}/index.ts +2 -2
- package/packages/model-runtime/src/{zeroone → providers/zeroone}/index.test.ts +1 -1
- package/packages/model-runtime/src/{zeroone → providers/zeroone}/index.ts +3 -3
- package/packages/model-runtime/src/{zhipu → providers/zhipu}/index.test.ts +1 -1
- package/packages/model-runtime/src/{zhipu → providers/zhipu}/index.ts +5 -5
- package/packages/model-runtime/src/runtimeMap.ts +61 -61
- package/packages/model-runtime/src/types/index.ts +1 -0
- package/packages/model-runtime/src/types/type.ts +1 -65
- package/packages/model-runtime/src/utils/createError.test.ts +95 -0
- package/packages/model-runtime/src/utils/handleOpenAIError.test.ts +149 -0
- package/packages/model-runtime/src/utils/postProcessModelList.test.ts +190 -0
- package/packages/model-runtime/src/utils/response.test.ts +91 -0
- package/packages/types/src/fetch.ts +1 -1
- package/packages/types/src/message/base.ts +2 -3
- package/packages/types/src/user/settings/index.ts +1 -2
- package/packages/types/src/user/settings/keyVaults.ts +1 -0
- package/packages/types/src/user/settings/modelProvider.ts +4 -3
- package/src/app/[variants]/(main)/settings/llm/ProviderList/HuggingFace/index.tsx +1 -1
- package/src/server/modules/ModelRuntime/index.test.ts +2 -4
- package/src/services/__tests__/chat.test.ts +13 -8
- package/src/store/user/slices/settings/initialState.ts +2 -3
- package/tsconfig.json +0 -2
- package/vitest.config.mts +0 -1
- package/packages/model-runtime/src/RouterRuntime/baseRuntimeMap.ts +0 -17
- /package/packages/model-runtime/src/{RouterRuntime → core/RouterRuntime}/createRuntime.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/__snapshots__/protocol.test.ts.snap +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/anthropic.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/anthropic.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/bedrock/common.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/bedrock/index.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/index.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/model.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/openai/__snapshots__/responsesStream.test.ts.snap +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/openai/index.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/openai/responsesStream.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/protocol.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/qwen.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/spark.test.ts +0 -0
- /package/packages/model-runtime/src/{utils → core}/streams/utils.ts +0 -0
- /package/packages/model-runtime/src/{anthropic → providers/anthropic}/handleAnthropicError.ts +0 -0
- /package/packages/model-runtime/src/{bfl → providers/bfl}/types.ts +0 -0
- /package/packages/model-runtime/src/{huggingface → providers/huggingface}/index.test.ts +0 -0
- /package/packages/model-runtime/src/{novita → providers/novita}/__snapshots__/index.test.ts.snap +0 -0
- /package/packages/model-runtime/src/{novita → providers/novita}/fixtures/models.json +0 -0
- /package/packages/model-runtime/src/{novita → providers/novita}/type.ts +0 -0
- /package/packages/model-runtime/src/{ollama → providers/ollama}/type.ts +0 -0
- /package/packages/model-runtime/src/{openai → providers/openai}/__snapshots__/index.test.ts.snap +0 -0
- /package/packages/model-runtime/src/{openai → providers/openai}/fixtures/openai-models.json +0 -0
- /package/packages/model-runtime/src/{openrouter → providers/openrouter}/fixtures/frontendModels.json +0 -0
- /package/packages/model-runtime/src/{openrouter → providers/openrouter}/fixtures/models.json +0 -0
- /package/packages/model-runtime/src/{openrouter → providers/openrouter}/type.ts +0 -0
- /package/packages/model-runtime/src/{ppio → providers/ppio}/__snapshots__/index.test.ts.snap +0 -0
- /package/packages/model-runtime/src/{ppio → providers/ppio}/fixtures/models.json +0 -0
- /package/packages/model-runtime/src/{ppio → providers/ppio}/type.ts +0 -0
- /package/packages/model-runtime/src/{togetherai → providers/togetherai}/fixtures/models.json +0 -0
- /package/packages/model-runtime/src/{togetherai → providers/togetherai}/type.ts +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { AiModelType } from 'model-bank';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import type { ChatModelCard } from '@/types/llm';
|
|
5
|
+
|
|
6
|
+
import { IMAGE_GENERATION_MODEL_WHITELIST, postProcessModelList } from './postProcessModelList';
|
|
7
|
+
|
|
8
|
+
// Mock model-bank
|
|
9
|
+
vi.mock('model-bank', () => ({
|
|
10
|
+
CHAT_MODEL_IMAGE_GENERATION_PARAMS: {
|
|
11
|
+
max_tokens: 1000,
|
|
12
|
+
temperature: 0.7,
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('IMAGE_GENERATION_MODEL_WHITELIST', () => {
|
|
17
|
+
it('should contain expected whitelisted models', () => {
|
|
18
|
+
expect(IMAGE_GENERATION_MODEL_WHITELIST).toContain('gemini-2.5-flash-image-preview');
|
|
19
|
+
expect(IMAGE_GENERATION_MODEL_WHITELIST).toContain('gemini-2.5-flash-image-preview:free');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('postProcessModelList', () => {
|
|
24
|
+
const mockModels: ChatModelCard[] = [
|
|
25
|
+
{
|
|
26
|
+
id: 'gpt-3.5-turbo',
|
|
27
|
+
displayName: 'GPT-3.5 Turbo',
|
|
28
|
+
enabled: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'gemini-2.5-flash-image-preview',
|
|
32
|
+
displayName: 'Gemini 2.5 Flash Image Preview',
|
|
33
|
+
enabled: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'claude-3-opus',
|
|
37
|
+
displayName: 'Claude 3 Opus',
|
|
38
|
+
enabled: true,
|
|
39
|
+
type: 'chat' as AiModelType,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
it('should ensure all models have type field with default "chat"', async () => {
|
|
44
|
+
const result = await postProcessModelList(mockModels);
|
|
45
|
+
|
|
46
|
+
expect(result.length).toBeGreaterThanOrEqual(mockModels.length);
|
|
47
|
+
// Filter out generated image models for this test
|
|
48
|
+
const originalModels = result.filter((model) => !model.id.endsWith(':image'));
|
|
49
|
+
originalModels.forEach((model) => {
|
|
50
|
+
expect(model.type).toBeDefined();
|
|
51
|
+
if (!mockModels.find((m) => m.id === model.id)?.type) {
|
|
52
|
+
expect(model.type).toBe('chat');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should preserve existing type field', async () => {
|
|
58
|
+
const result = await postProcessModelList(mockModels);
|
|
59
|
+
const claudeModel = result.find((m) => m.id === 'claude-3-opus');
|
|
60
|
+
|
|
61
|
+
expect(claudeModel?.type).toBe('chat');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should use getModelTypeProperty when type is missing', async () => {
|
|
65
|
+
const modelsWithoutType: ChatModelCard[] = [
|
|
66
|
+
{
|
|
67
|
+
id: 'custom-model',
|
|
68
|
+
displayName: 'Custom Model',
|
|
69
|
+
enabled: true,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const getModelTypeProperty = vi.fn().mockResolvedValue('embedding' as AiModelType);
|
|
74
|
+
const result = await postProcessModelList(modelsWithoutType, getModelTypeProperty);
|
|
75
|
+
|
|
76
|
+
expect(getModelTypeProperty).toHaveBeenCalledWith('custom-model');
|
|
77
|
+
expect(result[0].type).toBe('embedding');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should generate image models for whitelisted models', async () => {
|
|
81
|
+
const result = await postProcessModelList(mockModels);
|
|
82
|
+
const imageModel = result.find((m) => m.id === 'gemini-2.5-flash-image-preview:image');
|
|
83
|
+
|
|
84
|
+
expect(imageModel).toBeDefined();
|
|
85
|
+
expect(imageModel?.type).toBe('image');
|
|
86
|
+
expect(imageModel?.displayName).toBe('Gemini 2.5 Flash Image Preview');
|
|
87
|
+
expect(imageModel?.enabled).toBe(true);
|
|
88
|
+
expect(imageModel?.parameters).toEqual({
|
|
89
|
+
max_tokens: 1000,
|
|
90
|
+
temperature: 0.7,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle models that partially match whitelist patterns', async () => {
|
|
95
|
+
const modelsWithPartialMatch: ChatModelCard[] = [
|
|
96
|
+
{
|
|
97
|
+
id: 'custom-gemini-2.5-flash-image-preview',
|
|
98
|
+
displayName: 'Custom Gemini',
|
|
99
|
+
enabled: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'gemini-2.5-flash-image-preview-custom',
|
|
103
|
+
displayName: 'Gemini Custom',
|
|
104
|
+
enabled: false,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const result = await postProcessModelList(modelsWithPartialMatch);
|
|
109
|
+
|
|
110
|
+
// Should generate image model for the one that ends with whitelist pattern
|
|
111
|
+
const imageModel = result.find((m) => m.id === 'custom-gemini-2.5-flash-image-preview:image');
|
|
112
|
+
expect(imageModel).toBeDefined();
|
|
113
|
+
expect(imageModel?.enabled).toBe(true);
|
|
114
|
+
|
|
115
|
+
// Should not generate for the one that doesn't end with whitelist pattern
|
|
116
|
+
const noImageModel = result.find((m) => m.id === 'gemini-2.5-flash-image-preview-custom:image');
|
|
117
|
+
expect(noImageModel).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle empty model list', async () => {
|
|
121
|
+
const result = await postProcessModelList([]);
|
|
122
|
+
expect(result).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should handle multiple whitelisted models', async () => {
|
|
126
|
+
const multipleWhitelistedModels: ChatModelCard[] = [
|
|
127
|
+
{
|
|
128
|
+
id: 'test-gemini-2.5-flash-image-preview',
|
|
129
|
+
displayName: 'Test Gemini',
|
|
130
|
+
enabled: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'another-gemini-2.5-flash-image-preview:free',
|
|
134
|
+
displayName: 'Another Gemini Free',
|
|
135
|
+
enabled: false,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const result = await postProcessModelList(multipleWhitelistedModels);
|
|
140
|
+
|
|
141
|
+
// Should have original models plus image versions
|
|
142
|
+
expect(result).toHaveLength(4);
|
|
143
|
+
|
|
144
|
+
const imageModel1 = result.find((m) => m.id === 'test-gemini-2.5-flash-image-preview:image');
|
|
145
|
+
const imageModel2 = result.find(
|
|
146
|
+
(m) => m.id === 'another-gemini-2.5-flash-image-preview:free:image',
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(imageModel1).toBeDefined();
|
|
150
|
+
expect(imageModel2).toBeDefined();
|
|
151
|
+
expect(imageModel1?.type).toBe('image');
|
|
152
|
+
expect(imageModel2?.type).toBe('image');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should preserve all original model properties in image versions', async () => {
|
|
156
|
+
const modelWithManyProps: ChatModelCard[] = [
|
|
157
|
+
{
|
|
158
|
+
id: 'gemini-2.5-flash-image-preview',
|
|
159
|
+
displayName: 'Gemini Flash',
|
|
160
|
+
enabled: true,
|
|
161
|
+
contextWindowTokens: 4096,
|
|
162
|
+
description: 'A flash model',
|
|
163
|
+
functionCall: true,
|
|
164
|
+
vision: true,
|
|
165
|
+
reasoning: false,
|
|
166
|
+
maxOutput: 2048,
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const result = await postProcessModelList(modelWithManyProps);
|
|
171
|
+
const imageModel = result.find((m) => m.id === 'gemini-2.5-flash-image-preview:image');
|
|
172
|
+
|
|
173
|
+
expect(imageModel).toMatchObject({
|
|
174
|
+
id: 'gemini-2.5-flash-image-preview:image',
|
|
175
|
+
displayName: 'Gemini Flash',
|
|
176
|
+
enabled: true,
|
|
177
|
+
contextWindowTokens: 4096,
|
|
178
|
+
description: 'A flash model',
|
|
179
|
+
functionCall: true,
|
|
180
|
+
vision: true,
|
|
181
|
+
reasoning: false,
|
|
182
|
+
maxOutput: 2048,
|
|
183
|
+
type: 'image',
|
|
184
|
+
parameters: {
|
|
185
|
+
max_tokens: 1000,
|
|
186
|
+
temperature: 0.7,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { StreamingResponse } from './response';
|
|
4
|
+
|
|
5
|
+
describe('StreamingResponse', () => {
|
|
6
|
+
it('should create Response with default headers', () => {
|
|
7
|
+
const mockStream = new ReadableStream();
|
|
8
|
+
const response = StreamingResponse(mockStream);
|
|
9
|
+
|
|
10
|
+
expect(response).toBeInstanceOf(Response);
|
|
11
|
+
expect(response.body).toBe(mockStream);
|
|
12
|
+
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
|
13
|
+
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
|
|
14
|
+
expect(response.headers.get('X-Accel-Buffering')).toBe('no');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should create Response with custom headers', () => {
|
|
18
|
+
const mockStream = new ReadableStream();
|
|
19
|
+
const customHeaders = {
|
|
20
|
+
'Custom-Header': 'custom-value',
|
|
21
|
+
'Authorization': 'Bearer token',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const response = StreamingResponse(mockStream, { headers: customHeaders });
|
|
25
|
+
|
|
26
|
+
expect(response).toBeInstanceOf(Response);
|
|
27
|
+
expect(response.body).toBe(mockStream);
|
|
28
|
+
|
|
29
|
+
// Default headers should still be present
|
|
30
|
+
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
|
31
|
+
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
|
|
32
|
+
expect(response.headers.get('X-Accel-Buffering')).toBe('no');
|
|
33
|
+
|
|
34
|
+
// Custom headers should be added
|
|
35
|
+
expect(response.headers.get('Custom-Header')).toBe('custom-value');
|
|
36
|
+
expect(response.headers.get('Authorization')).toBe('Bearer token');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should allow custom headers to override default headers', () => {
|
|
40
|
+
const mockStream = new ReadableStream();
|
|
41
|
+
const overrideHeaders = {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Cache-Control': 'max-age=3600',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const response = StreamingResponse(mockStream, { headers: overrideHeaders });
|
|
47
|
+
|
|
48
|
+
expect(response.headers.get('Content-Type')).toBe('application/json');
|
|
49
|
+
expect(response.headers.get('Cache-Control')).toBe('max-age=3600');
|
|
50
|
+
expect(response.headers.get('X-Accel-Buffering')).toBe('no');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle empty options object', () => {
|
|
54
|
+
const mockStream = new ReadableStream();
|
|
55
|
+
const response = StreamingResponse(mockStream, {});
|
|
56
|
+
|
|
57
|
+
expect(response).toBeInstanceOf(Response);
|
|
58
|
+
expect(response.body).toBe(mockStream);
|
|
59
|
+
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
|
60
|
+
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
|
|
61
|
+
expect(response.headers.get('X-Accel-Buffering')).toBe('no');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle options with empty headers', () => {
|
|
65
|
+
const mockStream = new ReadableStream();
|
|
66
|
+
const response = StreamingResponse(mockStream, { headers: {} });
|
|
67
|
+
|
|
68
|
+
expect(response).toBeInstanceOf(Response);
|
|
69
|
+
expect(response.body).toBe(mockStream);
|
|
70
|
+
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
|
71
|
+
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
|
|
72
|
+
expect(response.headers.get('X-Accel-Buffering')).toBe('no');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should work with actual ReadableStream data', async () => {
|
|
76
|
+
const testData = 'data: {"test": "value"}\n\n';
|
|
77
|
+
const encoder = new TextEncoder();
|
|
78
|
+
|
|
79
|
+
const stream = new ReadableStream({
|
|
80
|
+
start(controller) {
|
|
81
|
+
controller.enqueue(encoder.encode(testData));
|
|
82
|
+
controller.close();
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const response = StreamingResponse(stream);
|
|
87
|
+
const responseText = await response.text();
|
|
88
|
+
|
|
89
|
+
expect(responseText).toBe(testData);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { ILobeAgentRuntimeErrorType } from '@/libs/model-runtime';
|
|
1
|
+
import type { ILobeAgentRuntimeErrorType } from '@lobechat/model-runtime';
|
|
2
|
+
import type { IPluginErrorType } from '@lobehub/chat-plugin-sdk';
|
|
4
3
|
|
|
5
4
|
import { ErrorType } from '../fetch';
|
|
6
5
|
import { GroundingSearch } from '../search';
|
|
@@ -47,6 +47,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
|
|
|
47
47
|
azureai?: AzureOpenAIKeyVault;
|
|
48
48
|
baichuan?: OpenAICompatibleKeyVault;
|
|
49
49
|
bedrock?: AWSBedrockKeyVault;
|
|
50
|
+
bfl?: any;
|
|
50
51
|
cloudflare?: CloudflareKeyVault;
|
|
51
52
|
cohere?: OpenAICompatibleKeyVault;
|
|
52
53
|
cometapi?: OpenAICompatibleKeyVault;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ModelProviderKey } from '
|
|
2
|
-
import { AiFullModelCard } from '
|
|
3
|
-
|
|
1
|
+
import type { ModelProviderKey } from '@lobechat/model-runtime';
|
|
2
|
+
import { AiFullModelCard } from 'model-bank';
|
|
3
|
+
|
|
4
|
+
import { ChatModelCard } from '../../llm';
|
|
4
5
|
|
|
5
6
|
export interface ProviderConfig {
|
|
6
7
|
/**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { GlobalLLMProviderKey } from '@lobechat/types';
|
|
3
4
|
import { InputPassword, Markdown } from '@lobehub/ui';
|
|
4
5
|
import { createStyles } from 'antd-style';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
6
7
|
|
|
7
8
|
import { HuggingFaceProviderCard } from '@/config/modelProviders';
|
|
8
|
-
import { GlobalLLMProviderKey } from '@/types/user/settings';
|
|
9
9
|
|
|
10
10
|
import { KeyVaultsConfigKey, LLMProviderApiTokenKey } from '../../const';
|
|
11
11
|
import { ProviderItem } from '../../type';
|
|
@@ -14,18 +14,16 @@ import {
|
|
|
14
14
|
LobeOpenRouterAI,
|
|
15
15
|
LobePerplexityAI,
|
|
16
16
|
LobeQwenAI,
|
|
17
|
-
|
|
17
|
+
LobeStepfunAI,
|
|
18
18
|
LobeTogetherAI,
|
|
19
19
|
LobeZeroOneAI,
|
|
20
20
|
LobeZhipuAI,
|
|
21
21
|
ModelProvider,
|
|
22
|
+
ModelRuntime,
|
|
22
23
|
} from '@lobechat/model-runtime';
|
|
23
|
-
import { ModelRuntime } from '@lobechat/model-runtime';
|
|
24
24
|
import { ClientSecretPayload } from '@lobechat/types';
|
|
25
25
|
import { describe, expect, it, vi } from 'vitest';
|
|
26
26
|
|
|
27
|
-
import { LobeStepfunAI } from '@/libs/model-runtime/stepfun';
|
|
28
|
-
|
|
29
27
|
import { initModelRuntimeWithUserPayload } from './index';
|
|
30
28
|
|
|
31
29
|
// 模拟依赖项
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
LobeZeroOneAI,
|
|
18
18
|
LobeZhipuAI,
|
|
19
19
|
ModelProvider,
|
|
20
|
+
ModelRuntime,
|
|
20
21
|
} from '@lobechat/model-runtime';
|
|
21
|
-
import { ModelRuntime } from '@lobechat/model-runtime';
|
|
22
22
|
import { ChatErrorType } from '@lobechat/types';
|
|
23
23
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
24
24
|
import { act } from '@testing-library/react';
|
|
@@ -67,9 +67,14 @@ vi.mock('@/utils/imageToBase64', () => ({
|
|
|
67
67
|
imageUrlToBase64: vi.fn(),
|
|
68
68
|
}));
|
|
69
69
|
|
|
70
|
-
vi.mock('
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
vi.mock('@lobechat/model-runtime', async (importOriginal) => {
|
|
71
|
+
const actual = await importOriginal();
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...(actual as any),
|
|
75
|
+
parseDataUri: vi.fn(),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
73
78
|
|
|
74
79
|
afterEach(() => {
|
|
75
80
|
vi.restoreAllMocks();
|
|
@@ -92,7 +97,7 @@ beforeEach(async () => {
|
|
|
92
97
|
// Set default mock return values for image processing utilities
|
|
93
98
|
const { isLocalUrl } = await import('@/utils/url');
|
|
94
99
|
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
|
|
95
|
-
const { parseDataUri } = await import('
|
|
100
|
+
const { parseDataUri } = await import('@lobechat/model-runtime');
|
|
96
101
|
|
|
97
102
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
98
103
|
vi.mocked(isLocalUrl).mockReturnValue(false);
|
|
@@ -404,7 +409,7 @@ describe('ChatService', () => {
|
|
|
404
409
|
it('should convert local image URLs to base64 and call processImageList', async () => {
|
|
405
410
|
const { isLocalUrl } = await import('@/utils/url');
|
|
406
411
|
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
|
|
407
|
-
const { parseDataUri } = await import('
|
|
412
|
+
const { parseDataUri } = await import('@lobechat/model-runtime');
|
|
408
413
|
|
|
409
414
|
// Mock for local URL
|
|
410
415
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
@@ -490,7 +495,7 @@ describe('ChatService', () => {
|
|
|
490
495
|
it('should not convert remote URLs to base64 and call processImageList', async () => {
|
|
491
496
|
const { isLocalUrl } = await import('@/utils/url');
|
|
492
497
|
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
|
|
493
|
-
const { parseDataUri } = await import('
|
|
498
|
+
const { parseDataUri } = await import('@lobechat/model-runtime');
|
|
494
499
|
|
|
495
500
|
// Mock for remote URL
|
|
496
501
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
@@ -570,7 +575,7 @@ describe('ChatService', () => {
|
|
|
570
575
|
it('should handle mixed local and remote URLs correctly', async () => {
|
|
571
576
|
const { isLocalUrl } = await import('@/utils/url');
|
|
572
577
|
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
|
|
573
|
-
const { parseDataUri } = await import('
|
|
578
|
+
const { parseDataUri } = await import('@lobechat/model-runtime');
|
|
574
579
|
|
|
575
580
|
// Mock parseDataUri to always return url type
|
|
576
581
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { DEFAULT_SETTINGS } from '@lobechat/const';
|
|
2
|
+
import { UserSettings } from '@lobechat/types';
|
|
1
3
|
import type { PartialDeep } from 'type-fest';
|
|
2
4
|
|
|
3
|
-
import { DEFAULT_SETTINGS } from '@/const/settings';
|
|
4
|
-
import { UserSettings } from '@/types/user/settings';
|
|
5
|
-
|
|
6
5
|
export interface UserSettingsState {
|
|
7
6
|
defaultSettings: UserSettings;
|
|
8
7
|
settings: PartialDeep<UserSettings>;
|
package/tsconfig.json
CHANGED
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
"baseUrl": ".",
|
|
19
19
|
"types": ["vitest/globals", "@serwist/next/typings"],
|
|
20
20
|
"paths": {
|
|
21
|
-
"@/libs/model-runtime": ["./packages/model-runtime/src/index.ts"],
|
|
22
|
-
"@/libs/model-runtime/*": ["./packages/model-runtime/src/*"],
|
|
23
21
|
"@/database/*": ["./packages/database/src/*", "./src/database/*"],
|
|
24
22
|
"@/const/*": ["./packages/const/src/*", "./src/const/*"],
|
|
25
23
|
"@/utils/*": ["./packages/utils/src/*", "./src/utils/*"],
|
package/vitest.config.mts
CHANGED
|
@@ -9,7 +9,6 @@ export default defineConfig({
|
|
|
9
9
|
test: {
|
|
10
10
|
alias: {
|
|
11
11
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
12
|
-
'@/libs/model-runtime': resolve(__dirname, './packages/model-runtime/src'),
|
|
13
12
|
'@/database/_deprecated': resolve(__dirname, './src/database/_deprecated'),
|
|
14
13
|
'@/database': resolve(__dirname, './packages/database/src'),
|
|
15
14
|
'@/utils/client/switchLang': resolve(__dirname, './src/utils/client/switchLang'),
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { LobeAnthropicAI } from '../anthropic';
|
|
2
|
-
import { LobeAzureAI } from '../azureai';
|
|
3
|
-
import { LobeCloudflareAI } from '../cloudflare';
|
|
4
|
-
import { LobeFalAI } from '../fal';
|
|
5
|
-
import { LobeGoogleAI } from '../google';
|
|
6
|
-
import { LobeOpenAI } from '../openai';
|
|
7
|
-
import { LobeXAI } from '../xai';
|
|
8
|
-
|
|
9
|
-
export const baseRuntimeMap = {
|
|
10
|
-
anthropic: LobeAnthropicAI,
|
|
11
|
-
azure: LobeAzureAI,
|
|
12
|
-
cloudflare: LobeCloudflareAI,
|
|
13
|
-
fal: LobeFalAI,
|
|
14
|
-
google: LobeGoogleAI,
|
|
15
|
-
openai: LobeOpenAI,
|
|
16
|
-
xai: LobeXAI,
|
|
17
|
-
};
|
/package/packages/model-runtime/src/{RouterRuntime → core/RouterRuntime}/createRuntime.test.ts
RENAMED
|
File without changes
|
/package/packages/model-runtime/src/{utils → core}/streams/__snapshots__/protocol.test.ts.snap
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{anthropic → providers/anthropic}/handleAnthropicError.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{novita → providers/novita}/__snapshots__/index.test.ts.snap
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{openai → providers/openai}/__snapshots__/index.test.ts.snap
RENAMED
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{openrouter → providers/openrouter}/fixtures/frontendModels.json
RENAMED
|
File without changes
|
/package/packages/model-runtime/src/{openrouter → providers/openrouter}/fixtures/models.json
RENAMED
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{ppio → providers/ppio}/__snapshots__/index.test.ts.snap
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/packages/model-runtime/src/{togetherai → providers/togetherai}/fixtures/models.json
RENAMED
|
File without changes
|
|
File without changes
|