refactor(canvas): 替换自定义ID生成函数为nanoid并优化配置逻辑
- 移除自定义createId函数,统一使用nanoid库生成唯一标识符 - 将useEffectiveAiConfig Hook替换为resolveEffectiveConfig工具函数 - 直接从config store获取publicSettings避免额外的Hook依赖 - 更新所有组件中的ID生成调用以使用nanoid - 简化buildApiUrl函数中的基础URL规范化逻辑
This commit is contained in:
@@ -6,9 +6,9 @@ import { useParams, useRouter } from "next/navigation";
|
|||||||
import { Home, ImageIcon, Images, Keyboard, List, LogOut, Menu, MessageSquare, Plus, Redo2, Settings2, Trash2, Undo2, Upload } from "lucide-react";
|
import { Home, ImageIcon, Images, Keyboard, List, LogOut, Menu, MessageSquare, Plus, Redo2, Settings2, Trash2, Undo2, Upload } from "lucide-react";
|
||||||
|
|
||||||
import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image";
|
import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image";
|
||||||
import { defaultConfig, isAiConfigReady, type AiConfig, useConfigStore, useEffectiveAiConfig } from "@/stores/use-config-store";
|
import { defaultConfig, isAiConfigReady, resolveEffectiveConfig, type AiConfig, useConfigStore } from "@/stores/use-config-store";
|
||||||
import { resolveImageUrl, uploadImage, type UploadedImage } from "@/services/image-storage";
|
import { resolveImageUrl, uploadImage, type UploadedImage } from "@/services/image-storage";
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
|
import { getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
|
||||||
import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
|
import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
|
||||||
import { useThemeStore } from "@/stores/use-theme-store";
|
import { useThemeStore } from "@/stores/use-theme-store";
|
||||||
@@ -231,7 +231,7 @@ function InfiniteCanvasPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const config = useConfigStore((state) => state.config);
|
const config = useConfigStore((state) => state.config);
|
||||||
const effectiveConfig = useEffectiveAiConfig(config);
|
const effectiveConfig = useConfigStore((state) => resolveEffectiveConfig(state.config, state.publicSettings?.modelChannel || null));
|
||||||
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
||||||
const addAsset = useAssetStore((state) => state.addAsset);
|
const addAsset = useAssetStore((state) => state.addAsset);
|
||||||
const cleanupAssetImages = useAssetStore((state) => state.cleanupImages);
|
const cleanupAssetImages = useAssetStore((state) => state.cleanupImages);
|
||||||
@@ -486,7 +486,7 @@ function InfiniteCanvasPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setNodes((prev) => [...prev, newNode]);
|
setNodes((prev) => [...prev, newNode]);
|
||||||
setConnections((prev) => [...prev, { id: createId(), ...connection }]);
|
setConnections((prev) => [...prev, { id: nanoid(), ...connection }]);
|
||||||
setSelectedNodeIds(new Set([newNode.id]));
|
setSelectedNodeIds(new Set([newNode.id]));
|
||||||
setSelectedConnectionId(null);
|
setSelectedConnectionId(null);
|
||||||
setDialogNodeId(newNode.id);
|
setDialogNodeId(newNode.id);
|
||||||
@@ -1287,7 +1287,7 @@ function InfiniteCanvasPage() {
|
|||||||
const cropped = await cropDataUrl(node.metadata.content, crop);
|
const cropped = await cropDataUrl(node.metadata.content, crop);
|
||||||
const image = await uploadImage(cropped);
|
const image = await uploadImage(cropped);
|
||||||
const width = Math.min(node.width, Math.max(220, image.width));
|
const width = Math.min(node.width, Math.max(220, image.width));
|
||||||
const childId = createId();
|
const childId = nanoid();
|
||||||
const child: CanvasNodeData = {
|
const child: CanvasNodeData = {
|
||||||
id: childId,
|
id: childId,
|
||||||
type: CanvasNodeType.Image,
|
type: CanvasNodeType.Image,
|
||||||
@@ -1301,7 +1301,7 @@ function InfiniteCanvasPage() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
setNodes((prev) => [...prev, child]);
|
setNodes((prev) => [...prev, child]);
|
||||||
setConnections((prev) => [...prev, { id: createId(), fromNodeId: node.id, toNodeId: childId }]);
|
setConnections((prev) => [...prev, { id: nanoid(), fromNodeId: node.id, toNodeId: childId }]);
|
||||||
setSelectedNodeIds(new Set([childId]));
|
setSelectedNodeIds(new Set([childId]));
|
||||||
setDialogNodeId(childId);
|
setDialogNodeId(childId);
|
||||||
setCropNodeId(null);
|
setCropNodeId(null);
|
||||||
@@ -1314,7 +1314,7 @@ function InfiniteCanvasPage() {
|
|||||||
openConfigDialog(true);
|
openConfigDialog(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const childId = createId();
|
const childId = nanoid();
|
||||||
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
||||||
const title = buildAngleLabel(params);
|
const title = buildAngleLabel(params);
|
||||||
const prompt = buildAnglePrompt(params);
|
const prompt = buildAnglePrompt(params);
|
||||||
@@ -1329,7 +1329,7 @@ function InfiniteCanvasPage() {
|
|||||||
height: imageConfig.height,
|
height: imageConfig.height,
|
||||||
metadata: { prompt, status: NODE_STATUS_LOADING },
|
metadata: { prompt, status: NODE_STATUS_LOADING },
|
||||||
}]);
|
}]);
|
||||||
setConnections((prev) => [...prev, { id: createId(), fromNodeId: node.id, toNodeId: childId }]);
|
setConnections((prev) => [...prev, { id: nanoid(), fromNodeId: node.id, toNodeId: childId }]);
|
||||||
setSelectedNodeIds(new Set([childId]));
|
setSelectedNodeIds(new Set([childId]));
|
||||||
setDialogNodeId(childId);
|
setDialogNodeId(childId);
|
||||||
try {
|
try {
|
||||||
@@ -1466,8 +1466,8 @@ function InfiniteCanvasPage() {
|
|||||||
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
|
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
|
||||||
const gap = 96;
|
const gap = 96;
|
||||||
const rowGap = 36;
|
const rowGap = 36;
|
||||||
const rootId = createId();
|
const rootId = nanoid();
|
||||||
const childIds = count > 1 ? Array.from({ length: count }, () => createId()) : [];
|
const childIds = count > 1 ? Array.from({ length: count }, () => nanoid()) : [];
|
||||||
const targetIds = count > 1 ? childIds : [rootId];
|
const targetIds = count > 1 ? childIds : [rootId];
|
||||||
pendingChildIds = [rootId, ...childIds];
|
pendingChildIds = [rootId, ...childIds];
|
||||||
const rootNode: CanvasNodeData = {
|
const rootNode: CanvasNodeData = {
|
||||||
@@ -1494,7 +1494,7 @@ function InfiniteCanvasPage() {
|
|||||||
height: imageConfig.height,
|
height: imageConfig.height,
|
||||||
metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, batchRootId: count > 1 ? rootId : undefined },
|
metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, batchRootId: count > 1 ? rootId : undefined },
|
||||||
}));
|
}));
|
||||||
const batchConnections = [{ id: createId(), fromNodeId: nodeId, toNodeId: rootId }, ...childIds.map((childId) => ({ id: createId(), fromNodeId: rootId, toNodeId: childId }))];
|
const batchConnections = [{ id: nanoid(), fromNodeId: nodeId, toNodeId: rootId }, ...childIds.map((childId) => ({ id: nanoid(), fromNodeId: rootId, toNodeId: childId }))];
|
||||||
|
|
||||||
setNodes((prev) => [
|
setNodes((prev) => [
|
||||||
...prev.map((node) =>
|
...prev.map((node) =>
|
||||||
@@ -1564,7 +1564,7 @@ function InfiniteCanvasPage() {
|
|||||||
const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : CanvasNodeType.Text];
|
const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : CanvasNodeType.Text];
|
||||||
const textConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Text];
|
const textConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Text];
|
||||||
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
|
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
|
||||||
const childIds = isConfigNode || editingTextNode ? Array.from({ length: textCount }, () => createId()) : [];
|
const childIds = isConfigNode || editingTextNode ? Array.from({ length: textCount }, () => nanoid()) : [];
|
||||||
pendingChildIds = childIds;
|
pendingChildIds = childIds;
|
||||||
if (isConfigNode || editingTextNode) {
|
if (isConfigNode || editingTextNode) {
|
||||||
const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({
|
const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({
|
||||||
@@ -1580,7 +1580,7 @@ function InfiniteCanvasPage() {
|
|||||||
metadata: { prompt, status: NODE_STATUS_LOADING, fontSize: 14 },
|
metadata: { prompt, status: NODE_STATUS_LOADING, fontSize: 14 },
|
||||||
}));
|
}));
|
||||||
setNodes((prev) => [...prev.map((node) => node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node), ...childNodes]);
|
setNodes((prev) => [...prev.map((node) => node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined } } : node), ...childNodes]);
|
||||||
setConnections((prev) => [...prev, ...childIds.map((childId) => ({ id: createId(), fromNodeId: nodeId, toNodeId: childId }))]);
|
setConnections((prev) => [...prev, ...childIds.map((childId) => ({ id: nanoid(), fromNodeId: nodeId, toNodeId: childId }))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const answers = await Promise.all(
|
const answers = await Promise.all(
|
||||||
@@ -1690,7 +1690,7 @@ function InfiniteCanvasPage() {
|
|||||||
size: effectiveConfig.size,
|
size: effectiveConfig.size,
|
||||||
count: 3,
|
count: 3,
|
||||||
});
|
});
|
||||||
const connection = { id: createId(), fromNodeId: sourceNode.id, toNodeId: configNode.id };
|
const connection = { id: nanoid(), fromNodeId: sourceNode.id, toNodeId: configNode.id };
|
||||||
const nextNodes = nodesRef.current.map((item) => item.id === sourceNode.id ? { ...item, metadata: { ...item.metadata, content: prompt, prompt, status: NODE_STATUS_SUCCESS } } : item).concat(configNode);
|
const nextNodes = nodesRef.current.map((item) => item.id === sourceNode.id ? { ...item, metadata: { ...item.metadata, content: prompt, prompt, status: NODE_STATUS_SUCCESS } } : item).concat(configNode);
|
||||||
const nextConnections = [...connectionsRef.current, connection];
|
const nextConnections = [...connectionsRef.current, connection];
|
||||||
nodesRef.current = nextNodes;
|
nodesRef.current = nextNodes;
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { motion } from "motion/react";
|
|||||||
|
|
||||||
import { ImageGenerationPending } from "@/components/image-generation-pending";
|
import { ImageGenerationPending } from "@/components/image-generation-pending";
|
||||||
import { ModelPicker } from "@/components/model-picker";
|
import { ModelPicker } from "@/components/model-picker";
|
||||||
import { isAiConfigReady, useConfigStore, useEffectiveAiConfig, type AiConfig } from "@/stores/use-config-store";
|
import { isAiConfigReady, resolveEffectiveConfig, useConfigStore, type AiConfig } from "@/stores/use-config-store";
|
||||||
import { canvasThemes } from "@/lib/canvas-theme";
|
import { canvasThemes } from "@/lib/canvas-theme";
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { requestEdit, requestGeneration, requestImageQuestion, type ChatCompletionMessage } from "@/services/api/image";
|
import { requestEdit, requestGeneration, requestImageQuestion, type ChatCompletionMessage } from "@/services/api/image";
|
||||||
import { imageToDataUrl, uploadImage } from "@/services/image-storage";
|
import { imageToDataUrl, uploadImage } from "@/services/image-storage";
|
||||||
@@ -41,7 +41,7 @@ type CanvasAssistantPanelProps = {
|
|||||||
export function CanvasAssistantPanel({ nodes, selectedNodeIds, sessions, activeSessionId, onSelectNodeIds, onSessionsChange, onInsertImage, onInsertText, onPasteImage, onCollapseStart, onCollapse }: CanvasAssistantPanelProps) {
|
export function CanvasAssistantPanel({ nodes, selectedNodeIds, sessions, activeSessionId, onSelectNodeIds, onSessionsChange, onInsertImage, onInsertText, onPasteImage, onCollapseStart, onCollapse }: CanvasAssistantPanelProps) {
|
||||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||||
const config = useConfigStore((state) => state.config);
|
const config = useConfigStore((state) => state.config);
|
||||||
const effectiveConfig = useEffectiveAiConfig(config);
|
const effectiveConfig = useConfigStore((state) => resolveEffectiveConfig(state.config, state.publicSettings?.modelChannel || null));
|
||||||
const cleanupImages = useAssetStore((state) => state.cleanupImages);
|
const cleanupImages = useAssetStore((state) => state.cleanupImages);
|
||||||
const updateConfig = useConfigStore((state) => state.updateConfig);
|
const updateConfig = useConfigStore((state) => state.updateConfig);
|
||||||
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
||||||
@@ -149,8 +149,8 @@ export function CanvasAssistantPanel({ nodes, selectedNodeIds, sessions, activeS
|
|||||||
}
|
}
|
||||||
|
|
||||||
const refs = savedReferences || selectedReferences;
|
const refs = savedReferences || selectedReferences;
|
||||||
const userMessage: CanvasAssistantMessage = { id: createId(), role: "user", mode: nextMode, text, references: refs };
|
const userMessage: CanvasAssistantMessage = { id: nanoid(), role: "user", mode: nextMode, text, references: refs };
|
||||||
const assistantId = createId();
|
const assistantId = nanoid();
|
||||||
appendMessage(session.id, userMessage);
|
appendMessage(session.id, userMessage);
|
||||||
appendMessage(session.id, { id: assistantId, role: "assistant", mode: nextMode, text: nextMode === "image" ? "正在生成图片" : "正在回答", isLoading: true });
|
appendMessage(session.id, { id: assistantId, role: "assistant", mode: nextMode, text: nextMode === "image" ? "正在生成图片" : "正在回答", isLoading: true });
|
||||||
setPrompt("");
|
setPrompt("");
|
||||||
@@ -616,5 +616,5 @@ async function buildChatMessages(messages: CanvasAssistantMessage[]): Promise<Ch
|
|||||||
|
|
||||||
function createSession(): CanvasAssistantSession {
|
function createSession(): CanvasAssistantSession {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
return { id: createId(), title: "新对话", messages: [], createdAt: now, updatedAt: now };
|
return { id: nanoid(), title: "新对话", messages: [], createdAt: now, updatedAt: now };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist, type PersistStorage, type StorageValue } from "zustand/middleware";
|
import { persist, type PersistStorage, type StorageValue } from "zustand/middleware";
|
||||||
|
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { localForageStorage } from "@/lib/localforage-storage";
|
import { localForageStorage } from "@/lib/localforage-storage";
|
||||||
import type { CanvasBackgroundMode } from "@/lib/canvas-theme";
|
import type { CanvasBackgroundMode } from "@/lib/canvas-theme";
|
||||||
import type { CanvasAssistantSession, CanvasConnection, CanvasNodeData, ViewportTransform } from "../types";
|
import type { CanvasAssistantSession, CanvasConnection, CanvasNodeData, ViewportTransform } from "../types";
|
||||||
@@ -64,7 +64,7 @@ export const useCanvasStore = create<CanvasStore>()(
|
|||||||
projects: [],
|
projects: [],
|
||||||
createProject: (title = "未命名画布") => {
|
createProject: (title = "未命名画布") => {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const id = createId();
|
const id = nanoid();
|
||||||
const project: CanvasProject = {
|
const project: CanvasProject = {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
@@ -83,7 +83,7 @@ export const useCanvasStore = create<CanvasStore>()(
|
|||||||
importProject: (source) => {
|
importProject: (source) => {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const project: CanvasProject = {
|
const project: CanvasProject = {
|
||||||
id: createId(),
|
id: nanoid(),
|
||||||
title: source.title || "导入画布",
|
title: source.title || "导入画布",
|
||||||
createdAt: source.createdAt || now,
|
createdAt: source.createdAt || now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import localforage from "localforage";
|
|||||||
import { ModelPicker } from "@/components/model-picker";
|
import { ModelPicker } from "@/components/model-picker";
|
||||||
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
|
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
|
||||||
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
|
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
|
||||||
import { isAiConfigReady, useConfigStore, useEffectiveAiConfig, type AiConfig } from "@/stores/use-config-store";
|
import { isAiConfigReady, resolveEffectiveConfig, useConfigStore, type AiConfig } from "@/stores/use-config-store";
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { formatBytes, formatDuration, getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
|
import { formatBytes, formatDuration, getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
|
||||||
import { requestEdit, requestGeneration } from "@/services/api/image";
|
import { requestEdit, requestGeneration } from "@/services/api/image";
|
||||||
import { uploadImage } from "@/services/image-storage";
|
import { uploadImage } from "@/services/image-storage";
|
||||||
@@ -60,7 +60,7 @@ export default function ImagePage() {
|
|||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const config = useConfigStore((state) => state.config);
|
const config = useConfigStore((state) => state.config);
|
||||||
const effectiveConfig = useEffectiveAiConfig(config);
|
const effectiveConfig = useConfigStore((state) => resolveEffectiveConfig(state.config, state.publicSettings?.modelChannel || null));
|
||||||
const updateConfig = useConfigStore((state) => state.updateConfig);
|
const updateConfig = useConfigStore((state) => state.updateConfig);
|
||||||
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
|
||||||
const addAsset = useAssetStore((state) => state.addAsset);
|
const addAsset = useAssetStore((state) => state.addAsset);
|
||||||
@@ -97,7 +97,7 @@ export default function ImagePage() {
|
|||||||
const imageFiles = Array.from(files || []).filter((file) => file.type.startsWith("image/"));
|
const imageFiles = Array.from(files || []).filter((file) => file.type.startsWith("image/"));
|
||||||
const nextReferences = await Promise.all(imageFiles.map(async (file) => {
|
const nextReferences = await Promise.all(imageFiles.map(async (file) => {
|
||||||
const image = await uploadImage(file);
|
const image = await uploadImage(file);
|
||||||
return { id: createId(), name: file.name, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
|
return { id: nanoid(), name: file.name, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
|
||||||
}));
|
}));
|
||||||
setReferences((value) => [...value, ...nextReferences]);
|
setReferences((value) => [...value, ...nextReferences]);
|
||||||
};
|
};
|
||||||
@@ -112,7 +112,7 @@ export default function ImagePage() {
|
|||||||
}
|
}
|
||||||
const nextReferences = await Promise.all(blobs.map(async (blob, index) => {
|
const nextReferences = await Promise.all(blobs.map(async (blob, index) => {
|
||||||
const image = await uploadImage(blob);
|
const image = await uploadImage(blob);
|
||||||
return { id: createId(), name: `clipboard-${index + 1}.png`, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
|
return { id: nanoid(), name: `clipboard-${index + 1}.png`, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
|
||||||
}));
|
}));
|
||||||
setReferences((value) => [...value, ...nextReferences]);
|
setReferences((value) => [...value, ...nextReferences]);
|
||||||
message.success(`已读取 ${nextReferences.length} 张参考图`);
|
message.success(`已读取 ${nextReferences.length} 张参考图`);
|
||||||
@@ -139,7 +139,7 @@ export default function ImagePage() {
|
|||||||
setElapsedMs(0);
|
setElapsedMs(0);
|
||||||
setRunning(true);
|
setRunning(true);
|
||||||
setPreviewLog(null);
|
setPreviewLog(null);
|
||||||
setResults(Array.from({ length: generationCount }, () => ({ id: createId(), status: "pending" })));
|
setResults(Array.from({ length: generationCount }, () => ({ id: nanoid(), status: "pending" })));
|
||||||
const batchStartedAt = performance.now();
|
const batchStartedAt = performance.now();
|
||||||
setStartedAt(batchStartedAt);
|
setStartedAt(batchStartedAt);
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ export default function ImagePage() {
|
|||||||
|
|
||||||
const addResultToReferences = async (image: GeneratedImage, index: number) => {
|
const addResultToReferences = async (image: GeneratedImage, index: number) => {
|
||||||
const stored = await uploadImage(image.dataUrl);
|
const stored = await uploadImage(image.dataUrl);
|
||||||
setReferences((value) => [...value, { id: createId(), name: `result-${index + 1}.png`, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
|
setReferences((value) => [...value, { id: nanoid(), name: `result-${index + 1}.png`, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
|
||||||
message.success("已加入参考图");
|
message.success("已加入参考图");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ export default function ImagePage() {
|
|||||||
setPrompt(payload.content);
|
setPrompt(payload.content);
|
||||||
} else {
|
} else {
|
||||||
const stored = await uploadImage(payload.dataUrl);
|
const stored = await uploadImage(payload.dataUrl);
|
||||||
setReferences((value) => [...value, { id: createId(), name: payload.title, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
|
setReferences((value) => [...value, { id: nanoid(), name: payload.title, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
|
||||||
}
|
}
|
||||||
setAssetPickerOpen(false);
|
setAssetPickerOpen(false);
|
||||||
};
|
};
|
||||||
@@ -583,7 +583,7 @@ async function readStoredLogs() {
|
|||||||
|
|
||||||
function normalizeLog(log: Partial<GenerationLog>): GenerationLog {
|
function normalizeLog(log: Partial<GenerationLog>): GenerationLog {
|
||||||
return {
|
return {
|
||||||
id: log.id || createId(),
|
id: log.id || nanoid(),
|
||||||
createdAt: log.createdAt || Date.now(),
|
createdAt: log.createdAt || Date.now(),
|
||||||
title: log.title || log.model || "未命名",
|
title: log.title || log.model || "未命名",
|
||||||
time: log.time || new Date().toLocaleString("zh-CN", { hour12: false }),
|
time: log.time || new Date().toLocaleString("zh-CN", { hour12: false }),
|
||||||
@@ -601,7 +601,7 @@ function normalizeLog(log: Partial<GenerationLog>): GenerationLog {
|
|||||||
|
|
||||||
function buildLog({ prompt, model, config, durationMs, successCount, failCount, status, thumbnails }: { prompt: string; model: string; config: { size: string; quality: string; count?: string }; durationMs: number; successCount: number; failCount: number; status: GenerationLog["status"]; thumbnails: string[] }): GenerationLog {
|
function buildLog({ prompt, model, config, durationMs, successCount, failCount, status, thumbnails }: { prompt: string; model: string; config: { size: string; quality: string; count?: string }; durationMs: number; successCount: number; failCount: number; status: GenerationLog["status"]; thumbnails: string[] }): GenerationLog {
|
||||||
return {
|
return {
|
||||||
id: createId(),
|
id: nanoid(),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
title: prompt.slice(0, 12) || "未命名",
|
title: prompt.slice(0, 12) || "未命名",
|
||||||
time: new Date().toLocaleString("zh-CN", { hour12: false }),
|
time: new Date().toLocaleString("zh-CN", { hour12: false }),
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
export function createId() {
|
|
||||||
return nanoid();
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { buildApiUrl, type AiConfig } from "@/stores/use-config-store";
|
import { buildApiUrl, type AiConfig } from "@/stores/use-config-store";
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { dataUrlToFile } from "@/lib/image-utils";
|
import { dataUrlToFile } from "@/lib/image-utils";
|
||||||
import { imageToDataUrl } from "@/services/image-storage";
|
import { imageToDataUrl } from "@/services/image-storage";
|
||||||
import type { ReferenceImage } from "@/types/image";
|
import type { ReferenceImage } from "@/types/image";
|
||||||
@@ -31,7 +31,7 @@ function parseImagePayload(payload: ImageApiResponse) {
|
|||||||
payload.data
|
payload.data
|
||||||
?.map(resolveImageDataUrl)
|
?.map(resolveImageDataUrl)
|
||||||
.filter((value): value is string => Boolean(value))
|
.filter((value): value is string => Boolean(value))
|
||||||
.map((dataUrl) => ({ id: createId(), dataUrl })) || [];
|
.map((dataUrl) => ({ id: nanoid(), dataUrl })) || [];
|
||||||
|
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
throw new Error("接口没有返回图片");
|
throw new Error("接口没有返回图片");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
|
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { readImageMeta } from "@/lib/image-utils";
|
import { readImageMeta } from "@/lib/image-utils";
|
||||||
|
|
||||||
export type UploadedImage = {
|
export type UploadedImage = {
|
||||||
@@ -19,7 +19,7 @@ const objectUrls = new Map<string, string>();
|
|||||||
|
|
||||||
export async function uploadImage(input: string | Blob): Promise<UploadedImage> {
|
export async function uploadImage(input: string | Blob): Promise<UploadedImage> {
|
||||||
const blob = typeof input === "string" ? await (await fetch(input)).blob() : input;
|
const blob = typeof input === "string" ? await (await fetch(input)).blob() : input;
|
||||||
const storageKey = `image:${createId()}`;
|
const storageKey = `image:${nanoid()}`;
|
||||||
await store.setItem(storageKey, blob);
|
await store.setItem(storageKey, blob);
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
objectUrls.set(storageKey, url);
|
objectUrls.set(storageKey, url);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist, type PersistStorage, type StorageValue } from "zustand/middleware";
|
import { persist, type PersistStorage, type StorageValue } from "zustand/middleware";
|
||||||
|
|
||||||
import { createId } from "@/lib/id";
|
import { nanoid } from "nanoid";
|
||||||
import { localForageStorage } from "@/lib/localforage-storage";
|
import { localForageStorage } from "@/lib/localforage-storage";
|
||||||
import { cleanupUnusedImages, resolveImageUrl, uploadImage } from "@/services/image-storage";
|
import { cleanupUnusedImages, resolveImageUrl, uploadImage } from "@/services/image-storage";
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ export const useAssetStore = create<AssetStore>()(
|
|||||||
assets: [],
|
assets: [],
|
||||||
addAsset: (asset) => {
|
addAsset: (asset) => {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const id = createId();
|
const id = nanoid();
|
||||||
set((state) => ({ assets: [{ ...asset, id, createdAt: now, updatedAt: now } as Asset, ...state.assets] }));
|
set((state) => ({ assets: [{ ...asset, id, createdAt: now, updatedAt: now } as Asset, ...state.assets] }));
|
||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
@@ -89,10 +88,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function useEffectiveAiConfig(config: AiConfig) {
|
export function resolveEffectiveConfig(config: AiConfig, modelChannel: AdminPublicSettings["modelChannel"] | null) {
|
||||||
const modelChannel = useConfigStore((state) => state.publicSettings?.modelChannel);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const channelMode = modelChannel?.allowCustomChannel ? config.channelMode : "remote";
|
const channelMode = modelChannel?.allowCustomChannel ? config.channelMode : "remote";
|
||||||
if (channelMode === "local" || !modelChannel) return { ...config, channelMode };
|
if (channelMode === "local" || !modelChannel) return { ...config, channelMode };
|
||||||
const models = modelChannel.availableModels;
|
const models = modelChannel.availableModels;
|
||||||
@@ -105,15 +101,10 @@ export function useEffectiveAiConfig(config: AiConfig) {
|
|||||||
textModel: models.includes(config.textModel) ? config.textModel : modelChannel.defaultTextModel || modelChannel.defaultModel,
|
textModel: models.includes(config.textModel) ? config.textModel : modelChannel.defaultTextModel || modelChannel.defaultModel,
|
||||||
systemPrompt: modelChannel.systemPrompt,
|
systemPrompt: modelChannel.systemPrompt,
|
||||||
};
|
};
|
||||||
}, [config, modelChannel]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeBaseUrl(value: string) {
|
|
||||||
return value.trim().replace(/\/+$/, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiUrl(baseUrl: string, path: string) {
|
export function buildApiUrl(baseUrl: string, path: string) {
|
||||||
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, "");
|
||||||
const apiBaseUrl = normalizedBaseUrl.endsWith("/v1") ? normalizedBaseUrl : `${normalizedBaseUrl}/v1`;
|
const apiBaseUrl = normalizedBaseUrl.endsWith("/v1") ? normalizedBaseUrl : `${normalizedBaseUrl}/v1`;
|
||||||
return `${apiBaseUrl}${path}`;
|
return `${apiBaseUrl}${path}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user