feat(asset): enhance asset handling by adding storageKey and updating upload functions
This commit is contained in:
@@ -417,9 +417,7 @@ export function ChatPanel() {
|
|||||||
onUploadImage={async (file) => {
|
onUploadImage={async (file) => {
|
||||||
shouldStickToBottomRef.current = true;
|
shouldStickToBottomRef.current = true;
|
||||||
const uploaded = await uploadImage(file);
|
const uploaded = await uploadImage(file);
|
||||||
return uploaded
|
return uploaded;
|
||||||
? { url: uploaded.url, filename: uploaded.filename }
|
|
||||||
: null;
|
|
||||||
}}
|
}}
|
||||||
onSendAttachment={async (file) => {
|
onSendAttachment={async (file) => {
|
||||||
shouldStickToBottomRef.current = true;
|
shouldStickToBottomRef.current = true;
|
||||||
|
|||||||
@@ -22,10 +22,39 @@ import { fetchQuickReplyListAll, type AdminQuickReply } from "@/lib/api/admin"
|
|||||||
import { generateUUID } from "@/lib/utils"
|
import { generateUUID } from "@/lib/utils"
|
||||||
|
|
||||||
type UploadedImage = {
|
type UploadedImage = {
|
||||||
|
assetId: string
|
||||||
|
provider: string
|
||||||
|
storageKey: string
|
||||||
url: string
|
url: string
|
||||||
filename?: string
|
filename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MessageImage = Image.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
dataAssetId: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-asset-id"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataAssetId ? { "data-asset-id": attributes.dataAssetId } : {},
|
||||||
|
},
|
||||||
|
dataProvider: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-provider"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataProvider ? { "data-provider": attributes.dataProvider } : {},
|
||||||
|
},
|
||||||
|
dataStorageKey: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-storage-key"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataStorageKey ? { "data-storage-key": attributes.dataStorageKey } : {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
type ImMessageEditorProps = {
|
type ImMessageEditorProps = {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
uploadingAsset?: boolean
|
uploadingAsset?: boolean
|
||||||
@@ -98,7 +127,7 @@ export function ImMessageEditor({
|
|||||||
orderedList: false,
|
orderedList: false,
|
||||||
horizontalRule: false,
|
horizontalRule: false,
|
||||||
}),
|
}),
|
||||||
Image,
|
MessageImage,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: "输入消息,Enter 发送,Shift + Enter 换行",
|
placeholder: "输入消息,Enter 发送,Shift + Enter 换行",
|
||||||
}),
|
}),
|
||||||
@@ -200,7 +229,7 @@ export function ImMessageEditor({
|
|||||||
removeImageByTitle(editor, placeholderId)
|
removeImageByTitle(editor, placeholderId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
replaceImageSourceByTitle(editor, placeholderId, uploaded.url, uploaded.filename || "image")
|
replaceImageSourceByTitle(editor, placeholderId, uploaded)
|
||||||
} finally {
|
} finally {
|
||||||
URL.revokeObjectURL(objectUrl)
|
URL.revokeObjectURL(objectUrl)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -387,8 +416,7 @@ function removeImageByTitle(editor: NonNullable<ReturnType<typeof useEditor>>, t
|
|||||||
function replaceImageSourceByTitle(
|
function replaceImageSourceByTitle(
|
||||||
editor: NonNullable<ReturnType<typeof useEditor>>,
|
editor: NonNullable<ReturnType<typeof useEditor>>,
|
||||||
title: string,
|
title: string,
|
||||||
src: string,
|
uploaded: UploadedImage
|
||||||
alt: string
|
|
||||||
) {
|
) {
|
||||||
const { state, view } = editor
|
const { state, view } = editor
|
||||||
let targetPos: number | null = null
|
let targetPos: number | null = null
|
||||||
@@ -404,8 +432,11 @@ function replaceImageSourceByTitle(
|
|||||||
}
|
}
|
||||||
const transaction = view.state.tr.setNodeMarkup(targetPos, undefined, {
|
const transaction = view.state.tr.setNodeMarkup(targetPos, undefined, {
|
||||||
...view.state.doc.nodeAt(targetPos)?.attrs,
|
...view.state.doc.nodeAt(targetPos)?.attrs,
|
||||||
src,
|
src: uploaded.url,
|
||||||
alt,
|
alt: uploaded.filename || "image",
|
||||||
|
dataAssetId: uploaded.assetId,
|
||||||
|
dataProvider: uploaded.provider,
|
||||||
|
dataStorageKey: uploaded.storageKey,
|
||||||
title: "",
|
title: "",
|
||||||
})
|
})
|
||||||
view.dispatch(transaction)
|
view.dispatch(transaction)
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export type AgentAsset = {
|
|||||||
id: number
|
id: number
|
||||||
assetId: string
|
assetId: string
|
||||||
provider: string
|
provider: string
|
||||||
|
storageKey: string
|
||||||
filename: string
|
filename: string
|
||||||
fileSize: number
|
fileSize: number
|
||||||
mimeType: string
|
mimeType: string
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export type ImAsset = {
|
|||||||
id: number
|
id: number
|
||||||
assetId: string
|
assetId: string
|
||||||
provider: string
|
provider: string
|
||||||
|
storageKey: string
|
||||||
filename: string
|
filename: string
|
||||||
fileSize: number
|
fileSize: number
|
||||||
mimeType: string
|
mimeType: string
|
||||||
|
|||||||
@@ -10,10 +10,39 @@ import { ImageIcon, PaperclipIcon, SendHorizonalIcon } from "lucide-react";
|
|||||||
import { generateUUID } from "@/lib/utils";
|
import { generateUUID } from "@/lib/utils";
|
||||||
|
|
||||||
type UploadedImage = {
|
type UploadedImage = {
|
||||||
|
assetId: string;
|
||||||
|
provider: string;
|
||||||
|
storageKey: string;
|
||||||
url: string;
|
url: string;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MessageImage = Image.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
dataAssetId: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-asset-id"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataAssetId ? { "data-asset-id": attributes.dataAssetId } : {},
|
||||||
|
},
|
||||||
|
dataProvider: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-provider"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataProvider ? { "data-provider": attributes.dataProvider } : {},
|
||||||
|
},
|
||||||
|
dataStorageKey: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-storage-key"),
|
||||||
|
renderHTML: (attributes) =>
|
||||||
|
attributes.dataStorageKey ? { "data-storage-key": attributes.dataStorageKey } : {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
type MessageEditorProps = {
|
type MessageEditorProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
uploadingAsset?: boolean;
|
uploadingAsset?: boolean;
|
||||||
@@ -61,7 +90,7 @@ export function MessageEditor({
|
|||||||
orderedList: false,
|
orderedList: false,
|
||||||
horizontalRule: false,
|
horizontalRule: false,
|
||||||
}),
|
}),
|
||||||
Image,
|
MessageImage,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: "输入消息,Enter 发送,Shift + Enter 换行",
|
placeholder: "输入消息,Enter 发送,Shift + Enter 换行",
|
||||||
}),
|
}),
|
||||||
@@ -161,12 +190,7 @@ export function MessageEditor({
|
|||||||
removeImageByTitle(editor, placeholderId);
|
removeImageByTitle(editor, placeholderId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
replaceImageSourceByTitle(
|
replaceImageSourceByTitle(editor, placeholderId, uploaded);
|
||||||
editor,
|
|
||||||
placeholderId,
|
|
||||||
uploaded.url,
|
|
||||||
uploaded.filename || "image",
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLocalUploading(false);
|
setLocalUploading(false);
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
@@ -314,8 +338,7 @@ function removeImageByTitle(editor: NonNullable<ReturnType<typeof useEditor>>, t
|
|||||||
function replaceImageSourceByTitle(
|
function replaceImageSourceByTitle(
|
||||||
editor: NonNullable<ReturnType<typeof useEditor>>,
|
editor: NonNullable<ReturnType<typeof useEditor>>,
|
||||||
title: string,
|
title: string,
|
||||||
src: string,
|
uploaded: UploadedImage,
|
||||||
alt: string,
|
|
||||||
) {
|
) {
|
||||||
const { state, view } = editor;
|
const { state, view } = editor;
|
||||||
let targetPos: number | null = null;
|
let targetPos: number | null = null;
|
||||||
@@ -331,8 +354,11 @@ function replaceImageSourceByTitle(
|
|||||||
}
|
}
|
||||||
const transaction = view.state.tr.setNodeMarkup(targetPos, undefined, {
|
const transaction = view.state.tr.setNodeMarkup(targetPos, undefined, {
|
||||||
...view.state.doc.nodeAt(targetPos)?.attrs,
|
...view.state.doc.nodeAt(targetPos)?.attrs,
|
||||||
src,
|
src: uploaded.url,
|
||||||
alt,
|
alt: uploaded.filename || "image",
|
||||||
|
dataAssetId: uploaded.assetId,
|
||||||
|
dataProvider: uploaded.provider,
|
||||||
|
dataStorageKey: uploaded.storageKey,
|
||||||
title: "",
|
title: "",
|
||||||
});
|
});
|
||||||
view.dispatch(transaction);
|
view.dispatch(transaction);
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export type WidgetAsset = {
|
|||||||
id: number;
|
id: number;
|
||||||
assetId: string;
|
assetId: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
|
storageKey: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
type RealtimeEnvelope,
|
type RealtimeEnvelope,
|
||||||
} from "@/lib/services/realtime";
|
} from "@/lib/services/realtime";
|
||||||
import type {
|
import type {
|
||||||
|
WidgetAsset,
|
||||||
WidgetConfigResponse,
|
WidgetConfigResponse,
|
||||||
WidgetConversation,
|
WidgetConversation,
|
||||||
WidgetMessage,
|
WidgetMessage,
|
||||||
@@ -111,9 +112,7 @@ export interface ChatStore {
|
|||||||
setIsVisible: (isVisible: boolean) => void;
|
setIsVisible: (isVisible: boolean) => void;
|
||||||
bootstrap: () => void;
|
bootstrap: () => void;
|
||||||
handleSendMessage: (html: string) => Promise<void>;
|
handleSendMessage: (html: string) => Promise<void>;
|
||||||
uploadMessageImage: (
|
uploadMessageImage: (file: File) => Promise<WidgetAsset | null>;
|
||||||
file: File,
|
|
||||||
) => Promise<{ url: string; filename?: string } | null>;
|
|
||||||
sendAttachment: (file: File) => Promise<void>;
|
sendAttachment: (file: File) => Promise<void>;
|
||||||
retry: () => void;
|
retry: () => void;
|
||||||
disconnectSocket: () => void;
|
disconnectSocket: () => void;
|
||||||
@@ -507,7 +506,7 @@ export const useChatStore = create<ChatStore>((set, get) => {
|
|||||||
set({ error: "" });
|
set({ error: "" });
|
||||||
try {
|
try {
|
||||||
const asset = await uploadImage(conversationId, file);
|
const asset = await uploadImage(conversationId, file);
|
||||||
return { url: asset.url, filename: asset.filename };
|
return asset;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
set({ error: e instanceof Error ? e.message : "发送图片失败" });
|
set({ error: e instanceof Error ? e.message : "发送图片失败" });
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user