@lobehub/chat 1.116.4 → 1.117.1
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 +117 -0
- package/changelog/v1.json +21 -0
- package/locales/ar/models.json +3 -0
- package/locales/bg-BG/models.json +3 -0
- package/locales/de-DE/models.json +3 -0
- package/locales/en-US/models.json +3 -0
- package/locales/es-ES/models.json +3 -0
- package/locales/fa-IR/models.json +3 -0
- package/locales/fr-FR/models.json +3 -0
- package/locales/it-IT/models.json +3 -0
- package/locales/ja-JP/models.json +3 -0
- package/locales/ko-KR/models.json +3 -0
- package/locales/nl-NL/models.json +3 -0
- package/locales/pl-PL/models.json +3 -0
- package/locales/pt-BR/models.json +3 -0
- package/locales/ru-RU/models.json +3 -0
- package/locales/tr-TR/models.json +3 -0
- package/locales/vi-VN/models.json +3 -0
- package/locales/zh-CN/models.json +3 -0
- package/locales/zh-TW/models.json +3 -0
- package/package.json +1 -2
- package/packages/const/src/image.ts +9 -0
- package/packages/database/vitest.config.mts +1 -0
- package/packages/database/vitest.config.server.mts +1 -0
- package/packages/file-loaders/package.json +1 -1
- package/packages/model-runtime/src/RouterRuntime/createRuntime.ts +11 -9
- package/packages/model-runtime/src/google/createImage.test.ts +657 -0
- package/packages/model-runtime/src/google/createImage.ts +152 -0
- package/packages/model-runtime/src/google/index.test.ts +0 -328
- package/packages/model-runtime/src/google/index.ts +3 -40
- package/packages/model-runtime/src/utils/modelParse.ts +2 -1
- package/packages/model-runtime/src/utils/openaiCompatibleFactory/createImage.ts +239 -0
- package/packages/model-runtime/src/utils/openaiCompatibleFactory/index.test.ts +22 -22
- package/packages/model-runtime/src/utils/openaiCompatibleFactory/index.ts +9 -116
- package/packages/model-runtime/src/utils/postProcessModelList.ts +55 -0
- package/packages/model-runtime/src/utils/streams/google-ai.test.ts +7 -7
- package/packages/model-runtime/src/utils/streams/google-ai.ts +15 -2
- package/packages/model-runtime/src/utils/streams/openai/openai.test.ts +41 -0
- package/packages/model-runtime/src/utils/streams/openai/openai.ts +38 -2
- package/packages/model-runtime/src/utils/streams/protocol.test.ts +32 -0
- package/packages/model-runtime/src/utils/streams/protocol.ts +7 -3
- package/packages/model-runtime/src/utils/usageConverter.test.ts +58 -0
- package/packages/model-runtime/src/utils/usageConverter.ts +5 -1
- package/packages/utils/vitest.config.mts +1 -0
- package/src/components/ChatItem/ChatItem.tsx +183 -0
- package/src/components/ChatItem/components/Actions.tsx +25 -0
- package/src/components/ChatItem/components/Avatar.tsx +50 -0
- package/src/components/ChatItem/components/BorderSpacing.tsx +13 -0
- package/src/components/ChatItem/components/ErrorContent.tsx +24 -0
- package/src/components/ChatItem/components/Loading.tsx +26 -0
- package/src/components/ChatItem/components/MessageContent.tsx +76 -0
- package/src/components/ChatItem/components/Title.tsx +43 -0
- package/src/components/ChatItem/index.ts +2 -0
- package/src/components/ChatItem/style.ts +208 -0
- package/src/components/ChatItem/type.ts +80 -0
- package/src/config/aiModels/google.ts +42 -22
- package/src/config/aiModels/openrouter.ts +33 -0
- package/src/config/aiModels/vertexai.ts +4 -4
- package/src/features/ChatItem/index.tsx +1 -1
- package/src/features/Conversation/Extras/Usage/UsageDetail/index.tsx +6 -0
- package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.test.ts +38 -0
- package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +13 -1
- package/src/locales/default/chat.ts +1 -0
- package/packages/model-runtime/src/UniformRuntime/index.ts +0 -117
@@ -0,0 +1,43 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
import { memo } from 'react';
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
4
|
+
|
5
|
+
import { useStyles } from '../style';
|
6
|
+
import { ChatItemProps } from '../type';
|
7
|
+
|
8
|
+
export interface TitleProps {
|
9
|
+
avatar: ChatItemProps['avatar'];
|
10
|
+
placement?: ChatItemProps['placement'];
|
11
|
+
showTitle?: ChatItemProps['showTitle'];
|
12
|
+
time?: ChatItemProps['time'];
|
13
|
+
}
|
14
|
+
|
15
|
+
const formatTime = (time: number): string => {
|
16
|
+
const now = dayjs();
|
17
|
+
const target = dayjs(time);
|
18
|
+
|
19
|
+
if (target.isSame(now, 'day')) {
|
20
|
+
return target.format('HH:mm:ss');
|
21
|
+
} else if (target.isSame(now, 'year')) {
|
22
|
+
return target.format('MM-DD HH:mm:ss');
|
23
|
+
} else {
|
24
|
+
return target.format('YYYY-MM-DD HH:mm:ss');
|
25
|
+
}
|
26
|
+
};
|
27
|
+
|
28
|
+
const Title = memo<TitleProps>(({ showTitle, placement, time, avatar }) => {
|
29
|
+
const { styles } = useStyles({ placement, showTitle, time });
|
30
|
+
|
31
|
+
return (
|
32
|
+
<Flexbox
|
33
|
+
className={styles.name}
|
34
|
+
direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
|
35
|
+
gap={4}
|
36
|
+
>
|
37
|
+
{showTitle ? avatar.title || 'untitled' : undefined}
|
38
|
+
{time && <time>{formatTime(time)}</time>}
|
39
|
+
</Flexbox>
|
40
|
+
);
|
41
|
+
});
|
42
|
+
|
43
|
+
export default Title;
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import { createStyles } from 'antd-style';
|
2
|
+
import { rgba } from 'polished';
|
3
|
+
|
4
|
+
export const useStyles = createStyles(
|
5
|
+
(
|
6
|
+
{ cx, css, token, responsive },
|
7
|
+
{
|
8
|
+
placement,
|
9
|
+
variant,
|
10
|
+
title,
|
11
|
+
avatarSize,
|
12
|
+
editing,
|
13
|
+
time,
|
14
|
+
}: {
|
15
|
+
avatarSize?: number;
|
16
|
+
editing?: boolean;
|
17
|
+
placement?: 'left' | 'right';
|
18
|
+
primary?: boolean;
|
19
|
+
showTitle?: boolean;
|
20
|
+
time?: number;
|
21
|
+
title?: string;
|
22
|
+
variant?: 'bubble' | 'docs';
|
23
|
+
},
|
24
|
+
) => {
|
25
|
+
const blockStylish = css`
|
26
|
+
padding-block: 8px;
|
27
|
+
padding-inline: 12px;
|
28
|
+
border: 1px solid ${rgba(token.colorBorderSecondary, 0.66)};
|
29
|
+
border-radius: ${token.borderRadiusLG}px;
|
30
|
+
|
31
|
+
background-color: ${token.colorBgContainer};
|
32
|
+
`;
|
33
|
+
|
34
|
+
const rawStylish = css`
|
35
|
+
padding-block-start: ${title ? 0 : '6px'};
|
36
|
+
`;
|
37
|
+
|
38
|
+
const rawContainerStylish = css`
|
39
|
+
margin-block-end: -16px;
|
40
|
+
transition: background-color 100ms ${token.motionEaseOut};
|
41
|
+
`;
|
42
|
+
|
43
|
+
const typeStylish = variant === 'bubble' ? blockStylish : rawStylish;
|
44
|
+
|
45
|
+
const editingStylish =
|
46
|
+
editing &&
|
47
|
+
css`
|
48
|
+
width: 100%;
|
49
|
+
`;
|
50
|
+
|
51
|
+
return {
|
52
|
+
actions: cx(
|
53
|
+
css`
|
54
|
+
flex: none;
|
55
|
+
align-self: ${variant === 'bubble'
|
56
|
+
? 'flex-end'
|
57
|
+
: placement === 'left'
|
58
|
+
? 'flex-start'
|
59
|
+
: 'flex-end'};
|
60
|
+
justify-content: ${placement === 'left' ? 'flex-end' : 'flex-start'};
|
61
|
+
`,
|
62
|
+
editing &&
|
63
|
+
css`
|
64
|
+
pointer-events: none !important;
|
65
|
+
opacity: 0 !important;
|
66
|
+
`,
|
67
|
+
),
|
68
|
+
avatarContainer: css`
|
69
|
+
position: relative;
|
70
|
+
flex: none;
|
71
|
+
width: ${avatarSize}px;
|
72
|
+
height: ${avatarSize}px;
|
73
|
+
`,
|
74
|
+
avatarGroupContainer: css`
|
75
|
+
width: ${avatarSize}px;
|
76
|
+
`,
|
77
|
+
container: cx(
|
78
|
+
variant === 'docs' && rawContainerStylish,
|
79
|
+
css`
|
80
|
+
position: relative;
|
81
|
+
|
82
|
+
width: 100%;
|
83
|
+
max-width: 100vw;
|
84
|
+
padding-block: 24px 12px;
|
85
|
+
padding-inline: 12px;
|
86
|
+
|
87
|
+
time {
|
88
|
+
display: inline-block;
|
89
|
+
white-space: nowrap;
|
90
|
+
}
|
91
|
+
|
92
|
+
div[role='menubar'] {
|
93
|
+
display: flex;
|
94
|
+
}
|
95
|
+
|
96
|
+
time,
|
97
|
+
div[role='menubar'] {
|
98
|
+
pointer-events: none;
|
99
|
+
opacity: 0;
|
100
|
+
transition: opacity 200ms ${token.motionEaseOut};
|
101
|
+
}
|
102
|
+
|
103
|
+
&:hover {
|
104
|
+
time,
|
105
|
+
div[role='menubar'] {
|
106
|
+
pointer-events: unset;
|
107
|
+
opacity: 1;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
${responsive.mobile} {
|
112
|
+
padding-block-start: ${variant === 'docs' ? '16px' : '12px'};
|
113
|
+
padding-inline: 8px;
|
114
|
+
}
|
115
|
+
`,
|
116
|
+
),
|
117
|
+
editingContainer: cx(
|
118
|
+
editingStylish,
|
119
|
+
css`
|
120
|
+
padding-block: 8px 12px;
|
121
|
+
padding-inline: 12px;
|
122
|
+
border: 1px solid ${token.colorBorderSecondary};
|
123
|
+
|
124
|
+
&:active,
|
125
|
+
&:hover {
|
126
|
+
border-color: ${token.colorBorder};
|
127
|
+
}
|
128
|
+
`,
|
129
|
+
variant === 'docs' &&
|
130
|
+
css`
|
131
|
+
border-radius: ${token.borderRadius}px;
|
132
|
+
background: ${token.colorFillQuaternary};
|
133
|
+
`,
|
134
|
+
),
|
135
|
+
editingInput: css`
|
136
|
+
width: 100%;
|
137
|
+
`,
|
138
|
+
errorContainer: css`
|
139
|
+
position: relative;
|
140
|
+
overflow: hidden;
|
141
|
+
width: 100%;
|
142
|
+
`,
|
143
|
+
|
144
|
+
loading: css`
|
145
|
+
position: absolute;
|
146
|
+
inset-block-end: 0;
|
147
|
+
inset-inline: ${placement === 'right' ? '-4px' : 'unset'}
|
148
|
+
${placement === 'left' ? '-4px' : 'unset'};
|
149
|
+
|
150
|
+
width: 16px;
|
151
|
+
height: 16px;
|
152
|
+
border-radius: 50%;
|
153
|
+
|
154
|
+
color: ${token.colorBgLayout};
|
155
|
+
|
156
|
+
background: ${token.colorPrimary};
|
157
|
+
`,
|
158
|
+
message: cx(
|
159
|
+
typeStylish,
|
160
|
+
css`
|
161
|
+
position: relative;
|
162
|
+
overflow: hidden;
|
163
|
+
max-width: 100%;
|
164
|
+
|
165
|
+
${responsive.mobile} {
|
166
|
+
width: 100%;
|
167
|
+
}
|
168
|
+
`,
|
169
|
+
),
|
170
|
+
messageContainer: cx(
|
171
|
+
editingStylish,
|
172
|
+
css`
|
173
|
+
position: relative;
|
174
|
+
overflow: hidden;
|
175
|
+
max-width: 100%;
|
176
|
+
margin-block-start: ${time ? -16 : 0}px;
|
177
|
+
|
178
|
+
${responsive.mobile} {
|
179
|
+
overflow-x: auto;
|
180
|
+
}
|
181
|
+
`,
|
182
|
+
),
|
183
|
+
messageContent: cx(
|
184
|
+
editingStylish,
|
185
|
+
css`
|
186
|
+
position: relative;
|
187
|
+
overflow: hidden;
|
188
|
+
max-width: 100%;
|
189
|
+
|
190
|
+
${responsive.mobile} {
|
191
|
+
flex-direction: column !important;
|
192
|
+
}
|
193
|
+
`,
|
194
|
+
),
|
195
|
+
messageExtra: cx('message-extra'),
|
196
|
+
name: css`
|
197
|
+
pointer-events: none;
|
198
|
+
|
199
|
+
margin-block-end: 6px;
|
200
|
+
|
201
|
+
font-size: 12px;
|
202
|
+
line-height: 1;
|
203
|
+
color: ${token.colorTextDescription};
|
204
|
+
text-align: ${placement === 'left' ? 'left' : 'right'};
|
205
|
+
`,
|
206
|
+
};
|
207
|
+
},
|
208
|
+
);
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import { AlertProps, AvatarProps, DivProps, MarkdownProps } from '@lobehub/ui';
|
2
|
+
import { EditableMessageProps, MetaData } from '@lobehub/ui/chat';
|
3
|
+
import { ReactNode } from 'react';
|
4
|
+
import { FlexboxProps } from 'react-layout-kit';
|
5
|
+
|
6
|
+
export interface ChatItemProps extends Omit<FlexboxProps, 'children' | 'onChange'> {
|
7
|
+
aboveMessage?: ReactNode;
|
8
|
+
/**
|
9
|
+
* @description Actions to be displayed in the chat item
|
10
|
+
*/
|
11
|
+
actions?: ReactNode;
|
12
|
+
actionsWrapWidth?: number;
|
13
|
+
/**
|
14
|
+
* @description Metadata for the avatar
|
15
|
+
*/
|
16
|
+
avatar: MetaData;
|
17
|
+
avatarAddon?: ReactNode;
|
18
|
+
avatarProps?: AvatarProps;
|
19
|
+
belowMessage?: ReactNode;
|
20
|
+
/**
|
21
|
+
* @description Whether the chat item is in editing mode
|
22
|
+
*/
|
23
|
+
editing?: boolean;
|
24
|
+
/**
|
25
|
+
* @description Props for Error render
|
26
|
+
*/
|
27
|
+
error?: AlertProps;
|
28
|
+
errorMessage?: ReactNode;
|
29
|
+
fontSize?: number;
|
30
|
+
/**
|
31
|
+
* @description Whether the chat item is in loading state
|
32
|
+
*/
|
33
|
+
loading?: boolean;
|
34
|
+
markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
|
35
|
+
/**
|
36
|
+
* @description The message content of the chat item
|
37
|
+
*/
|
38
|
+
message?: ReactNode;
|
39
|
+
messageExtra?: ReactNode;
|
40
|
+
onAvatarClick?: () => void;
|
41
|
+
/**
|
42
|
+
* @description Callback when the message content changes
|
43
|
+
* @param value - The new message content
|
44
|
+
*/
|
45
|
+
onChange?: (value: string) => void;
|
46
|
+
onDoubleClick?: DivProps['onDoubleClick'];
|
47
|
+
/**
|
48
|
+
* @description Callback when the editing mode changes
|
49
|
+
* @param editing - The new editing mode
|
50
|
+
*/
|
51
|
+
onEditingChange?: (editing: boolean) => void;
|
52
|
+
/**
|
53
|
+
* @default "..."
|
54
|
+
*/
|
55
|
+
placeholderMessage?: string;
|
56
|
+
/**
|
57
|
+
* @description The placement of the chat item
|
58
|
+
* @default 'left'
|
59
|
+
*/
|
60
|
+
placement?: 'left' | 'right';
|
61
|
+
/**
|
62
|
+
* @description Whether the chat item is primary
|
63
|
+
*/
|
64
|
+
primary?: boolean;
|
65
|
+
renderMessage?: (content: ReactNode) => ReactNode;
|
66
|
+
/**
|
67
|
+
* @description Whether to show the title of the chat item
|
68
|
+
*/
|
69
|
+
showTitle?: boolean;
|
70
|
+
text?: EditableMessageProps['text'];
|
71
|
+
/**
|
72
|
+
* @description The timestamp of the chat item
|
73
|
+
*/
|
74
|
+
time?: number;
|
75
|
+
/**
|
76
|
+
* @description The type of the chat item
|
77
|
+
* @default 'bubble'
|
78
|
+
*/
|
79
|
+
variant?: 'bubble' | 'docs';
|
80
|
+
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { CHAT_MODEL_IMAGE_GENERATION_PARAMS } from '@/const/image';
|
1
2
|
import { ModelParamsSchema } from '@/libs/standard-parameters';
|
2
3
|
import { AIChatModelCard, AIImageModelCard } from '@/types/aiModel';
|
3
4
|
|
@@ -194,21 +195,21 @@ const googleChatModels: AIChatModelCard[] = [
|
|
194
195
|
imageOutput: true,
|
195
196
|
vision: true,
|
196
197
|
},
|
197
|
-
contextWindowTokens: 32_768 +
|
198
|
+
contextWindowTokens: 32_768 + 8192,
|
198
199
|
description:
|
199
200
|
'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
|
200
201
|
displayName: 'Gemini 2.5 Flash Image Preview',
|
201
202
|
enabled: true,
|
202
203
|
id: 'gemini-2.5-flash-image-preview',
|
203
|
-
maxOutput:
|
204
|
+
maxOutput: 8192,
|
204
205
|
pricing: {
|
205
206
|
units: [
|
206
207
|
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
207
208
|
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
208
|
-
{ name: 'imageOutput', rate:
|
209
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
209
210
|
],
|
210
211
|
},
|
211
|
-
releasedAt: '2025-08-
|
212
|
+
releasedAt: '2025-08-26',
|
212
213
|
type: 'chat',
|
213
214
|
},
|
214
215
|
{
|
@@ -605,71 +606,90 @@ const imagenBaseParameters: ModelParamsSchema = {
|
|
605
606
|
prompt: { default: '' },
|
606
607
|
};
|
607
608
|
|
609
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
608
610
|
const googleImageModels: AIImageModelCard[] = [
|
609
611
|
{
|
610
|
-
|
611
|
-
|
612
|
+
displayName: 'Gemini 2.5 Flash Image Preview',
|
613
|
+
id: 'gemini-2.5-flash-image-preview:image',
|
612
614
|
enabled: true,
|
615
|
+
type: 'image',
|
616
|
+
description:
|
617
|
+
'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
|
618
|
+
releasedAt: '2025-08-26',
|
619
|
+
parameters: CHAT_MODEL_IMAGE_GENERATION_PARAMS,
|
620
|
+
pricing: {
|
621
|
+
units: [
|
622
|
+
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
623
|
+
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
624
|
+
{ name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
|
625
|
+
],
|
626
|
+
},
|
627
|
+
},
|
628
|
+
{
|
629
|
+
displayName: 'Imagen 4',
|
613
630
|
id: 'imagen-4.0-generate-001',
|
631
|
+
enabled: true,
|
632
|
+
type: 'image',
|
633
|
+
description: 'Imagen 4th generation text-to-image model series',
|
614
634
|
organization: 'Deepmind',
|
635
|
+
releasedAt: '2025-08-15',
|
615
636
|
parameters: imagenBaseParameters,
|
616
637
|
pricing: {
|
617
638
|
units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
|
618
639
|
},
|
619
|
-
releasedAt: '2024-08-14',
|
620
|
-
type: 'image',
|
621
640
|
},
|
622
641
|
{
|
623
|
-
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
624
642
|
displayName: 'Imagen 4 Ultra',
|
625
|
-
enabled: true,
|
626
643
|
id: 'imagen-4.0-ultra-generate-001',
|
644
|
+
enabled: true,
|
645
|
+
type: 'image',
|
646
|
+
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
627
647
|
organization: 'Deepmind',
|
648
|
+
releasedAt: '2025-08-15',
|
628
649
|
parameters: imagenBaseParameters,
|
629
650
|
pricing: {
|
630
651
|
units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
|
631
652
|
},
|
632
|
-
releasedAt: '2024-08-14',
|
633
|
-
type: 'image',
|
634
653
|
},
|
635
654
|
{
|
636
|
-
description: 'Imagen 4th generation text-to-image model series Fast version',
|
637
655
|
displayName: 'Imagen 4 Fast',
|
638
|
-
enabled: true,
|
639
656
|
id: 'imagen-4.0-fast-generate-001',
|
657
|
+
enabled: true,
|
658
|
+
type: 'image',
|
659
|
+
description: 'Imagen 4th generation text-to-image model series Fast version',
|
640
660
|
organization: 'Deepmind',
|
661
|
+
releasedAt: '2025-08-15',
|
641
662
|
parameters: imagenBaseParameters,
|
642
663
|
pricing: {
|
643
664
|
units: [{ name: 'imageGeneration', rate: 0.02, strategy: 'fixed', unit: 'image' }],
|
644
665
|
},
|
645
|
-
releasedAt: '2024-08-14',
|
646
|
-
type: 'image',
|
647
666
|
},
|
648
667
|
{
|
649
|
-
description: 'Imagen 4th generation text-to-image model series',
|
650
668
|
displayName: 'Imagen 4 Preview 06-06',
|
651
669
|
id: 'imagen-4.0-generate-preview-06-06',
|
670
|
+
type: 'image',
|
671
|
+
description: 'Imagen 4th generation text-to-image model series',
|
652
672
|
organization: 'Deepmind',
|
673
|
+
releasedAt: '2024-06-06',
|
653
674
|
parameters: imagenBaseParameters,
|
654
675
|
pricing: {
|
655
676
|
units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
|
656
677
|
},
|
657
|
-
releasedAt: '2024-06-06',
|
658
|
-
type: 'image',
|
659
678
|
},
|
660
679
|
{
|
661
|
-
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
662
680
|
displayName: 'Imagen 4 Ultra Preview 06-06',
|
663
681
|
id: 'imagen-4.0-ultra-generate-preview-06-06',
|
682
|
+
type: 'image',
|
683
|
+
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
664
684
|
organization: 'Deepmind',
|
685
|
+
releasedAt: '2025-06-11',
|
665
686
|
parameters: imagenBaseParameters,
|
666
687
|
pricing: {
|
667
688
|
units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
|
668
689
|
},
|
669
|
-
releasedAt: '2024-06-06',
|
670
|
-
type: 'image',
|
671
690
|
},
|
672
691
|
];
|
692
|
+
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
673
693
|
|
674
694
|
export const allModels = [...googleChatModels, ...googleImageModels];
|
675
695
|
|
@@ -11,6 +11,39 @@ const openrouterChatModels: AIChatModelCard[] = [
|
|
11
11
|
id: 'openrouter/auto',
|
12
12
|
type: 'chat',
|
13
13
|
},
|
14
|
+
{
|
15
|
+
abilities: {
|
16
|
+
imageOutput: true,
|
17
|
+
vision: true,
|
18
|
+
},
|
19
|
+
contextWindowTokens: 32_768 + 8192,
|
20
|
+
description: 'Gemini 2.5 Flash 实验模型,支持图像生成',
|
21
|
+
displayName: 'Gemini 2.5 Flash Image Preview',
|
22
|
+
id: 'google/gemini-2.5-flash-image-preview',
|
23
|
+
maxOutput: 8192,
|
24
|
+
pricing: {
|
25
|
+
units: [
|
26
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
27
|
+
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
28
|
+
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
29
|
+
],
|
30
|
+
},
|
31
|
+
releasedAt: '2025-08-26',
|
32
|
+
type: 'chat',
|
33
|
+
},
|
34
|
+
{
|
35
|
+
abilities: {
|
36
|
+
imageOutput: true,
|
37
|
+
vision: true,
|
38
|
+
},
|
39
|
+
contextWindowTokens: 32_768 + 8192,
|
40
|
+
description: 'Gemini 2.5 Flash 实验模型,支持图像生成',
|
41
|
+
displayName: 'Gemini 2.5 Flash Image Preview (free)',
|
42
|
+
id: 'google/gemini-2.5-flash-image-preview:free',
|
43
|
+
maxOutput: 8192,
|
44
|
+
releasedAt: '2025-08-26',
|
45
|
+
type: 'chat',
|
46
|
+
},
|
14
47
|
{
|
15
48
|
abilities: {
|
16
49
|
reasoning: true,
|
@@ -126,21 +126,21 @@ const vertexaiChatModels: AIChatModelCard[] = [
|
|
126
126
|
imageOutput: true,
|
127
127
|
vision: true,
|
128
128
|
},
|
129
|
-
contextWindowTokens: 32_768 +
|
129
|
+
contextWindowTokens: 32_768 + 8192,
|
130
130
|
description:
|
131
131
|
'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
|
132
132
|
displayName: 'Gemini 2.5 Flash Image Preview',
|
133
133
|
enabled: true,
|
134
134
|
id: 'gemini-2.5-flash-image-preview',
|
135
|
-
maxOutput:
|
135
|
+
maxOutput: 8192,
|
136
136
|
pricing: {
|
137
137
|
units: [
|
138
138
|
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
139
139
|
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
140
|
-
{ name: 'imageOutput', rate:
|
140
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
141
141
|
],
|
142
142
|
},
|
143
|
-
releasedAt: '2025-08-
|
143
|
+
releasedAt: '2025-08-26',
|
144
144
|
type: 'chat',
|
145
145
|
},
|
146
146
|
{
|
@@ -1,9 +1,9 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { ChatItemProps, ChatItem as ChatItemRaw } from '@lobehub/ui/chat';
|
4
3
|
import isEqual from 'fast-deep-equal';
|
5
4
|
import { memo, useMemo } from 'react';
|
6
5
|
|
6
|
+
import { ChatItemProps, ChatItem as ChatItemRaw } from '@/components/ChatItem';
|
7
7
|
import { isDesktop } from '@/const/version';
|
8
8
|
import { useElectronStore } from '@/store/electron';
|
9
9
|
import { electronSyncSelectors } from '@/store/electron/selectors';
|
@@ -61,6 +61,12 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
|
|
61
61
|
? detailTokens.outputReasoning.credit
|
62
62
|
: detailTokens.outputReasoning.token,
|
63
63
|
},
|
64
|
+
!!detailTokens.outputImage && {
|
65
|
+
color: theme.purple,
|
66
|
+
id: 'outputImage',
|
67
|
+
title: t('messages.tokenDetails.outputImage'),
|
68
|
+
value: isShowCredit ? detailTokens.outputImage.credit : detailTokens.outputImage.token,
|
69
|
+
},
|
64
70
|
!!detailTokens.outputAudio && {
|
65
71
|
color: theme.cyan9,
|
66
72
|
id: 'outputAudio',
|
@@ -143,6 +143,44 @@ describe('getDetailsToken', () => {
|
|
143
143
|
});
|
144
144
|
});
|
145
145
|
|
146
|
+
it('should handle outputImageTokens correctly', () => {
|
147
|
+
const usage = {
|
148
|
+
inputTextTokens: 100,
|
149
|
+
outputImageTokens: 60,
|
150
|
+
outputReasoningTokens: 30,
|
151
|
+
totalOutputTokens: 200,
|
152
|
+
totalTokens: 300,
|
153
|
+
} as ModelTokensUsage;
|
154
|
+
|
155
|
+
const result = getDetailsToken(usage, mockModelCard);
|
156
|
+
|
157
|
+
expect(result.outputImage).toEqual({
|
158
|
+
credit: 1, // 60 * 0.02 = 1.2 -> 1
|
159
|
+
id: 'outputImage',
|
160
|
+
token: 60,
|
161
|
+
});
|
162
|
+
|
163
|
+
expect(result.outputReasoning).toEqual({
|
164
|
+
credit: 1, // 30 * 0.02 = 0.6 -> 1
|
165
|
+
token: 30,
|
166
|
+
});
|
167
|
+
|
168
|
+
expect(result.outputText).toEqual({
|
169
|
+
credit: 2, // (200 - 30 - 60) * 0.02 = 2.2 -> 2
|
170
|
+
token: 110,
|
171
|
+
});
|
172
|
+
|
173
|
+
expect(result.totalOutput).toEqual({
|
174
|
+
credit: 4, // 200 * 0.02 = 4
|
175
|
+
token: 200,
|
176
|
+
});
|
177
|
+
|
178
|
+
expect(result.totalTokens).toEqual({
|
179
|
+
credit: 4, // total credit equals totalOutputCredit here
|
180
|
+
token: 300,
|
181
|
+
});
|
182
|
+
});
|
183
|
+
|
146
184
|
it('should handle inputCitationTokens correctly', () => {
|
147
185
|
const usage: ModelTokensUsage = {
|
148
186
|
inputCitationTokens: 75,
|
@@ -21,9 +21,14 @@ export const getDetailsToken = (
|
|
21
21
|
|
22
22
|
const outputReasoningTokens = usage.outputReasoningTokens || (usage as any).reasoningTokens || 0;
|
23
23
|
|
24
|
+
const outputImageTokens = usage.outputImageTokens || (usage as any).imageTokens || 0;
|
25
|
+
|
24
26
|
const outputTextTokens = usage.outputTextTokens
|
25
27
|
? usage.outputTextTokens
|
26
|
-
: totalOutputTokens -
|
28
|
+
: totalOutputTokens -
|
29
|
+
outputReasoningTokens -
|
30
|
+
(usage.outputAudioTokens || 0) -
|
31
|
+
outputImageTokens;
|
27
32
|
|
28
33
|
const inputWriteCacheTokens = usage.inputWriteCacheTokens || 0;
|
29
34
|
const inputCacheTokens = usage.inputCachedTokens || (usage as any).cachedTokens || 0;
|
@@ -93,6 +98,13 @@ export const getDetailsToken = (
|
|
93
98
|
token: usage.outputAudioTokens,
|
94
99
|
}
|
95
100
|
: undefined,
|
101
|
+
outputImage: !!outputImageTokens
|
102
|
+
? {
|
103
|
+
credit: calcCredit(outputImageTokens, formatPrice.output),
|
104
|
+
id: 'outputImage',
|
105
|
+
token: outputImageTokens,
|
106
|
+
}
|
107
|
+
: undefined,
|
96
108
|
outputReasoning: !!outputReasoningTokens
|
97
109
|
? {
|
98
110
|
credit: calcCredit(outputReasoningTokens, formatPrice.output),
|