fix(ai): 修复AI代理请求和图片生成功能

- 添加详细的错误日志记录,包括请求读取、渠道选择、请求构建和响应处理的失败情况
- 统一AI接口请求失败返回消息为"AI 接口请求失败"
- 添加对上游响应状态码>=400的检查和错误处理
- 在JWT解析中验证签名方法有效性,防止无效登录状态
- 移除JWT密钥警告日志,改为运行时生成随机密钥
- 调整应用配置模态框中云端和本地选项的显示顺序
- 添加图片生成元数据记录生成类型、模型、尺寸、质量和参考图等信息
- 实现空图片节点直接生成图片而不保留空框的功能
- 完善图片重试逻辑,优先使用保存的元数据进行重试
- 添加参考图片丢失时的错误提示
- 修复图片节点样式圆角显示问题
- 更新AI代理路径为/api/v1/*保持OpenAI风格
- 添加系统设置页面的安全警告提示
This commit is contained in:
HouYunFei
2026-05-21 17:50:14 +08:00
parent f9b2759499
commit d40b9b53c2
14 changed files with 214 additions and 51 deletions
+6 -1
View File
@@ -2,7 +2,7 @@
import { CheckCircleOutlined, DeleteOutlined, FormatPainterOutlined, PlusOutlined, ReloadOutlined, SaveOutlined } from "@ant-design/icons";
import { json } from "@codemirror/lang-json";
import { App, Button, Card, Col, Drawer, Flex, Form, Input, InputNumber, Modal, Row, Segmented, Select, Space, Switch, Table, Tabs, Tag, Typography } from "antd";
import { Alert, App, Button, Card, Col, Drawer, Flex, Form, Input, InputNumber, Modal, Row, Segmented, Select, Space, Switch, Table, Tabs, Tag, Typography } from "antd";
import dynamic from "next/dynamic";
import { useEffect, useMemo, useState } from "react";
import { EditorView } from "@uiw/react-codemirror";
@@ -294,6 +294,11 @@ export default function AdminSettingsPage() {
) : activeMode === "visual" ? (
<Form form={form} layout="vertical" initialValues={emptySettings} requiredMark={false}>
<Flex vertical gap={12}>
<Alert
showIcon
type="warning"
message="当前还没有完整用户体系,所有访问到站点的用户都可以无条件使用后端渠道 API。请不要公网部署,避免私有渠道额度被他人消耗。"
/>
<Button type="primary" icon={<PlusOutlined />} onClick={() => openChannelDrawer(null)}></Button>
<Table
rowKey={(_, index) => String(index)}
@@ -33,7 +33,8 @@ import { CanvasToolbar } from "../components/canvas-toolbar";
import { AssetPickerModal, type AssetPickerTab, type InsertAssetPayload } from "../components/asset-picker-modal";
import { CanvasZoomControls } from "../components/canvas-zoom-controls";
import { useCanvasStore } from "../stores/use-canvas-store";
import { CanvasNodeType, type CanvasAssistantImage, type CanvasAssistantSession, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ConnectionHandle, type ContextMenuState, type Position, type SelectionBox, type ViewportTransform } from "../types";
import { CanvasNodeType, type CanvasAssistantImage, type CanvasAssistantSession, type CanvasConnection, type CanvasImageGenerationType, type CanvasNodeData, type CanvasNodeMetadata, type ConnectionHandle, type ContextMenuState, type Position, type SelectionBox, type ViewportTransform } from "../types";
import type { ReferenceImage } from "@/types/image";
type CanvasClipboard = {
nodes: CanvasNodeData[];
@@ -1316,6 +1317,7 @@ function InfiniteCanvasPage() {
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
const title = buildAngleLabel(params);
const prompt = buildAnglePrompt(params);
const generationMetadata = buildImageGenerationMetadata("edit", generationConfig, 1, [{ id: node.id, name: `${node.title || node.id}.png`, type: node.metadata.mimeType || "image/png", dataUrl: node.metadata.content, storageKey: node.metadata.storageKey }]);
setAngleNodeId(null);
setRunningNodeId(childId);
setNodes((prev) => [...prev, {
@@ -1325,7 +1327,7 @@ function InfiniteCanvasPage() {
position: { x: node.position.x + node.width + 96, y: node.position.y },
width: imageConfig.width,
height: imageConfig.height,
metadata: { prompt, status: NODE_STATUS_LOADING },
metadata: { prompt, status: NODE_STATUS_LOADING, ...generationMetadata },
}]);
setConnections((prev) => [...prev, { id: nanoid(), fromNodeId: node.id, toNodeId: childId }]);
setSelectedNodeIds(new Set([childId]));
@@ -1334,7 +1336,7 @@ function InfiniteCanvasPage() {
const image = await requestEdit(generationConfig, prompt, [{ id: node.id, name: `${node.title || node.id}.png`, type: node.metadata.mimeType || "image/png", dataUrl: node.metadata.content, storageKey: node.metadata.storageKey }]).then((items) => items[0]);
const uploaded = await uploadImage(image.dataUrl);
const size = fitImageNodeSize(uploaded.width, uploaded.height, imageConfig.width, imageConfig.height);
setNodes((prev) => prev.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...imageMetadata(uploaded), prompt } } : item));
setNodes((prev) => prev.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...item.metadata, ...imageMetadata(uploaded), prompt, ...generationMetadata } } : item));
} catch (error) {
const errorDetails = error instanceof Error ? error.message : "生成失败";
setNodes((prev) => prev.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails } } : item));
@@ -1364,7 +1366,7 @@ function InfiniteCanvasPage() {
setNodes((prev) =>
prev.map((node) =>
node.id === target.nodeId
? { ...node, type: CanvasNodeType.Image, title: file.name, width: size.width, height: size.height, metadata: { ...node.metadata, ...imageMetadata(image), errorDetails: undefined, freeResize: false, isBatchRoot: undefined, batchRootId: undefined, batchChildIds: undefined, primaryImageId: undefined, imageBatchExpanded: undefined } }
? { ...node, type: CanvasNodeType.Image, title: file.name, width: size.width, height: size.height, metadata: { ...node.metadata, ...imageMetadata(image), errorDetails: undefined, freeResize: false, isBatchRoot: undefined, batchRootId: undefined, batchChildIds: undefined, batchUsesReferenceImages: undefined, generationType: undefined, model: undefined, size: undefined, quality: undefined, count: undefined, references: undefined, primaryImageId: undefined, imageBatchExpanded: undefined } }
: node,
),
);
@@ -1455,30 +1457,33 @@ function InfiniteCanvasPage() {
const count = getGenerationCount(generationConfig.count);
const isConfigNode = sourceNode?.type === CanvasNodeType.Config;
const isImageNode = sourceNode?.type === CanvasNodeType.Image;
const isEmptyImageNode = isImageNode && !sourceNode?.metadata?.content;
const sourceReference = isImageNode && sourceNode?.metadata?.content
? [{ id: sourceNode.id, name: `${sourceNode.title || sourceNode.id}.png`, type: sourceNode.metadata.mimeType || "image/png", dataUrl: sourceNode.metadata.content, storageKey: sourceNode.metadata.storageKey }]
: [];
const referenceImages = [...sourceReference, ...generationContext.referenceImages];
const generationType = referenceImages.length ? "edit" as const : "generation" as const;
const generationMetadata = buildImageGenerationMetadata(generationType, generationConfig, count, referenceImages);
const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : isImageNode ? CanvasNodeType.Image : CanvasNodeType.Text];
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
const gap = 96;
const rowGap = 36;
const rootId = nanoid();
const rootId = isEmptyImageNode ? nodeId : nanoid();
const childIds = count > 1 ? Array.from({ length: count }, () => nanoid()) : [];
const targetIds = count > 1 ? childIds : [rootId];
pendingChildIds = [rootId, ...childIds];
pendingChildIds = isEmptyImageNode ? childIds : [rootId, ...childIds];
const rootNode: CanvasNodeData = {
id: rootId,
type: CanvasNodeType.Image,
title: effectivePrompt.slice(0, 32) || "Generated Image",
position: {
x: parentPosition.x + parentConfig.width + gap,
x: isEmptyImageNode ? parentPosition.x : parentPosition.x + parentConfig.width + gap,
y: parentPosition.y + parentConfig.height / 2 - imageConfig.height / 2,
},
width: imageConfig.width,
height: imageConfig.height,
metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, isBatchRoot: count > 1, batchChildIds: count > 1 ? childIds : undefined, imageBatchExpanded: count > 1 ? true : undefined },
width: isEmptyImageNode ? sourceNode?.width || imageConfig.width : imageConfig.width,
height: isEmptyImageNode ? sourceNode?.height || imageConfig.height : imageConfig.height,
metadata: { prompt: effectivePrompt, status: NODE_STATUS_LOADING, isBatchRoot: count > 1, batchChildIds: count > 1 ? childIds : undefined, batchUsesReferenceImages: referenceImages.length > 0, ...generationMetadata, imageBatchExpanded: count > 1 ? true : undefined },
};
const childNodes: CanvasNodeData[] = childIds.map((id, index) => ({
id,
@@ -1490,9 +1495,12 @@ function InfiniteCanvasPage() {
},
width: imageConfig.width,
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, ...generationMetadata },
}));
const batchConnections = [{ id: nanoid(), fromNodeId: nodeId, toNodeId: rootId }, ...childIds.map((childId) => ({ id: nanoid(), fromNodeId: rootId, toNodeId: childId }))];
const batchConnections = [
...(isEmptyImageNode ? [] : [{ id: nanoid(), fromNodeId: nodeId, toNodeId: rootId }]),
...childIds.map((childId) => ({ id: nanoid(), fromNodeId: rootId, toNodeId: childId })),
];
setNodes((prev) => [
...prev.map((node) =>
@@ -1500,6 +1508,13 @@ function InfiniteCanvasPage() {
? isConfigNode ? {
...node,
metadata: { ...node.metadata, prompt, status: NODE_STATUS_LOADING, errorDetails: undefined },
} : isEmptyImageNode ? {
...node,
position: rootNode.position,
width: rootNode.width,
height: rootNode.height,
title: rootNode.title,
metadata: { ...node.metadata, ...rootNode.metadata, errorDetails: undefined },
} : isImageNode ? {
...node,
metadata: { ...node.metadata, status: NODE_STATUS_SUCCESS, errorDetails: undefined },
@@ -1513,7 +1528,7 @@ function InfiniteCanvasPage() {
}
: node,
),
rootNode,
...isEmptyImageNode ? [] : [rootNode],
...childNodes,
]);
setConnections((prev) => [...prev, ...batchConnections]);
@@ -1522,6 +1537,7 @@ function InfiniteCanvasPage() {
setDialogNodeId(nodeId);
let hasSuccess = false;
let hasFailure = false;
await Promise.all(targetIds.map(async (targetId) => {
try {
const image = referenceImages.length
@@ -1544,12 +1560,15 @@ function InfiniteCanvasPage() {
return true;
} catch (error) {
const errorDetails = error instanceof Error ? error.message : "生成失败";
hasFailure = true;
setNodes((prev) => prev.map((node) => node.id === targetId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails } } : node));
return false;
}
}));
if (hasFailure) message.error(hasSuccess ? "部分图片生成失败" : "全部图片生成失败");
setNodes((prev) => prev.map((node) =>
node.id === nodeId && isConfigNode ? { ...node, metadata: { ...node.metadata, status: hasSuccess ? NODE_STATUS_SUCCESS : NODE_STATUS_ERROR, errorDetails: hasSuccess ? undefined : "全部图片生成失败" } }
: node.id === nodeId && isEmptyImageNode ? { ...node, metadata: { ...node.metadata, status: hasSuccess ? NODE_STATUS_SUCCESS : NODE_STATUS_ERROR, errorDetails: hasSuccess ? undefined : "全部图片生成失败" } }
: node.id === rootId && !hasSuccess ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails: "全部图片生成失败" } }
: node,
));
@@ -1603,6 +1622,7 @@ function InfiniteCanvasPage() {
);
} catch (error) {
const errorDetails = error instanceof Error ? error.message : "生成失败";
message.error(errorDetails);
setNodes((prev) =>
prev.map((node) =>
node.id === nodeId || pendingChildIds.includes(node.id)
@@ -1620,24 +1640,42 @@ function InfiniteCanvasPage() {
const handleRetryNode = useCallback(
async (node: CanvasNodeData) => {
const sourceNode = findRetrySourceNode(node.id, nodesRef.current, connectionsRef.current) || node;
const generationConfig = { ...buildGenerationConfig(effectiveConfig, sourceNode, node.type === CanvasNodeType.Text ? "text" : "image"), count: "1" };
const batchRoot = node.metadata?.batchRootId ? nodesRef.current.find((item) => item.id === node.metadata?.batchRootId) : null;
const savedImageMetadata = node.type === CanvasNodeType.Image ? { ...batchRoot?.metadata, ...node.metadata } : undefined;
const hasSavedImageMetadata = Boolean(savedImageMetadata?.generationType);
const generationConfig = hasSavedImageMetadata && savedImageMetadata
? { ...effectiveConfig, model: savedImageMetadata.model || effectiveConfig.imageModel || effectiveConfig.model, quality: savedImageMetadata.quality || effectiveConfig.quality, size: savedImageMetadata.size || effectiveConfig.size, count: "1" }
: { ...buildGenerationConfig(effectiveConfig, sourceNode, node.type === CanvasNodeType.Text ? "text" : "image"), count: "1" };
if (!isAiConfigReady(generationConfig, generationConfig.model)) {
openConfigDialog(true);
return;
}
const context = await hydrateNodeGenerationContext(buildNodeGenerationContext(sourceNode.id, nodesRef.current, connectionsRef.current, sourceNode.metadata?.prompt || node.metadata?.prompt || ""));
const prompt = context.prompt.trim();
const context = hasSavedImageMetadata ? null : await hydrateNodeGenerationContext(buildNodeGenerationContext(sourceNode.id, nodesRef.current, connectionsRef.current, sourceNode.metadata?.prompt || node.metadata?.prompt || ""));
const prompt = (savedImageMetadata?.prompt || context?.prompt || "").trim();
if (!prompt) {
message.warning("找不到提示词,无法重试");
return;
}
const generationType = savedImageMetadata?.generationType;
const useReferenceImages = generationType ? generationType === "edit" : Boolean(context?.referenceImages.length);
const retryReferenceImages = hasSavedImageMetadata && savedImageMetadata
? await resolveMetadataReferences(savedImageMetadata)
: useReferenceImages
? context?.referenceImages.length ? context.referenceImages : sourceNodeReferenceImages(batchRoot || sourceNode)
: [];
if (useReferenceImages && !retryReferenceImages) {
message.error("参考图片已丢失,无法继续重试");
setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails: "参考图片已丢失,无法继续重试" } } : item));
return;
}
setRunningNodeId(node.id);
setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_LOADING, errorDetails: undefined } } : item));
try {
if (node.type === CanvasNodeType.Text) {
if (!context) return;
let streamed = "";
const answer = await requestImageQuestion(generationConfig, buildNodeChatMessages({ ...context, prompt }), (text) => {
streamed = text;
@@ -1647,21 +1685,25 @@ function InfiniteCanvasPage() {
return;
}
const image = context.referenceImages.length
? await requestEdit(generationConfig, prompt, context.referenceImages).then((items) => items[0])
const image = useReferenceImages
? await requestEdit(generationConfig, prompt, retryReferenceImages).then((items) => items[0])
: await requestGeneration(generationConfig, prompt).then((items) => items[0]);
const uploadedImage = await uploadImage(image.dataUrl);
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
const imageSize = fitImageNodeSize(uploadedImage.width, uploadedImage.height, imageConfig.width, imageConfig.height);
const generationMetadata = savedImageMetadata?.generationType
? { generationType: savedImageMetadata.generationType, model: generationConfig.model, size: generationConfig.size, quality: generationConfig.quality, count: savedImageMetadata.count || 1, references: savedImageMetadata.references }
: buildImageGenerationMetadata(useReferenceImages ? "edit" : "generation", generationConfig, 1, retryReferenceImages || []);
setNodes((prev) => prev.map((item) => item.id === node.id ? {
...item,
type: CanvasNodeType.Image,
width: imageSize.width,
height: imageSize.height,
metadata: { ...item.metadata, ...imageMetadata(uploadedImage), prompt },
metadata: { ...item.metadata, ...imageMetadata(uploadedImage), prompt, ...generationMetadata },
} : item));
} catch (error) {
const errorDetails = error instanceof Error ? error.message : "生成失败";
message.error(errorDetails);
setNodes((prev) => prev.map((item) => item.id === node.id ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails } } : item));
} finally {
setRunningNodeId(null);
@@ -2237,6 +2279,31 @@ function imageMetadata(image: UploadedImage): CanvasNodeMetadata {
return { content: image.url, storageKey: image.storageKey, status: "success", naturalWidth: image.width, naturalHeight: image.height, bytes: image.bytes, mimeType: image.mimeType };
}
function buildImageGenerationMetadata(type: CanvasImageGenerationType, config: AiConfig, count: number, references: ReferenceImage[]): CanvasNodeMetadata {
return {
generationType: type,
model: config.model,
size: config.size,
quality: config.quality,
count,
references: references.map(referenceUrl).filter((url): url is string => Boolean(url)),
};
}
function referenceUrl(image: ReferenceImage) {
return image.storageKey || image.url || (!image.dataUrl.startsWith("data:") ? image.dataUrl : undefined);
}
async function resolveMetadataReferences(metadata: CanvasNodeMetadata) {
if (metadata.generationType !== "edit") return [];
if (!metadata.references?.length) return null;
const references = await Promise.all(metadata.references.map(async (url, index) => {
const dataUrl = url.startsWith("image:") ? await resolveImageUrl(url, "") : url;
return dataUrl ? { id: `${index}`, name: `reference-${index}.png`, type: "image/png", dataUrl, storageKey: url.startsWith("image:") ? url : undefined } : null;
}));
return references.every(Boolean) ? references as ReferenceImage[] : null;
}
async function hydrateCanvasImages(nodes: CanvasNodeData[]) {
return Promise.all(nodes.map(async (node) => {
const content = node.metadata?.content;
@@ -2306,7 +2373,7 @@ function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | undefine
model: node?.metadata?.model || defaultModel || config.model || defaultConfig.model,
quality: config.quality || defaultConfig.quality,
size: node?.metadata?.size || config.size || defaultConfig.size,
count: String(node?.metadata?.count || config.count || defaultConfig.count),
count: String(node?.metadata?.count || (mode === "image" ? 3 : config.count) || defaultConfig.count),
};
}
@@ -2330,6 +2397,17 @@ function findRetrySourceNode(nodeId: string, nodes: CanvasNodeData[], connection
return null;
}
function sourceNodeReferenceImages(node: CanvasNodeData | null) {
if (!node || node.type !== CanvasNodeType.Image || !node.metadata?.content) return [];
return [{
id: node.id,
name: `${node.title || node.id}.png`,
type: node.metadata.mimeType || "image/png",
dataUrl: node.metadata.content,
storageKey: node.metadata.storageKey,
}];
}
function isHiddenBatchChild(node: CanvasNodeData, nodes: CanvasNodeData[], collapsingBatchIds?: Set<string>) {
const rootId = node.metadata?.batchRootId;
if (!rootId) return false;
@@ -495,13 +495,15 @@ function ImageContent({
return (
<BatchFrame batchCount={isBatchRoot ? batchCount : 0} batchExpanded={batchExpanded} batchOpening={batchOpening} batchRecovering={batchRecovering} onToggleBatch={onToggleBatch}>
<img
src={node.metadata!.content!}
alt={node.title}
draggable={false}
onDragStart={(event) => event.preventDefault()}
className={`pointer-events-none block h-full w-full select-none rounded-[inherit] ${node.metadata?.freeResize ? "object-fill" : "object-contain"}`}
/>
<div className="h-full w-full overflow-hidden rounded-3xl">
<img
src={node.metadata!.content!}
alt={node.title}
draggable={false}
onDragStart={(event) => event.preventDefault()}
className={`pointer-events-none block h-full w-full select-none ${node.metadata?.freeResize ? "object-fill" : "object-contain"}`}
/>
</div>
{isBatchRoot ? (
<button
type="button"
+5
View File
@@ -17,6 +17,7 @@ export enum CanvasNodeType {
export type CanvasNodeStatus = "idle" | "success" | "loading" | "error";
export type CanvasGenerationMode = "text" | "image";
export type CanvasImageGenerationType = "generation" | "edit";
export type CanvasNodeMetadata = {
content?: string;
@@ -25,15 +26,19 @@ export type CanvasNodeMetadata = {
errorDetails?: string;
fontSize?: number;
generationMode?: CanvasGenerationMode;
generationType?: CanvasImageGenerationType;
model?: string;
size?: string;
quality?: string;
count?: number;
references?: string[];
naturalWidth?: number;
naturalHeight?: number;
freeResize?: boolean;
isBatchRoot?: boolean;
batchRootId?: string;
batchChildIds?: string[];
batchUsesReferenceImages?: boolean;
primaryImageId?: string;
imageBatchExpanded?: boolean;
inputOrder?: string[];
@@ -70,7 +70,7 @@ export function AppConfigModal() {
size="middle"
value={effectiveMode}
onChange={(value) => updateConfig("channelMode", value as AiConfig["channelMode"])}
options={[{ label: "云端渠道", value: "remote" }, { label: "本地直连", value: "local" }]}
options={[{ label: "本地直连", value: "local" },{ label: "云端渠道", value: "remote" }]}
/>
</Form.Item>
) : null}
+25 -4
View File
@@ -14,6 +14,8 @@ export type ChatCompletionMessage = {
type ImageApiResponse = {
data?: Array<Record<string, unknown>>;
error?: { message?: string };
code?: number;
msg?: string;
};
function resolveImageDataUrl(item: Record<string, unknown>) {
@@ -27,6 +29,9 @@ function resolveImageDataUrl(item: Record<string, unknown>) {
}
function parseImagePayload(payload: ImageApiResponse) {
if (typeof payload.code === "number" && payload.code !== 0) {
throw new Error(payload.msg || "请求失败");
}
const images =
payload.data
?.map(resolveImageDataUrl)
@@ -41,8 +46,9 @@ function parseImagePayload(payload: ImageApiResponse) {
}
function readAxiosError(error: unknown, fallback: string) {
if (axios.isAxiosError<{ error?: { message?: string } }>(error)) {
return error.response?.data?.error?.message || (error.response?.status ? `${fallback}${error.response.status}` : fallback);
if (axios.isAxiosError<{ error?: { message?: string }; msg?: string; code?: number }>(error)) {
const responseData = error.response?.data;
return responseData?.msg || responseData?.error?.message || (error.response?.status ? `${fallback}${error.response.status}` : fallback);
}
return error instanceof Error ? error.message : fallback;
}
@@ -64,7 +70,7 @@ function withSystemPrompt(config: AiConfig, prompt: string) {
}
function aiApiUrl(config: AiConfig, path: string) {
return config.channelMode === "remote" ? `/api/ai${path}` : buildApiUrl(config.baseUrl, path);
return config.channelMode === "remote" ? `/api/v1${path}` : buildApiUrl(config.baseUrl, path);
}
function aiHeaders(config: AiConfig, contentType?: string) {
@@ -134,7 +140,7 @@ export async function requestImageQuestion(config: AiConfig, messages: ChatCompl
let processedLength = 0;
try {
await axios.post(
const response = await axios.post(
aiApiUrl(config, "/chat/completions"),
{
model: config.model,
@@ -162,6 +168,21 @@ export async function requestImageQuestion(config: AiConfig, messages: ChatCompl
},
},
);
if (typeof response.data === "object" && response.data && "code" in response.data && (response.data as { code?: number; msg?: string }).code !== 0) {
throw new Error((response.data as { msg?: string }).msg || "请求失败");
}
if (typeof response.data === "string") {
let apiError = "";
try {
const payload = JSON.parse(response.data) as { code?: number; msg?: string };
if (typeof payload.code === "number" && payload.code !== 0) {
apiError = payload.msg || "请求失败";
}
} catch {
// ignore plain text stream content
}
if (apiError) throw new Error(apiError);
}
if (buffer) {
parseStreamChunk(buffer, (delta) => {
answer += delta;