@lobehub/lobehub 2.0.0-next.101 → 2.0.0-next.103
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/package.json +1 -1
- package/packages/database/src/models/aiModel.ts +1 -0
- package/packages/model-bank/package.json +1 -0
- package/packages/model-bank/src/aiModels/aihubmix.ts +29 -0
- package/packages/model-bank/src/aiModels/google.ts +72 -10
- package/packages/model-bank/src/aiModels/index.ts +3 -0
- package/packages/model-bank/src/aiModels/infiniai.ts +5 -22
- package/packages/model-bank/src/aiModels/ollamacloud.ts +13 -0
- package/packages/model-bank/src/aiModels/siliconcloud.ts +0 -61
- package/packages/model-bank/src/aiModels/vertexai.ts +90 -1
- package/packages/model-bank/src/aiModels/zenmux.ts +1426 -0
- package/packages/model-bank/src/const/modelProvider.ts +1 -0
- package/packages/model-bank/src/standard-parameters/index.ts +9 -0
- package/packages/model-bank/src/types/aiModel.ts +1 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +2 -2
- package/packages/model-runtime/src/core/streams/google/index.ts +7 -2
- package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +166 -166
- package/packages/model-runtime/src/index.ts +1 -1
- package/packages/model-runtime/src/providers/google/createImage.ts +1 -0
- package/packages/model-runtime/src/providers/google/index.ts +11 -1
- package/packages/model-runtime/src/providers/zenmux/index.test.ts +320 -0
- package/packages/model-runtime/src/providers/zenmux/index.ts +84 -0
- package/packages/model-runtime/src/runtimeMap.ts +2 -0
- package/packages/types/src/user/settings/keyVaults.ts +1 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +16 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ModelSelect/index.tsx +10 -9
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ResolutionSelect.tsx +88 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +9 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -3
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +2 -7
- package/src/components/ModelSelect/NewModelBadge.tsx +23 -0
- package/src/components/ModelSelect/index.tsx +4 -0
- package/src/config/modelProviders/index.ts +3 -0
- package/src/config/modelProviders/zenmux.ts +21 -0
- package/src/envs/llm.ts +6 -0
- package/src/locales/default/image.ts +8 -0
- package/src/store/aiInfra/slices/aiProvider/action.ts +3 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +3 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +11 -0
|
@@ -19,6 +19,7 @@ import ImageUrl from './components/ImageUrl';
|
|
|
19
19
|
import ImageUrlsUpload from './components/ImageUrlsUpload';
|
|
20
20
|
import ModelSelect from './components/ModelSelect';
|
|
21
21
|
import QualitySelect from './components/QualitySelect';
|
|
22
|
+
import ResolutionSelect from './components/ResolutionSelect';
|
|
22
23
|
import SeedNumberInput from './components/SeedNumberInput';
|
|
23
24
|
import SizeSelect from './components/SizeSelect';
|
|
24
25
|
import StepsSliderInput from './components/StepsSliderInput';
|
|
@@ -54,6 +55,7 @@ const ConfigPanel = memo(() => {
|
|
|
54
55
|
const isSupportImageUrl = useImageStore(isSupportedParamSelector('imageUrl'));
|
|
55
56
|
const isSupportSize = useImageStore(isSupportedParamSelector('size'));
|
|
56
57
|
const isSupportQuality = useImageStore(isSupportedParamSelector('quality'));
|
|
58
|
+
const isSupportResolution = useImageStore(isSupportedParamSelector('resolution'));
|
|
57
59
|
const isSupportSeed = useImageStore(isSupportedParamSelector('seed'));
|
|
58
60
|
const isSupportSteps = useImageStore(isSupportedParamSelector('steps'));
|
|
59
61
|
const isSupportCfg = useImageStore(isSupportedParamSelector('cfg'));
|
|
@@ -78,6 +80,7 @@ const ConfigPanel = memo(() => {
|
|
|
78
80
|
isSupportImageUrl,
|
|
79
81
|
isSupportSize,
|
|
80
82
|
isSupportQuality,
|
|
83
|
+
isSupportResolution,
|
|
81
84
|
isSupportSeed,
|
|
82
85
|
isSupportSteps,
|
|
83
86
|
isSupportCfg,
|
|
@@ -168,6 +171,12 @@ const ConfigPanel = memo(() => {
|
|
|
168
171
|
</ConfigItemLayout>
|
|
169
172
|
)}
|
|
170
173
|
|
|
174
|
+
{isSupportResolution && (
|
|
175
|
+
<ConfigItemLayout label={t('config.resolution.label')}>
|
|
176
|
+
<ResolutionSelect />
|
|
177
|
+
</ConfigItemLayout>
|
|
178
|
+
)}
|
|
179
|
+
|
|
171
180
|
{showDimensionControl && <DimensionControlGroup />}
|
|
172
181
|
|
|
173
182
|
{isSupportSteps && (
|
|
@@ -22,7 +22,7 @@ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfi
|
|
|
22
22
|
export const useCategory = () => {
|
|
23
23
|
const { t } = useTranslation('setting');
|
|
24
24
|
const mobile = useServerConfigStore((s) => s.isMobile);
|
|
25
|
-
const { enableSTT, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
|
25
|
+
const { enableSTT, hideDocs, showAiImage } = useServerConfigStore(featureFlagsSelectors);
|
|
26
26
|
|
|
27
27
|
const cateItems: MenuProps['items'] = useMemo(
|
|
28
28
|
() =>
|
|
@@ -50,7 +50,7 @@ export const useCategory = () => {
|
|
|
50
50
|
key: SettingsTabs.Provider,
|
|
51
51
|
label: t('tab.provider'),
|
|
52
52
|
},
|
|
53
|
-
{
|
|
53
|
+
showAiImage && {
|
|
54
54
|
icon: <Icon icon={ImageIcon} />,
|
|
55
55
|
key: SettingsTabs.Image,
|
|
56
56
|
label: t('tab.image'),
|
|
@@ -84,7 +84,7 @@ export const useCategory = () => {
|
|
|
84
84
|
label: t('tab.about'),
|
|
85
85
|
},
|
|
86
86
|
].filter(Boolean) as MenuProps['items'],
|
|
87
|
-
[t, enableSTT, hideDocs, mobile],
|
|
87
|
+
[t, enableSTT, hideDocs, mobile, showAiImage],
|
|
88
88
|
);
|
|
89
89
|
|
|
90
90
|
return cateItems;
|
|
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
9
9
|
import { Flexbox } from 'react-layout-kit';
|
|
10
10
|
|
|
11
11
|
import { ModelInfoTags } from '@/components/ModelSelect';
|
|
12
|
+
import NewModelBadge from '@/components/ModelSelect/NewModelBadge';
|
|
12
13
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
13
14
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
|
14
15
|
import { formatPriceByCurrency } from '@/utils/format';
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
getTextInputUnitRate,
|
|
18
19
|
getTextOutputUnitRate,
|
|
19
20
|
} from '@/utils/pricing';
|
|
20
|
-
import { isNewReleaseDate } from '@/utils/time';
|
|
21
21
|
|
|
22
22
|
import ModelConfigModal from './ModelConfigModal';
|
|
23
23
|
import { ProviderSettingsContext } from './ProviderSettingsContext';
|
|
@@ -163,12 +163,7 @@ const ModelItem = memo<ModelItemProps>(
|
|
|
163
163
|
|
|
164
164
|
const isMobile = useIsMobile();
|
|
165
165
|
|
|
166
|
-
const NewTag =
|
|
167
|
-
releasedAt && isNewReleaseDate(releasedAt) ? (
|
|
168
|
-
<Tag color="blue" style={{ marginLeft: 8 }}>
|
|
169
|
-
{t('new', { ns: 'common' })}
|
|
170
|
-
</Tag>
|
|
171
|
-
) : null;
|
|
166
|
+
const NewTag = <NewModelBadge releasedAt={releasedAt} />;
|
|
172
167
|
|
|
173
168
|
const ModelIdTag = (
|
|
174
169
|
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Tag } from '@lobehub/ui';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
import { isNewReleaseDate } from '@/utils/time';
|
|
6
|
+
|
|
7
|
+
interface NewModelBadgeProps {
|
|
8
|
+
releasedAt?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const NewModelBadge = memo<NewModelBadgeProps>(({ releasedAt }) => {
|
|
12
|
+
const { t } = useTranslation('common');
|
|
13
|
+
|
|
14
|
+
if (!releasedAt || !isNewReleaseDate(releasedAt)) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Tag color="blue" size="small">
|
|
18
|
+
{t('new')}
|
|
19
|
+
</Tag>
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export default NewModelBadge;
|
|
@@ -21,6 +21,8 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
21
21
|
import { AiProviderSourceType } from '@/types/aiProvider';
|
|
22
22
|
import { formatTokenNumber } from '@/utils/format';
|
|
23
23
|
|
|
24
|
+
import NewModelBadge from './NewModelBadge';
|
|
25
|
+
|
|
24
26
|
export const TAG_CLASSNAME = 'lobe-model-info-tags';
|
|
25
27
|
|
|
26
28
|
const useStyles = createStyles(({ css, token }) => ({
|
|
@@ -179,6 +181,7 @@ interface ModelItemRenderProps extends ChatModelCard {
|
|
|
179
181
|
|
|
180
182
|
export const ModelItemRender = memo<ModelItemRenderProps>(({ showInfoTag = true, ...model }) => {
|
|
181
183
|
const { mobile } = useResponsive();
|
|
184
|
+
|
|
182
185
|
return (
|
|
183
186
|
<Flexbox
|
|
184
187
|
align={'center'}
|
|
@@ -202,6 +205,7 @@ export const ModelItemRender = memo<ModelItemRenderProps>(({ showInfoTag = true,
|
|
|
202
205
|
<Text style={mobile ? { maxWidth: '60vw', overflowX: 'auto', whiteSpace: 'nowrap' } : {}}>
|
|
203
206
|
{model.displayName || model.id}
|
|
204
207
|
</Text>
|
|
208
|
+
<NewModelBadge releasedAt={model.releasedAt} />
|
|
205
209
|
</Flexbox>
|
|
206
210
|
{showInfoTag && <ModelInfoTags {...model} />}
|
|
207
211
|
</Flexbox>
|
|
@@ -64,6 +64,7 @@ import VolcengineProvider from './volcengine';
|
|
|
64
64
|
import WenxinProvider from './wenxin';
|
|
65
65
|
import XAIProvider from './xai';
|
|
66
66
|
import XinferenceProvider from './xinference';
|
|
67
|
+
import ZenMuxProvider from './zenmux';
|
|
67
68
|
import ZeroOneProvider from './zeroone';
|
|
68
69
|
import ZhiPuProvider from './zhipu';
|
|
69
70
|
|
|
@@ -190,6 +191,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
|
|
|
190
191
|
CometAPIProvider,
|
|
191
192
|
VercelAIGatewayProvider,
|
|
192
193
|
CerebrasProvider,
|
|
194
|
+
ZenMuxProvider,
|
|
193
195
|
];
|
|
194
196
|
|
|
195
197
|
export const filterEnabledModels = (provider: ModelProviderCard) => {
|
|
@@ -266,5 +268,6 @@ export { default as VolcengineProviderCard } from './volcengine';
|
|
|
266
268
|
export { default as WenxinProviderCard } from './wenxin';
|
|
267
269
|
export { default as XAIProviderCard } from './xai';
|
|
268
270
|
export { default as XinferenceProviderCard } from './xinference';
|
|
271
|
+
export { default as ZenMuxProviderCard } from './zenmux';
|
|
269
272
|
export { default as ZeroOneProviderCard } from './zeroone';
|
|
270
273
|
export { default as ZhiPuProviderCard } from './zhipu';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ModelProviderCard } from '@/types/llm';
|
|
2
|
+
|
|
3
|
+
const ZenMux: ModelProviderCard = {
|
|
4
|
+
chatModels: [],
|
|
5
|
+
checkModel: 'openai/gpt-5-nano',
|
|
6
|
+
description:
|
|
7
|
+
'ZenMux 是一个统一的 AI 服务聚合平台,支持 OpenAI、Anthropic、Google VertexAI 等多种主流 AI 服务接口。提供灵活的路由能力,让您可以轻松切换和管理不同的 AI 模型。',
|
|
8
|
+
id: 'zenmux',
|
|
9
|
+
name: 'ZenMux',
|
|
10
|
+
settings: {
|
|
11
|
+
disableBrowserRequest: true, // CORS error
|
|
12
|
+
proxyUrl: {
|
|
13
|
+
placeholder: 'https://zenmux.ai',
|
|
14
|
+
},
|
|
15
|
+
sdkType: 'router',
|
|
16
|
+
showModelFetcher: true,
|
|
17
|
+
},
|
|
18
|
+
url: 'https://zenmux.ai',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default ZenMux;
|
package/src/envs/llm.ts
CHANGED
|
@@ -209,6 +209,9 @@ export const getLLMConfig = () => {
|
|
|
209
209
|
|
|
210
210
|
ENABLED_CEREBRAS: z.boolean(),
|
|
211
211
|
CEREBRAS_API_KEY: z.string().optional(),
|
|
212
|
+
|
|
213
|
+
ENABLED_ZENMUX: z.boolean(),
|
|
214
|
+
ZENMUX_API_KEY: z.string().optional(),
|
|
212
215
|
},
|
|
213
216
|
runtimeEnv: {
|
|
214
217
|
API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
|
|
@@ -416,6 +419,9 @@ export const getLLMConfig = () => {
|
|
|
416
419
|
|
|
417
420
|
ENABLED_CEREBRAS: !!process.env.CEREBRAS_API_KEY,
|
|
418
421
|
CEREBRAS_API_KEY: process.env.CEREBRAS_API_KEY,
|
|
422
|
+
|
|
423
|
+
ENABLED_ZENMUX: !!process.env.ZENMUX_API_KEY,
|
|
424
|
+
ZENMUX_API_KEY: process.env.ZENMUX_API_KEY,
|
|
419
425
|
},
|
|
420
426
|
});
|
|
421
427
|
};
|
|
@@ -40,6 +40,7 @@ export type ProviderModelListItem = {
|
|
|
40
40
|
parameters?: ModelParamsSchema;
|
|
41
41
|
pricePerImage?: number;
|
|
42
42
|
pricing?: Pricing;
|
|
43
|
+
releasedAt?: string;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
type ModelNormalizer = (model: EnabledAiModel) => Promise<ProviderModelListItem>;
|
|
@@ -67,6 +68,7 @@ export const normalizeChatModel = (model: EnabledAiModel): ProviderModelListItem
|
|
|
67
68
|
contextWindowTokens: model.contextWindowTokens,
|
|
68
69
|
displayName: model.displayName ?? '',
|
|
69
70
|
id: model.id,
|
|
71
|
+
releasedAt: model.releasedAt,
|
|
70
72
|
});
|
|
71
73
|
|
|
72
74
|
export const normalizeImageModel = async (
|
|
@@ -107,6 +109,7 @@ export const normalizeImageModel = async (
|
|
|
107
109
|
contextWindowTokens: model.contextWindowTokens,
|
|
108
110
|
displayName: model.displayName ?? '',
|
|
109
111
|
id: model.id,
|
|
112
|
+
releasedAt: model.releasedAt,
|
|
110
113
|
...(parameters && { parameters }),
|
|
111
114
|
...(description && { description }),
|
|
112
115
|
...(pricing && { pricing }),
|
|
@@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react';
|
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { aiChatService } from '@/services/aiChat';
|
|
5
|
+
import { getSessionStoreState } from '@/store/session';
|
|
5
6
|
|
|
6
7
|
import { useChatStore } from '../../../../store';
|
|
7
8
|
import { TEST_CONTENT, TEST_IDS, createMockMessage } from './fixtures';
|
|
@@ -30,6 +31,8 @@ beforeEach(() => {
|
|
|
30
31
|
resetTestEnvironment();
|
|
31
32
|
setupMockSelectors();
|
|
32
33
|
spyOnMessageService();
|
|
34
|
+
const sessionStore = getSessionStoreState();
|
|
35
|
+
vi.spyOn(sessionStore, 'triggerSessionUpdate').mockResolvedValue(undefined);
|
|
33
36
|
|
|
34
37
|
act(() => {
|
|
35
38
|
useChatStore.setState({
|
|
@@ -530,6 +530,17 @@ export const streamingExecutor: StateCreator<
|
|
|
530
530
|
get().completeOperation(reasoningOperationId);
|
|
531
531
|
reasoningOperationId = undefined;
|
|
532
532
|
}
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
case 'stop': {
|
|
537
|
+
// Complete reasoning operation when receiving stop signal
|
|
538
|
+
if (!duration && reasoningOperationId) {
|
|
539
|
+
duration = Date.now() - thinkingStartAt;
|
|
540
|
+
get().completeOperation(reasoningOperationId);
|
|
541
|
+
reasoningOperationId = undefined;
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
533
544
|
}
|
|
534
545
|
}
|
|
535
546
|
},
|