Show billing estimate and clarify session replacement
This commit is contained in:
@@ -79,7 +79,7 @@ import {
|
|||||||
import { isViduModel } from "../../utils/viduRouting";
|
import { isViduModel } from "../../utils/viduRouting";
|
||||||
import { isPixverseModel } from "../../utils/pixverseRouting";
|
import { isPixverseModel } from "../../utils/pixverseRouting";
|
||||||
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
||||||
import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
import { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
||||||
import {
|
import {
|
||||||
getImageQualityOptions,
|
getImageQualityOptions,
|
||||||
getDefaultImageQuality,
|
getDefaultImageQuality,
|
||||||
@@ -220,6 +220,12 @@ const MODE_ICONS: Record<WorkbenchMode, ReactNode> = {
|
|||||||
video: <VideoCameraOutlined />,
|
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({
|
function WorkbenchPage({
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
session,
|
session,
|
||||||
@@ -464,6 +470,48 @@ function WorkbenchPage({
|
|||||||
const videoQualityLabel = getVideoQualityLabel(videoModel, videoQuality);
|
const videoQualityLabel = getVideoQualityLabel(videoModel, videoQuality);
|
||||||
|
|
||||||
const imageSettingsSummary = `${imageRatio} / ${imageQuality}`;
|
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 =
|
const composerPlaceholder =
|
||||||
referenceItems.length > 0 ? `${toolTheme.placeholder},可输入 @ 引用参考内容` : toolTheme.placeholder;
|
referenceItems.length > 0 ? `${toolTheme.placeholder},可输入 @ 引用参考内容` : toolTheme.placeholder;
|
||||||
const dropdownDirection = hasActivatedWorkspace ? "up" : "down";
|
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 = [
|
const suggestedPrompts = [
|
||||||
{ text: "画一个赛博朋克风格的城市夜景", mode: "image" as WorkbenchMode },
|
{ text: "画一个赛博朋克风格的城市夜景", mode: "image" as WorkbenchMode },
|
||||||
@@ -2882,10 +2938,15 @@ function WorkbenchPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="wb-composer__toolbar-right">
|
<div className="wb-composer__toolbar-right">
|
||||||
|
<span className="wb-composer__billing-estimate" title={billingEstimate.title}>
|
||||||
|
{billingEstimate.label}
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`wb-composer__send-primary${isGenerating ? " is-loading" : ""}`}
|
className={`wb-composer__send-primary${isGenerating ? " is-loading" : ""}`}
|
||||||
disabled={sendDisabled || isGenerating}
|
disabled={sendDisabled || isGenerating}
|
||||||
|
title={isGenerating ? "任务处理中" : sendButtonTitle}
|
||||||
|
aria-label={isGenerating ? "任务处理中" : sendButtonTitle}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (getCachedRole() === "admin") console.log("[ai/workbench-send-click]", {
|
if (getCachedRole() === "admin") console.log("[ai/workbench-send-click]", {
|
||||||
mode: activeMode,
|
mode: activeMode,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const initialState: SessionState = {
|
|||||||
loginPromptOpen: false,
|
loginPromptOpen: false,
|
||||||
pendingAction: null,
|
pendingAction: null,
|
||||||
sessionReplacedOpen: false,
|
sessionReplacedOpen: false,
|
||||||
sessionReplacedMessage: '您的账号已在其他设备登录,此设备的登录状态已失效。',
|
sessionReplacedMessage: '当前账号已在其他设备登录,此设备的登录状态已失效。',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSessionStore = create<SessionState & SessionActions>((set) => ({
|
export const useSessionStore = create<SessionState & SessionActions>((set) => ({
|
||||||
@@ -55,7 +55,7 @@ export const useSessionStore = create<SessionState & SessionActions>((set) => ({
|
|||||||
|
|
||||||
showSessionReplaced: (message) => set({
|
showSessionReplaced: (message) => set({
|
||||||
sessionReplacedOpen: true,
|
sessionReplacedOpen: true,
|
||||||
sessionReplacedMessage: message || '您的账号已在其他设备登录(最多同时 2 台设备),此设备的登录状态已失效。',
|
sessionReplacedMessage: message || '当前账号已在其他设备登录,此设备的登录状态已失效。',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
hideSessionReplaced: () => set({ sessionReplacedOpen: false }),
|
hideSessionReplaced: () => set({ sessionReplacedOpen: false }),
|
||||||
|
|||||||
@@ -11915,6 +11915,21 @@
|
|||||||
gap: 6px;
|
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 {
|
.wb-composer__send-primary {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1794,6 +1794,14 @@
|
|||||||
box-shadow: none;
|
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) {
|
.web-shell[data-ui-theme="dark-green"] .wb-composer__send-primary:hover:not(:disabled) {
|
||||||
background: var(--accent-hover);
|
background: var(--accent-hover);
|
||||||
color: var(--dg-button-text);
|
color: var(--dg-button-text);
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ export function getEnterpriseVideoCreditRate(input: EnterpriseVideoPricingInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("vidu")) {
|
if (model.includes("vidu")) {
|
||||||
return resolution === "720P" ? 0.4 : 0.8;
|
return resolution === "720P" ? 0.6 : 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("pixverse")) {
|
if (model.includes("pixverse")) {
|
||||||
return resolution === "720P" ? 0.4 : 0.8;
|
return resolution === "720P" ? 0.6 : 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("kling")) {
|
if (model.includes("kling")) {
|
||||||
|
|||||||
Reference in New Issue
Block a user