Show billing estimate and clarify session replacement
This commit is contained in:
@@ -79,7 +79,7 @@ import {
|
||||
import { isViduModel } from "../../utils/viduRouting";
|
||||
import { isPixverseModel } from "../../utils/pixverseRouting";
|
||||
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
||||
import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
||||
import { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
||||
import {
|
||||
getImageQualityOptions,
|
||||
getDefaultImageQuality,
|
||||
@@ -220,6 +220,12 @@ const MODE_ICONS: Record<WorkbenchMode, ReactNode> = {
|
||||
video: <VideoCameraOutlined />,
|
||||
};
|
||||
|
||||
function formatCreditValue(value: number): string {
|
||||
if (!Number.isFinite(value)) return "-";
|
||||
if (value >= 100) return Math.round(value).toLocaleString("zh-CN");
|
||||
return Number(value.toFixed(2)).toString();
|
||||
}
|
||||
|
||||
function WorkbenchPage({
|
||||
isAuthenticated,
|
||||
session,
|
||||
@@ -464,6 +470,48 @@ function WorkbenchPage({
|
||||
const videoQualityLabel = getVideoQualityLabel(videoModel, videoQuality);
|
||||
|
||||
const imageSettingsSummary = `${imageRatio} / ${imageQuality}`;
|
||||
const billingEstimate = useMemo(() => {
|
||||
if (activeMode === "image") {
|
||||
return {
|
||||
label: "预计 20 积分",
|
||||
title: `图片生成按任务计费:${activeModel},${imageSettingsSummary},预计 20 积分`,
|
||||
};
|
||||
}
|
||||
if (activeMode === "video") {
|
||||
try {
|
||||
const durationSeconds = Math.max(1, Math.ceil(Number(videoDuration) || 1));
|
||||
const credits = calculateEnterpriseVideoCredits({
|
||||
model: activeModelValue,
|
||||
resolution: videoQuality,
|
||||
durationSeconds,
|
||||
muted: false,
|
||||
hasReferenceVideo: referenceItems.some((item) => item.kind === "video"),
|
||||
});
|
||||
return {
|
||||
label: `预计 ${formatCreditValue(credits)} 积分`,
|
||||
title: `${activeModel},${videoQualityLabel},${durationSeconds} 秒,预计 ${formatCreditValue(credits)} 积分`,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
label: "计费以提交后为准",
|
||||
title: "当前模型的预估计费暂不可用,实际扣费以服务端结算为准",
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: "按 Token 结算",
|
||||
title: "文本对话按输入、输出 Token 实际用量结算,完成后显示本次积分",
|
||||
};
|
||||
}, [
|
||||
activeMode,
|
||||
activeModel,
|
||||
activeModelValue,
|
||||
imageSettingsSummary,
|
||||
referenceItems,
|
||||
videoDuration,
|
||||
videoQuality,
|
||||
videoQualityLabel,
|
||||
]);
|
||||
const composerPlaceholder =
|
||||
referenceItems.length > 0 ? `${toolTheme.placeholder},可输入 @ 引用参考内容` : toolTheme.placeholder;
|
||||
const dropdownDirection = hasActivatedWorkspace ? "up" : "down";
|
||||
@@ -2545,7 +2593,15 @@ function WorkbenchPage({
|
||||
}
|
||||
};
|
||||
|
||||
const sendDisabled = !inputValue.trim() || (activeMode !== "chat" && getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3);
|
||||
const activeGenerationCount = getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id));
|
||||
const generationLimitReached = activeMode !== "chat" && activeGenerationCount >= 3;
|
||||
const promptIsEmpty = !inputValue.trim();
|
||||
const sendDisabled = promptIsEmpty || generationLimitReached;
|
||||
const sendButtonTitle = promptIsEmpty
|
||||
? "输入内容后可发送"
|
||||
: generationLimitReached
|
||||
? `当前已有 ${activeGenerationCount} 个任务进行中,请等待任一任务完成`
|
||||
: billingEstimate.title;
|
||||
|
||||
const suggestedPrompts = [
|
||||
{ text: "画一个赛博朋克风格的城市夜景", mode: "image" as WorkbenchMode },
|
||||
@@ -2882,10 +2938,15 @@ function WorkbenchPage({
|
||||
)}
|
||||
</div>
|
||||
<div className="wb-composer__toolbar-right">
|
||||
<span className="wb-composer__billing-estimate" title={billingEstimate.title}>
|
||||
{billingEstimate.label}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className={`wb-composer__send-primary${isGenerating ? " is-loading" : ""}`}
|
||||
disabled={sendDisabled || isGenerating}
|
||||
title={isGenerating ? "任务处理中" : sendButtonTitle}
|
||||
aria-label={isGenerating ? "任务处理中" : sendButtonTitle}
|
||||
onClick={() => {
|
||||
if (getCachedRole() === "admin") console.log("[ai/workbench-send-click]", {
|
||||
mode: activeMode,
|
||||
|
||||
@@ -33,7 +33,7 @@ const initialState: SessionState = {
|
||||
loginPromptOpen: false,
|
||||
pendingAction: null,
|
||||
sessionReplacedOpen: false,
|
||||
sessionReplacedMessage: '您的账号已在其他设备登录,此设备的登录状态已失效。',
|
||||
sessionReplacedMessage: '当前账号已在其他设备登录,此设备的登录状态已失效。',
|
||||
};
|
||||
|
||||
export const useSessionStore = create<SessionState & SessionActions>((set) => ({
|
||||
@@ -55,7 +55,7 @@ export const useSessionStore = create<SessionState & SessionActions>((set) => ({
|
||||
|
||||
showSessionReplaced: (message) => set({
|
||||
sessionReplacedOpen: true,
|
||||
sessionReplacedMessage: message || '您的账号已在其他设备登录(最多同时 2 台设备),此设备的登录状态已失效。',
|
||||
sessionReplacedMessage: message || '当前账号已在其他设备登录,此设备的登录状态已失效。',
|
||||
}),
|
||||
|
||||
hideSessionReplaced: () => set({ sessionReplacedOpen: false }),
|
||||
|
||||
@@ -11915,6 +11915,21 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wb-composer__billing-estimate {
|
||||
max-width: 138px;
|
||||
padding: 6px 9px;
|
||||
border: 2px solid #111;
|
||||
background: #fffbe8;
|
||||
color: #111;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-shadow: 2px 2px 0 #111;
|
||||
}
|
||||
|
||||
.wb-composer__send-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1794,6 +1794,14 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .wb-composer__billing-estimate {
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: 999px;
|
||||
background: var(--bg-elevated);
|
||||
color: var(--fg-body);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .wb-composer__send-primary:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
color: var(--dg-button-text);
|
||||
|
||||
@@ -74,11 +74,11 @@ export function getEnterpriseVideoCreditRate(input: EnterpriseVideoPricingInput)
|
||||
}
|
||||
|
||||
if (model.includes("vidu")) {
|
||||
return resolution === "720P" ? 0.4 : 0.8;
|
||||
return resolution === "720P" ? 0.6 : 1.0;
|
||||
}
|
||||
|
||||
if (model.includes("pixverse")) {
|
||||
return resolution === "720P" ? 0.4 : 0.8;
|
||||
return resolution === "720P" ? 0.6 : 1.0;
|
||||
}
|
||||
|
||||
if (model.includes("kling")) {
|
||||
|
||||
Reference in New Issue
Block a user