Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a9196a63a | |||
| 4dfcb6fc8a | |||
| 60d5cd2edf |
+7
-1
@@ -374,6 +374,7 @@ function App() {
|
|||||||
})));
|
})));
|
||||||
|
|
||||||
const [ecommerceEverMounted, setEcommerceEverMounted] = useState(false);
|
const [ecommerceEverMounted, setEcommerceEverMounted] = useState(false);
|
||||||
|
const [workbenchResetToken, setWorkbenchResetToken] = useState(0);
|
||||||
const isEcommerceActive = activeView === "ecommerce" || activeView === "ecommerceHub";
|
const isEcommerceActive = activeView === "ecommerce" || activeView === "ecommerceHub";
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEcommerceActive && !ecommerceEverMounted) setEcommerceEverMounted(true);
|
if (isEcommerceActive && !ecommerceEverMounted) setEcommerceEverMounted(true);
|
||||||
@@ -459,6 +460,9 @@ function App() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSetView = useCallback((view: WebViewKey) => {
|
const handleSetView = useCallback((view: WebViewKey) => {
|
||||||
|
if (view === "workbench" && Boolean(session)) {
|
||||||
|
setWorkbenchResetToken((token) => token + 1);
|
||||||
|
}
|
||||||
window.location.hash = `/${view}`;
|
window.location.hash = `/${view}`;
|
||||||
setView(view);
|
setView(view);
|
||||||
if (view !== "login") {
|
if (view !== "login") {
|
||||||
@@ -467,7 +471,7 @@ function App() {
|
|||||||
if (isWorkspaceView(view)) {
|
if (isWorkspaceView(view)) {
|
||||||
setWorkspaceExpanded(true);
|
setWorkspaceExpanded(true);
|
||||||
}
|
}
|
||||||
}, [setView, setWorkspaceExpanded]);
|
}, [session, setView, setWorkspaceExpanded]);
|
||||||
|
|
||||||
const clearAuthenticatedState = useCallback((options?: { resetView?: boolean }) => {
|
const clearAuthenticatedState = useCallback((options?: { resetView?: boolean }) => {
|
||||||
clearAllUserStorage();
|
clearAllUserStorage();
|
||||||
@@ -1313,11 +1317,13 @@ function App() {
|
|||||||
case "workbench":
|
case "workbench":
|
||||||
return (
|
return (
|
||||||
<WorkbenchPage
|
<WorkbenchPage
|
||||||
|
key={`workbench-${workbenchResetToken}`}
|
||||||
isAuthenticated={Boolean(session)}
|
isAuthenticated={Boolean(session)}
|
||||||
session={session}
|
session={session}
|
||||||
onRequireLogin={handleRequireTaskLogin}
|
onRequireLogin={handleRequireTaskLogin}
|
||||||
onOpenResultInCanvas={handleOpenResultInCanvas}
|
onOpenResultInCanvas={handleOpenResultInCanvas}
|
||||||
onRefreshUsage={refreshUsage}
|
onRefreshUsage={refreshUsage}
|
||||||
|
resetToken={workbenchResetToken}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "home":
|
case "home":
|
||||||
|
|||||||
@@ -248,17 +248,6 @@ function isNonAuthErrorCode(code: string | undefined): boolean {
|
|||||||
].includes(code);
|
].includes(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAuthFailureResponse(status: number, payload: unknown): boolean {
|
|
||||||
if (status === 401) return true;
|
|
||||||
if (status !== 403) return false;
|
|
||||||
|
|
||||||
const code = getPayloadCode(payload);
|
|
||||||
if (code === "SESSION_REPLACED" || code === "TOKEN_EXPIRED" || code === "ACCOUNT_DISABLED") return true;
|
|
||||||
|
|
||||||
const message = getPayloadMessage(payload) || "";
|
|
||||||
return /账号已禁用|登录已过期|登录状态|session|token|企业信息不存在/i.test(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifySessionExpired(status: number, response: Response, payload: unknown): void {
|
function notifySessionExpired(status: number, response: Response, payload: unknown): void {
|
||||||
if (status !== 401 && status !== 403) return;
|
if (status !== 401 && status !== 403) return;
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
@@ -274,7 +263,6 @@ function notifySessionExpired(status: number, response: Response, payload: unkno
|
|||||||
// Non-auth 403 errors (enterprise model access, insufficient balance) must
|
// Non-auth 403 errors (enterprise model access, insufficient balance) must
|
||||||
// not trigger session expiry.
|
// not trigger session expiry.
|
||||||
if (status === 403 && isNonAuthErrorCode(getPayloadCode(payload))) return;
|
if (status === 403 && isNonAuthErrorCode(getPayloadCode(payload))) return;
|
||||||
if (!isAuthFailureResponse(status, payload)) return;
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastSessionExpiredEventAt < 1500) return;
|
if (now - lastSessionExpiredEventAt < 1500) return;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
|
|||||||
import { aiGenerationClient } from "../../api/aiGenerationClient";
|
import { aiGenerationClient } from "../../api/aiGenerationClient";
|
||||||
import { communityClient } from "../../api/communityClient";
|
import { communityClient } from "../../api/communityClient";
|
||||||
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
||||||
import "../../styles/pages/compliance.css";
|
|
||||||
import type { WebCanvasWorkflow, WebUserSession } from "../../types";
|
import type { WebCanvasWorkflow, WebUserSession } from "../../types";
|
||||||
import { getWorkflowCoverUrl, isCanvasWorkflow } from "../community/communityCaseUtils";
|
import { getWorkflowCoverUrl, isCanvasWorkflow } from "../community/communityCaseUtils";
|
||||||
import { canManageCommunityCases } from "./communityPermissions";
|
import { canManageCommunityCases } from "./communityPermissions";
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||||||
import { communityClient, type ServerCommunityCase } from "../../api/communityClient";
|
import { communityClient, type ServerCommunityCase } from "../../api/communityClient";
|
||||||
import { reportClient, type AdminReportItem } from "../../api/reportClient";
|
import { reportClient, type AdminReportItem } from "../../api/reportClient";
|
||||||
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
||||||
import "../../styles/pages/compliance.css";
|
|
||||||
import type { WebUserSession } from "../../types";
|
import type { WebUserSession } from "../../types";
|
||||||
import { canManageCommunityCases, canReviewCommunity } from "./communityPermissions";
|
import { canManageCommunityCases, canReviewCommunity } from "./communityPermissions";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CheckCircleOutlined, FlagOutlined, MailOutlined, PhoneOutlined } from "
|
|||||||
import { useEffect, useState, type FormEvent } from "react";
|
import { useEffect, useState, type FormEvent } from "react";
|
||||||
import { publicConfigClient, type WebPublicConfig } from "../../api/publicConfigClient";
|
import { publicConfigClient, type WebPublicConfig } from "../../api/publicConfigClient";
|
||||||
import { reportClient, type ReportInput } from "../../api/reportClient";
|
import { reportClient, type ReportInput } from "../../api/reportClient";
|
||||||
import "../../styles/pages/compliance.css";
|
|
||||||
|
|
||||||
type SubmitState = "idle" | "loading" | "success" | "error";
|
type SubmitState = "idle" | "loading" | "success" | "error";
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import { downloadResultAsset } from "./workbenchDownload";
|
|||||||
import { translateTaskError } from "../../utils/translateTaskError";
|
import { translateTaskError } from "../../utils/translateTaskError";
|
||||||
import {
|
import {
|
||||||
buildLocalTimeoutMessage,
|
buildLocalTimeoutMessage,
|
||||||
|
formatTextTokenUsage,
|
||||||
getTaskTimeoutPolicy,
|
getTaskTimeoutPolicy,
|
||||||
isTaskLocallyTimedOut,
|
isTaskLocallyTimedOut,
|
||||||
} from "../../utils/taskLifecycle";
|
} from "../../utils/taskLifecycle";
|
||||||
@@ -78,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 { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
|
||||||
import {
|
import {
|
||||||
getImageQualityOptions,
|
getImageQualityOptions,
|
||||||
getDefaultImageQuality,
|
getDefaultImageQuality,
|
||||||
@@ -200,6 +201,7 @@ interface WorkbenchPageProps {
|
|||||||
onRequireLogin: (input: CreatePreviewTaskInput) => void;
|
onRequireLogin: (input: CreatePreviewTaskInput) => void;
|
||||||
onOpenResultInCanvas?: (payload: import("./workbenchConstants").WorkbenchResultActionPayload) => void;
|
onOpenResultInCanvas?: (payload: import("./workbenchConstants").WorkbenchResultActionPayload) => void;
|
||||||
onRefreshUsage?: () => void;
|
onRefreshUsage?: () => void;
|
||||||
|
resetToken?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Component ───────────────────────────────────────────────────────────
|
// ─── Component ───────────────────────────────────────────────────────────
|
||||||
@@ -219,18 +221,13 @@ 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,
|
||||||
onRequireLogin,
|
onRequireLogin,
|
||||||
onOpenResultInCanvas,
|
onOpenResultInCanvas,
|
||||||
onRefreshUsage,
|
onRefreshUsage,
|
||||||
|
resetToken,
|
||||||
}: WorkbenchPageProps) {
|
}: WorkbenchPageProps) {
|
||||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
const referenceInputRef = useRef<HTMLInputElement | null>(null);
|
const referenceInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
@@ -249,10 +246,11 @@ function WorkbenchPage({
|
|||||||
const activeConversationIdRef = useRef<number | null>(null);
|
const activeConversationIdRef = useRef<number | null>(null);
|
||||||
const messagesRef = useRef<ChatMessage[]>([]);
|
const messagesRef = useRef<ChatMessage[]>([]);
|
||||||
const conversationMessagesCacheRef = useRef<Map<number, ChatMessage[]>>(new Map());
|
const conversationMessagesCacheRef = useRef<Map<number, ChatMessage[]>>(new Map());
|
||||||
const skipConversationAutoSelectRef = useRef(false);
|
const skipConversationAutoSelectRef = useRef(Boolean(resetToken));
|
||||||
const keepaliveTasksRef = useRef<Record<string, WorkbenchKeepaliveTask>>(readStoredKeepaliveTasks());
|
const keepaliveTasksRef = useRef<Record<string, WorkbenchKeepaliveTask>>(readStoredKeepaliveTasks());
|
||||||
const taskAbortControllersRef = useRef<Map<string, AbortController>>(new Map());
|
const taskAbortControllersRef = useRef<Map<string, AbortController>>(new Map());
|
||||||
const lastScrollTopRef = useRef(0);
|
const lastScrollTopRef = useRef(0);
|
||||||
|
const scrollActionHintTimerRef = useRef<number | null>(null);
|
||||||
const shouldFollowNewMessagesRef = useRef(true);
|
const shouldFollowNewMessagesRef = useRef(true);
|
||||||
const pendingScrollToLatestRef = useRef(true);
|
const pendingScrollToLatestRef = useRef(true);
|
||||||
const genTracker = useGenerationTasks({ sourceView: "workbench" });
|
const genTracker = useGenerationTasks({ sourceView: "workbench" });
|
||||||
@@ -261,7 +259,7 @@ function WorkbenchPage({
|
|||||||
|
|
||||||
const [activeMode, setActiveMode] = useState<WorkbenchMode>("video");
|
const [activeMode, setActiveMode] = useState<WorkbenchMode>("video");
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>(() => readStoredMessages());
|
const [messages, setMessages] = useState<ChatMessage[]>(() => (resetToken ? [] : readStoredMessages()));
|
||||||
const [promptHistory, setPromptHistory] = useState<string[]>(() => readStoredPromptHistory());
|
const [promptHistory, setPromptHistory] = useState<string[]>(() => readStoredPromptHistory());
|
||||||
const [toolbarMenuId, setToolbarMenuId] = useState<ToolbarMenuId>(null);
|
const [toolbarMenuId, setToolbarMenuId] = useState<ToolbarMenuId>(null);
|
||||||
const [referenceItems, setReferenceItems] = useState<ReferenceItem[]>([]);
|
const [referenceItems, setReferenceItems] = useState<ReferenceItem[]>([]);
|
||||||
@@ -284,7 +282,7 @@ function WorkbenchPage({
|
|||||||
const [projectError, setProjectError] = useState<string | null>(null);
|
const [projectError, setProjectError] = useState<string | null>(null);
|
||||||
const [conversations, setConversations] = useState<ConversationSummary[]>([]);
|
const [conversations, setConversations] = useState<ConversationSummary[]>([]);
|
||||||
const [activeConversationId, setActiveConversationId] = useState<number | null>(() =>
|
const [activeConversationId, setActiveConversationId] = useState<number | null>(() =>
|
||||||
readStoredActiveConversationId(readStoredMessages()),
|
resetToken ? null : readStoredActiveConversationId(readStoredMessages()),
|
||||||
);
|
);
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
|
||||||
const [deleteDialog, setDeleteDialog] = useState<DeleteDialogState | null>(null);
|
const [deleteDialog, setDeleteDialog] = useState<DeleteDialogState | null>(null);
|
||||||
@@ -294,7 +292,9 @@ function WorkbenchPage({
|
|||||||
const [promptSelectionRange, setPromptSelectionRange] = useState({ start: 0, end: 0 });
|
const [promptSelectionRange, setPromptSelectionRange] = useState({ start: 0, end: 0 });
|
||||||
const [mentionActiveIndex, setMentionActiveIndex] = useState(0);
|
const [mentionActiveIndex, setMentionActiveIndex] = useState(0);
|
||||||
const [composerHidden, setComposerHidden] = useState(false);
|
const [composerHidden, setComposerHidden] = useState(false);
|
||||||
|
const [scrollActionHint, setScrollActionHint] = useState<"top" | "bottom" | null>(null);
|
||||||
const [workspaceStarted, setWorkspaceStarted] = useState(false);
|
const [workspaceStarted, setWorkspaceStarted] = useState(false);
|
||||||
|
const lastResetTokenRef = useRef(resetToken);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
activeConversationIdRef.current = activeConversationId;
|
activeConversationIdRef.current = activeConversationId;
|
||||||
@@ -420,7 +420,6 @@ function WorkbenchPage({
|
|||||||
const toolTheme = MODE_META[activeMode];
|
const toolTheme = MODE_META[activeMode];
|
||||||
const workbenchAccent = "#00ff88";
|
const workbenchAccent = "#00ff88";
|
||||||
const hasConversationRecords = activeConversationId !== null || messages.length > 0;
|
const hasConversationRecords = activeConversationId !== null || messages.length > 0;
|
||||||
const hasActivatedWorkspace = workspaceStarted || isGenerating || hasConversationRecords;
|
|
||||||
const referenceCount = referenceItems.length;
|
const referenceCount = referenceItems.length;
|
||||||
const activeVideoModelValue = toHappyHorseDisplayModel(videoModel);
|
const activeVideoModelValue = toHappyHorseDisplayModel(videoModel);
|
||||||
const activeModelValue =
|
const activeModelValue =
|
||||||
@@ -448,6 +447,7 @@ function WorkbenchPage({
|
|||||||
[conversations],
|
[conversations],
|
||||||
);
|
);
|
||||||
const hasSidebarRecords = conversationRecords.length > 0;
|
const hasSidebarRecords = conversationRecords.length > 0;
|
||||||
|
const hasActivatedWorkspace = workspaceStarted || isGenerating || hasConversationRecords;
|
||||||
|
|
||||||
const activeConversationTitle = useMemo(() => {
|
const activeConversationTitle = useMemo(() => {
|
||||||
if (!activeConversationId) return "";
|
if (!activeConversationId) return "";
|
||||||
@@ -469,48 +469,6 @@ 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";
|
||||||
@@ -543,6 +501,31 @@ function WorkbenchPage({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const hideScrollActionHint = useCallback(() => {
|
||||||
|
if (scrollActionHintTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(scrollActionHintTimerRef.current);
|
||||||
|
scrollActionHintTimerRef.current = null;
|
||||||
|
}
|
||||||
|
setScrollActionHint(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showScrollActionHint = useCallback((direction: "top" | "bottom") => {
|
||||||
|
if (scrollActionHintTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(scrollActionHintTimerRef.current);
|
||||||
|
}
|
||||||
|
setScrollActionHint(direction);
|
||||||
|
scrollActionHintTimerRef.current = window.setTimeout(() => {
|
||||||
|
setScrollActionHint(null);
|
||||||
|
scrollActionHintTimerRef.current = null;
|
||||||
|
}, 1400);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => () => {
|
||||||
|
if (scrollActionHintTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(scrollActionHintTimerRef.current);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const imageSettingGroups = useMemo<WorkbenchFieldGroup[]>(
|
const imageSettingGroups = useMemo<WorkbenchFieldGroup[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -1313,6 +1296,12 @@ function WorkbenchPage({
|
|||||||
activeConversationIdRef.current = null;
|
activeConversationIdRef.current = null;
|
||||||
}, [syncActiveGenerationUi]);
|
}, [syncActiveGenerationUi]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (resetToken === undefined || lastResetTokenRef.current === resetToken) return;
|
||||||
|
lastResetTokenRef.current = resetToken;
|
||||||
|
handleNewConversation();
|
||||||
|
}, [handleNewConversation, resetToken]);
|
||||||
|
|
||||||
const handleSelectProject = useCallback((id: string) => {
|
const handleSelectProject = useCallback((id: string) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
handleNewConversation();
|
handleNewConversation();
|
||||||
@@ -1467,6 +1456,7 @@ function WorkbenchPage({
|
|||||||
const atBottom = top + surface.clientHeight >= surface.scrollHeight - edgeThreshold;
|
const atBottom = top + surface.clientHeight >= surface.scrollHeight - edgeThreshold;
|
||||||
shouldFollowNewMessagesRef.current = atBottom;
|
shouldFollowNewMessagesRef.current = atBottom;
|
||||||
setComposerHidden(!(atTop || atBottom));
|
setComposerHidden(!(atTop || atBottom));
|
||||||
|
hideScrollActionHint();
|
||||||
lastScrollTopRef.current = top;
|
lastScrollTopRef.current = top;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1481,24 +1471,27 @@ function WorkbenchPage({
|
|||||||
shouldFollowNewMessagesRef.current = atBottom;
|
shouldFollowNewMessagesRef.current = atBottom;
|
||||||
if (atTop || atBottom) {
|
if (atTop || atBottom) {
|
||||||
setComposerHidden(false);
|
setComposerHidden(false);
|
||||||
|
hideScrollActionHint();
|
||||||
} else if (Math.abs(delta) > scrollDeltaThreshold) {
|
} else if (Math.abs(delta) > scrollDeltaThreshold) {
|
||||||
setComposerHidden(true);
|
setComposerHidden(true);
|
||||||
|
showScrollActionHint(delta < 0 ? "top" : "bottom");
|
||||||
}
|
}
|
||||||
lastScrollTopRef.current = top;
|
lastScrollTopRef.current = top;
|
||||||
};
|
};
|
||||||
|
|
||||||
surface.addEventListener("scroll", handleScroll, { passive: true });
|
surface.addEventListener("scroll", handleScroll, { passive: true });
|
||||||
return () => surface.removeEventListener("scroll", handleScroll);
|
return () => surface.removeEventListener("scroll", handleScroll);
|
||||||
}, [hasActivatedWorkspace]);
|
}, [hasActivatedWorkspace, hideScrollActionHint, showScrollActionHint]);
|
||||||
|
|
||||||
const scrollMessagesSurface = useCallback((direction: "top" | "bottom") => {
|
const scrollMessagesSurface = useCallback((direction: "top" | "bottom") => {
|
||||||
const surface = messagesSurfaceRef.current;
|
const surface = messagesSurfaceRef.current;
|
||||||
if (!surface) return;
|
if (!surface) return;
|
||||||
|
|
||||||
const top = direction === "top" ? 0 : surface.scrollHeight;
|
const top = direction === "top" ? 0 : surface.scrollHeight;
|
||||||
|
hideScrollActionHint();
|
||||||
setComposerHidden(false);
|
setComposerHidden(false);
|
||||||
surface.scrollTo({ top, behavior: "smooth" });
|
surface.scrollTo({ top, behavior: "smooth" });
|
||||||
}, []);
|
}, [hideScrollActionHint]);
|
||||||
|
|
||||||
const closeToolbarMenus = () => setToolbarMenuId(null);
|
const closeToolbarMenus = () => setToolbarMenuId(null);
|
||||||
const toggleToolbarMenu = (menuId: Exclude<ToolbarMenuId, null>) => {
|
const toggleToolbarMenu = (menuId: Exclude<ToolbarMenuId, null>) => {
|
||||||
@@ -2592,15 +2585,7 @@ function WorkbenchPage({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeGenerationCount = getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id));
|
const sendDisabled = !inputValue.trim() || (activeMode !== "chat" && getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3);
|
||||||
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 },
|
||||||
@@ -2937,15 +2922,10 @@ 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,
|
||||||
@@ -2993,9 +2973,29 @@ function WorkbenchPage({
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const renderPromptCaseOverlay = () =>
|
const renderPromptCaseOverlay = () => {
|
||||||
selectedPromptCase ? (
|
if (!selectedPromptCase) return null;
|
||||||
<div className="wb-prompt-case-modal" role="dialog" aria-modal="true" aria-labelledby="wb-prompt-case-title">
|
|
||||||
|
const measuredRatio = promptCaseMeasuredRatios[selectedPromptCase.id];
|
||||||
|
const ratioParts = selectedPromptCase.ratio.replace(/\s+/g, "").split(":").map(Number);
|
||||||
|
const declaredRatio =
|
||||||
|
ratioParts.length === 2 && ratioParts[0] > 0 && ratioParts[1] > 0
|
||||||
|
? ratioParts[0] / ratioParts[1]
|
||||||
|
: null;
|
||||||
|
const caseRatio =
|
||||||
|
typeof measuredRatio === "number" && Number.isFinite(measuredRatio) && measuredRatio > 0
|
||||||
|
? measuredRatio
|
||||||
|
: declaredRatio;
|
||||||
|
const copyLength = `${selectedPromptCase.summary} ${selectedPromptCase.prompt}`.length;
|
||||||
|
const modalClassName = [
|
||||||
|
"wb-prompt-case-modal",
|
||||||
|
caseRatio && caseRatio < 0.72 ? "is-tall-media" : "",
|
||||||
|
caseRatio && caseRatio >= 0.72 && caseRatio < 1 ? "is-portrait-media" : "",
|
||||||
|
copyLength > 260 ? "is-long-copy" : "",
|
||||||
|
].filter(Boolean).join(" ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={modalClassName} role="dialog" aria-modal="true" aria-labelledby="wb-prompt-case-title">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="wb-prompt-case-modal__backdrop"
|
className="wb-prompt-case-modal__backdrop"
|
||||||
@@ -3004,7 +3004,11 @@ function WorkbenchPage({
|
|||||||
/>
|
/>
|
||||||
<section className="wb-prompt-case-modal__panel">
|
<section className="wb-prompt-case-modal__panel">
|
||||||
<div className="wb-prompt-case-modal__media">
|
<div className="wb-prompt-case-modal__media">
|
||||||
<img src={selectedPromptCase.imageUrl} alt={selectedPromptCase.title} />
|
<img
|
||||||
|
src={selectedPromptCase.imageUrl}
|
||||||
|
alt={selectedPromptCase.title}
|
||||||
|
onLoad={(event) => handlePromptCaseImageLoad(selectedPromptCase.id, event)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<aside className="wb-prompt-case-modal__sidebar">
|
<aside className="wb-prompt-case-modal__sidebar">
|
||||||
<button
|
<button
|
||||||
@@ -3044,7 +3048,8 @@ function WorkbenchPage({
|
|||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (!hasActivatedWorkspace) {
|
if (!hasActivatedWorkspace) {
|
||||||
return (
|
return (
|
||||||
@@ -3139,8 +3144,8 @@ function WorkbenchPage({
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{renderConversationSidebar()}
|
||||||
</div>
|
</div>
|
||||||
{renderConversationSidebar()}
|
|
||||||
{renderMessagePreviewOverlay()}
|
{renderMessagePreviewOverlay()}
|
||||||
{renderPromptCaseOverlay()}
|
{renderPromptCaseOverlay()}
|
||||||
{renderDeleteDialog()}
|
{renderDeleteDialog()}
|
||||||
@@ -3226,6 +3231,11 @@ function WorkbenchPage({
|
|||||||
<span>{message.taskStatusLabel || generationStatus}</span>
|
<span>{message.taskStatusLabel || generationStatus}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{message.role === "assistant" && message.mode === "chat" && message.status === "completed" && (
|
||||||
|
<div className="ai-chat-task-billing-note">
|
||||||
|
{formatTextTokenUsage(message.taskUsage)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{(message.resultUrl || (message.result && message.status !== "thinking")) && (
|
{(message.resultUrl || (message.result && message.status !== "thinking")) && (
|
||||||
<ResultCard
|
<ResultCard
|
||||||
message={message}
|
message={message}
|
||||||
@@ -3282,10 +3292,10 @@ function WorkbenchPage({
|
|||||||
{renderComposerToolbar(false, isGenerating)}
|
{renderComposerToolbar(false, isGenerating)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div className="wb-chat-scroll-actions" aria-label="聊天滚动">
|
<div className={`wb-chat-scroll-actions${scrollActionHint ? ` is-showing-${scrollActionHint}` : ""}`} aria-label="聊天滚动">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="wb-chat-scroll-actions__button"
|
className="wb-chat-scroll-actions__button wb-chat-scroll-actions__button--top"
|
||||||
title="返回聊天顶部"
|
title="返回聊天顶部"
|
||||||
aria-label="返回聊天顶部"
|
aria-label="返回聊天顶部"
|
||||||
onClick={() => scrollMessagesSurface("top")}
|
onClick={() => scrollMessagesSurface("top")}
|
||||||
@@ -3294,7 +3304,7 @@ function WorkbenchPage({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="wb-chat-scroll-actions__button"
|
className="wb-chat-scroll-actions__button wb-chat-scroll-actions__button--bottom"
|
||||||
title="到达聊天底部"
|
title="到达聊天底部"
|
||||||
aria-label="到达聊天底部"
|
aria-label="到达聊天底部"
|
||||||
onClick={() => scrollMessagesSurface("bottom")}
|
onClick={() => scrollMessagesSurface("bottom")}
|
||||||
|
|||||||
@@ -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 || '当前账号已在其他设备登录,此设备的登录状态已失效。',
|
sessionReplacedMessage: message || '您的账号已在其他设备登录(最多同时 2 台设备),此设备的登录状态已失效。',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
hideSessionReplaced: () => set({ sessionReplacedOpen: false }),
|
hideSessionReplaced: () => set({ sessionReplacedOpen: false }),
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
width: min(1180px, calc(100vw - 48px));
|
width: min(1180px, calc(100vw - 48px));
|
||||||
margin: 0 auto;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-admin-toolbar {
|
.beta-admin-toolbar {
|
||||||
@@ -94,8 +90,6 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 300px minmax(0, 1fr);
|
grid-template-columns: 300px minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +97,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
max-height: 100%;
|
max-height: calc(100vh - 220px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
@@ -180,10 +174,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-admin-detail__header,
|
.beta-admin-detail__header,
|
||||||
@@ -386,7 +376,6 @@
|
|||||||
.beta-admin-page__inner {
|
.beta-admin-page__inner {
|
||||||
width: min(100%, calc(100vw - 24px));
|
width: min(100%, calc(100vw - 24px));
|
||||||
padding: 16px 12px;
|
padding: 16px 12px;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-admin-toolbar,
|
.beta-admin-toolbar,
|
||||||
@@ -396,18 +385,11 @@
|
|||||||
|
|
||||||
.beta-admin-layout {
|
.beta-admin-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-admin-list {
|
.beta-admin-list {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-admin-detail {
|
|
||||||
max-height: none;
|
|
||||||
overflow: visible;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|||||||
@@ -11915,21 +11915,6 @@
|
|||||||
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;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1794,14 +1794,6 @@
|
|||||||
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);
|
||||||
@@ -10845,8 +10837,44 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
|
.web-shell[data-ui-theme="dark-green"] {
|
||||||
|
--dg-mobile-nav-height: 58px;
|
||||||
|
--dg-mobile-nav-gap: 12px;
|
||||||
|
--dg-mobile-nav-space: calc(var(--dg-mobile-nav-height) + var(--dg-mobile-nav-gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-shell[data-ui-theme="dark-green"] .web-topbar {
|
||||||
|
z-index: 72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-shell[data-ui-theme="dark-green"] .web-shell__content,
|
||||||
|
.web-shell[data-ui-theme="dark-green"] .web-shell__page {
|
||||||
|
min-height: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-shell[data-ui-theme="dark-green"] .web-shell__page {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-shell[data-ui-theme="dark-green"]:not([data-view="home"]):not([data-view="login"]):not([data-view="workbench"]):not([data-view="agent"]):not([data-view="avatarConsole"]) .web-shell__page {
|
||||||
|
padding-top: var(--dg-mobile-nav-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-shell[data-ui-theme="dark-green"] .profile-popover {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(56px + var(--dg-mobile-nav-space) + env(safe-area-inset-top, 0px));
|
||||||
|
right: 12px;
|
||||||
|
z-index: 120;
|
||||||
|
width: min(288px, calc(100vw - 24px));
|
||||||
|
max-height: calc(100svh - 56px - var(--dg-mobile-nav-space) - 24px);
|
||||||
|
overflow-y: auto;
|
||||||
|
transform-origin: top right;
|
||||||
|
}
|
||||||
|
|
||||||
.web-shell[data-ui-theme="dark-green"] .floating-nav {
|
.web-shell[data-ui-theme="dark-green"] .floating-nav {
|
||||||
top: calc(56px + env(safe-area-inset-top, 0px));
|
top: calc(56px + env(safe-area-inset-top, 0px));
|
||||||
|
z-index: 50;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const ENTERPRISE_VIDEO_RESOLUTION_OPTIONS = [
|
|||||||
|
|
||||||
export const ENTERPRISE_DEFAULT_VIDEO_MODEL = HAPPY_HORSE_UI_MODEL;
|
export const ENTERPRISE_DEFAULT_VIDEO_MODEL = HAPPY_HORSE_UI_MODEL;
|
||||||
export const ENTERPRISE_DEFAULT_VIDEO_RESOLUTION = "1080P";
|
export const ENTERPRISE_DEFAULT_VIDEO_RESOLUTION = "1080P";
|
||||||
const CREDITS_PER_CNY = 100;
|
|
||||||
|
|
||||||
export interface EnterpriseVideoPricingInput {
|
export interface EnterpriseVideoPricingInput {
|
||||||
model: string;
|
model: string;
|
||||||
@@ -75,11 +74,11 @@ export function getEnterpriseVideoCreditRate(input: EnterpriseVideoPricingInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("vidu")) {
|
if (model.includes("vidu")) {
|
||||||
return resolution === "720P" ? 0.6 : 1.0;
|
return resolution === "720P" ? 0.4 : 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("pixverse")) {
|
if (model.includes("pixverse")) {
|
||||||
return resolution === "720P" ? 0.6 : 1.0;
|
return resolution === "720P" ? 0.4 : 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.includes("kling")) {
|
if (model.includes("kling")) {
|
||||||
@@ -95,5 +94,5 @@ export function getEnterpriseVideoCreditRate(input: EnterpriseVideoPricingInput)
|
|||||||
|
|
||||||
export function calculateEnterpriseVideoCredits(input: EnterpriseVideoPricingInput): number {
|
export function calculateEnterpriseVideoCredits(input: EnterpriseVideoPricingInput): number {
|
||||||
const duration = Math.max(1, Math.ceil(Number(input.durationSeconds) || 1));
|
const duration = Math.max(1, Math.ceil(Number(input.durationSeconds) || 1));
|
||||||
return Number((getEnterpriseVideoCreditRate(input) * duration * CREDITS_PER_CNY).toFixed(2));
|
return Number((getEnterpriseVideoCreditRate(input) * duration).toFixed(2));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ export interface TextTokenUsage {
|
|||||||
totalTokens?: number;
|
totalTokens?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CREDITS_PER_CNY = 100;
|
export const TEXT_INPUT_CREDITS_PER_MILLION = 2;
|
||||||
|
export const TEXT_OUTPUT_CREDITS_PER_MILLION = 5;
|
||||||
export const TEXT_INPUT_CREDITS_PER_MILLION = 2 * CREDITS_PER_CNY;
|
|
||||||
export const TEXT_OUTPUT_CREDITS_PER_MILLION = 5 * CREDITS_PER_CNY;
|
|
||||||
|
|
||||||
const IMAGE_TIMEOUT_POLICY: TaskTimeoutPolicy = {
|
const IMAGE_TIMEOUT_POLICY: TaskTimeoutPolicy = {
|
||||||
submitTimeoutMs: 90_000,
|
submitTimeoutMs: 90_000,
|
||||||
@@ -153,7 +151,7 @@ export function estimateTextTokenCredits(usage: TextTokenUsage): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatTextTokenUsage(usage?: TextTokenUsage | null): string {
|
export function formatTextTokenUsage(usage?: TextTokenUsage | null): string {
|
||||||
const rule = "文本计费规则:输入 Token 每百万 200 积分,输出 Token 每百万 500 积分,实际以服务端结算为准。";
|
const rule = "文本计费规则:输入 Token 每百万 2 积分,输出 Token 每百万 5 积分,实际以服务端结算为准。";
|
||||||
if (!usage) return rule;
|
if (!usage) return rule;
|
||||||
const promptTokens = Math.max(0, Number(usage.promptTokens || 0));
|
const promptTokens = Math.max(0, Number(usage.promptTokens || 0));
|
||||||
const completionTokens = Math.max(0, Number(usage.completionTokens || 0));
|
const completionTokens = Math.max(0, Number(usage.completionTokens || 0));
|
||||||
|
|||||||
Reference in New Issue
Block a user