Merge branch 'yongpxu-dev' into yongpxu-dev2
This commit is contained in:
@@ -23,12 +23,16 @@ import {
|
||||
SendOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import styles from "./PushTaskModal.module.scss";
|
||||
import {
|
||||
useCustomerStore,
|
||||
} from "@/store/module/weChat/customer";
|
||||
import { useCustomerStore } from "@/store/module/weChat/customer";
|
||||
import { getContactList, getGroupList } from "@/pages/pc/ckbox/weChat/api";
|
||||
|
||||
export type PushType = "friend-message" | "group-message" | "group-announcement";
|
||||
const DEFAULT_FRIEND_INTERVAL: [number, number] = [3, 10];
|
||||
const DEFAULT_MESSAGE_INTERVAL: [number, number] = [1, 3];
|
||||
|
||||
export type PushType =
|
||||
| "friend-message"
|
||||
| "group-message"
|
||||
| "group-announcement";
|
||||
|
||||
interface PushTaskModalProps {
|
||||
visible: boolean;
|
||||
@@ -67,8 +71,12 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
const [selectedAccounts, setSelectedAccounts] = useState<any[]>([]);
|
||||
const [selectedContacts, setSelectedContacts] = useState<ContactItem[]>([]);
|
||||
const [messageContent, setMessageContent] = useState("");
|
||||
const [friendInterval, setFriendInterval] = useState(10);
|
||||
const [messageInterval, setMessageInterval] = useState(1);
|
||||
const [friendInterval, setFriendInterval] = useState<[number, number]>([
|
||||
...DEFAULT_FRIEND_INTERVAL,
|
||||
]);
|
||||
const [messageInterval, setMessageInterval] = useState<[number, number]>([
|
||||
...DEFAULT_MESSAGE_INTERVAL,
|
||||
]);
|
||||
const [selectedTag, setSelectedTag] = useState<string>("");
|
||||
const [aiRewriteEnabled, setAiRewriteEnabled] = useState(false);
|
||||
const [aiPrompt, setAiPrompt] = useState("");
|
||||
@@ -120,8 +128,8 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
setSelectedAccounts([]);
|
||||
setSelectedContacts([]);
|
||||
setMessageContent("");
|
||||
setFriendInterval(10);
|
||||
setMessageInterval(1);
|
||||
setFriendInterval([...DEFAULT_FRIEND_INTERVAL]);
|
||||
setMessageInterval([...DEFAULT_MESSAGE_INTERVAL]);
|
||||
setSelectedTag("");
|
||||
setAiRewriteEnabled(false);
|
||||
setAiPrompt("");
|
||||
@@ -270,7 +278,9 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
setCurrentStep(2);
|
||||
} else if (currentStep === 2) {
|
||||
if (selectedContacts.length === 0) {
|
||||
message.warning(`请至少选择一个${pushType === "friend-message" ? "好友" : "群"}`);
|
||||
message.warning(
|
||||
`请至少选择一个${pushType === "friend-message" ? "好友" : "群"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
setCurrentStep(3);
|
||||
@@ -343,7 +353,9 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
<div className={styles.accountCards}>
|
||||
{filteredAccounts.length > 0 ? (
|
||||
filteredAccounts.map(account => {
|
||||
const isSelected = selectedAccounts.some(a => a.id === account.id);
|
||||
const isSelected = selectedAccounts.some(
|
||||
a => a.id === account.id,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={account.id}
|
||||
@@ -355,7 +367,8 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
size={48}
|
||||
style={{ backgroundColor: "#1890ff" }}
|
||||
>
|
||||
{!account.avatar && (account.nickname || account.name || "").charAt(0)}
|
||||
{!account.avatar &&
|
||||
(account.nickname || account.name || "").charAt(0)}
|
||||
</Avatar>
|
||||
<div className={styles.cardName}>
|
||||
{account.nickname || account.name || "未知"}
|
||||
@@ -570,10 +583,7 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
<Button type="text" icon="⭐" />
|
||||
</div>
|
||||
<div className={styles.aiRewriteSection}>
|
||||
<Switch
|
||||
checked={aiRewriteEnabled}
|
||||
onChange={setAiRewriteEnabled}
|
||||
/>
|
||||
<Switch checked={aiRewriteEnabled} onChange={setAiRewriteEnabled} />
|
||||
<span style={{ marginLeft: 8 }}>AI智能话术改写</span>
|
||||
{aiRewriteEnabled && (
|
||||
<Input
|
||||
@@ -598,13 +608,16 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
min={10}
|
||||
max={20}
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={friendInterval}
|
||||
onChange={setFriendInterval}
|
||||
onChange={value => setFriendInterval(value as [number, number])}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>{friendInterval} - 20</span>
|
||||
<span>
|
||||
{friendInterval[0]} - {friendInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
@@ -612,13 +625,18 @@ const PushTaskModal: React.FC<PushTaskModalProps> = ({
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={12}
|
||||
max={60}
|
||||
value={messageInterval}
|
||||
onChange={setMessageInterval}
|
||||
onChange={value =>
|
||||
setMessageInterval(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>{messageInterval} - 12</span>
|
||||
<span>
|
||||
{messageInterval[0]} - {messageInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
// 获取客服列表
|
||||
export function queryWorkbenchCreate(params) {
|
||||
return request("/v1/workbench/create", params, "POST");
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import { getContactList, getGroupList } from "@/pages/pc/ckbox/weChat/api";
|
||||
|
||||
import styles from "../../index.module.scss";
|
||||
import { ContactItem, PushType } from "../../types";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import type { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
interface ContactFilterValues {
|
||||
includeTags: string[];
|
||||
@@ -61,6 +63,8 @@ interface StepSelectContactsProps {
|
||||
selectedAccounts: any[];
|
||||
selectedContacts: ContactItem[];
|
||||
onChange: (contacts: ContactItem[]) => void;
|
||||
selectedTrafficPools: PoolSelectionItem[];
|
||||
onTrafficPoolsChange: (pools: PoolSelectionItem[]) => void;
|
||||
}
|
||||
|
||||
const StepSelectContacts: React.FC<StepSelectContactsProps> = ({
|
||||
@@ -68,6 +72,8 @@ const StepSelectContacts: React.FC<StepSelectContactsProps> = ({
|
||||
selectedAccounts,
|
||||
selectedContacts,
|
||||
onChange,
|
||||
selectedTrafficPools,
|
||||
onTrafficPoolsChange,
|
||||
}) => {
|
||||
const [contactsData, setContactsData] = useState<ContactItem[]>([]);
|
||||
const [loadingContacts, setLoadingContacts] = useState(false);
|
||||
@@ -415,6 +421,13 @@ const StepSelectContacts: React.FC<StepSelectContactsProps> = ({
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
<PoolSelection
|
||||
selectedOptions={selectedTrafficPools}
|
||||
onSelect={onTrafficPoolsChange}
|
||||
placeholder="选择流量池包"
|
||||
showSelectedList
|
||||
selectedListMaxHeight={200}
|
||||
/>
|
||||
<div className={styles.contentBody}>
|
||||
<div className={styles.contactList}>
|
||||
<div className={styles.listHeader}>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import request from "@/api/request";
|
||||
// 创建内容库参数
|
||||
export interface CreateContentLibraryParams {
|
||||
name: string;
|
||||
sourceType: number;
|
||||
sourceFriends?: string[];
|
||||
sourceGroups?: string[];
|
||||
keywordInclude?: string[];
|
||||
keywordExclude?: string[];
|
||||
aiPrompt?: string;
|
||||
timeEnabled?: number;
|
||||
timeStart?: string;
|
||||
timeEnd?: string;
|
||||
}
|
||||
|
||||
// 创建内容库
|
||||
export function createContentLibrary(
|
||||
params: CreateContentLibraryParams,
|
||||
): Promise<any> {
|
||||
return request("/v1/content/library/create", params, "POST");
|
||||
}
|
||||
|
||||
// 删除内容库
|
||||
export function deleteContentLibrary(params: { id: number }) {
|
||||
return request(`/v1/content/library/update`, params, "DELETE");
|
||||
}
|
||||
|
||||
// 智能话术改写
|
||||
export function aiEditContent(params: { aiPrompt: string; content: string }) {
|
||||
return request(`/v1/content/library/aiEditContent`, params, "GET");
|
||||
}
|
||||
@@ -254,6 +254,7 @@
|
||||
|
||||
.aiRewriteSection {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
gap: 12px;
|
||||
@@ -272,6 +273,16 @@
|
||||
.aiRewriteInput {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.aiRewriteActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aiRewriteButton {
|
||||
min-width: 96px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
Switch,
|
||||
message as antdMessage,
|
||||
} from "antd";
|
||||
import { CopyOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { CheckboxChangeEvent } from "antd/es/checkbox";
|
||||
|
||||
import styles from "./index.module.scss";
|
||||
@@ -18,6 +23,12 @@ import { ContactItem, ScriptGroup } from "../../types";
|
||||
import InputMessage from "./InputMessage/InputMessage";
|
||||
import ContentLibrarySelector from "./ContentLibrarySelector";
|
||||
import type { ContentItem } from "@/components/ContentSelection/data";
|
||||
import {
|
||||
createContentLibrary,
|
||||
deleteContentLibrary,
|
||||
aiEditContent,
|
||||
type CreateContentLibraryParams,
|
||||
} from "./api";
|
||||
|
||||
interface StepSendMessageProps {
|
||||
selectedAccounts: any[];
|
||||
@@ -25,10 +36,10 @@ interface StepSendMessageProps {
|
||||
targetLabel: string;
|
||||
messageContent: string;
|
||||
onMessageContentChange: (value: string) => void;
|
||||
friendInterval: number;
|
||||
onFriendIntervalChange: (value: number) => void;
|
||||
messageInterval: number;
|
||||
onMessageIntervalChange: (value: number) => void;
|
||||
friendInterval: [number, number];
|
||||
onFriendIntervalChange: (value: [number, number]) => void;
|
||||
messageInterval: [number, number];
|
||||
onMessageIntervalChange: (value: [number, number]) => void;
|
||||
selectedTag: string;
|
||||
onSelectedTagChange: (value: string) => void;
|
||||
aiRewriteEnabled: boolean;
|
||||
@@ -74,6 +85,10 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
selectedContentLibraries,
|
||||
onSelectedContentLibrariesChange,
|
||||
}) => {
|
||||
const [savingScriptGroup, setSavingScriptGroup] = useState(false);
|
||||
const [aiRewriting, setAiRewriting] = useState(false);
|
||||
const [deletingGroupIds, setDeletingGroupIds] = useState<string[]>([]);
|
||||
|
||||
const handleAddMessage = useCallback(
|
||||
(content?: string, showSuccess?: boolean) => {
|
||||
const finalContent = (content ?? messageContent).trim();
|
||||
@@ -103,24 +118,57 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
[currentScriptMessages, onCurrentScriptMessagesChange],
|
||||
);
|
||||
|
||||
const handleSaveScriptGroup = useCallback(() => {
|
||||
const handleSaveScriptGroup = useCallback(async () => {
|
||||
if (savingScriptGroup) {
|
||||
return;
|
||||
}
|
||||
if (currentScriptMessages.length === 0) {
|
||||
antdMessage.warning("请先添加消息内容");
|
||||
return;
|
||||
}
|
||||
const groupName =
|
||||
currentScriptName.trim() || `话术组${savedScriptGroups.length + 1}`;
|
||||
const newGroup: ScriptGroup = {
|
||||
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
const messages = [...currentScriptMessages];
|
||||
const params: CreateContentLibraryParams = {
|
||||
name: groupName,
|
||||
messages: currentScriptMessages,
|
||||
sourceType: 1,
|
||||
keywordInclude: messages,
|
||||
};
|
||||
onSavedScriptGroupsChange([...savedScriptGroups, newGroup]);
|
||||
onCurrentScriptMessagesChange([]);
|
||||
onCurrentScriptNameChange("");
|
||||
onMessageContentChange("");
|
||||
antdMessage.success("已保存为话术组");
|
||||
const trimmedPrompt = aiPrompt.trim();
|
||||
if (aiRewriteEnabled && trimmedPrompt) {
|
||||
params.aiPrompt = trimmedPrompt;
|
||||
}
|
||||
let hideLoading: ReturnType<typeof antdMessage.loading> | undefined;
|
||||
try {
|
||||
setSavingScriptGroup(true);
|
||||
hideLoading = antdMessage.loading("正在保存话术组...", 0);
|
||||
const response = await createContentLibrary(params);
|
||||
hideLoading?.();
|
||||
const responseId =
|
||||
response?.id ?? response?.data?.id ?? response?.libraryId;
|
||||
const newGroup: ScriptGroup = {
|
||||
id:
|
||||
responseId !== undefined
|
||||
? String(responseId)
|
||||
: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: groupName,
|
||||
messages,
|
||||
};
|
||||
onSavedScriptGroupsChange([...savedScriptGroups, newGroup]);
|
||||
onCurrentScriptMessagesChange([]);
|
||||
onCurrentScriptNameChange("");
|
||||
onMessageContentChange("");
|
||||
antdMessage.success("已保存为话术组");
|
||||
} catch (error) {
|
||||
hideLoading?.();
|
||||
console.error("保存话术组失败:", error);
|
||||
antdMessage.error("保存失败,请稍后重试");
|
||||
} finally {
|
||||
setSavingScriptGroup(false);
|
||||
}
|
||||
}, [
|
||||
aiPrompt,
|
||||
aiRewriteEnabled,
|
||||
currentScriptMessages,
|
||||
currentScriptName,
|
||||
onCurrentScriptMessagesChange,
|
||||
@@ -128,6 +176,100 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
onMessageContentChange,
|
||||
onSavedScriptGroupsChange,
|
||||
savedScriptGroups,
|
||||
savingScriptGroup,
|
||||
]);
|
||||
|
||||
const handleAiRewrite = useCallback(async () => {
|
||||
if (!aiRewriteEnabled) {
|
||||
antdMessage.warning("请先开启AI智能话术改写");
|
||||
return;
|
||||
}
|
||||
const trimmedPrompt = aiPrompt.trim();
|
||||
const originalContent = messageContent;
|
||||
const trimmedContent = originalContent.trim();
|
||||
if (!trimmedPrompt) {
|
||||
antdMessage.warning("请输入改写提示词");
|
||||
return;
|
||||
}
|
||||
if (!trimmedContent) {
|
||||
antdMessage.warning("请输入需要改写的内容");
|
||||
return;
|
||||
}
|
||||
if (aiRewriting) {
|
||||
return;
|
||||
}
|
||||
let hideLoading: ReturnType<typeof antdMessage.loading> | undefined;
|
||||
try {
|
||||
setAiRewriting(true);
|
||||
hideLoading = antdMessage.loading("AI正在改写话术...", 0);
|
||||
const response = await aiEditContent({
|
||||
aiPrompt: trimmedPrompt,
|
||||
content: originalContent,
|
||||
});
|
||||
hideLoading?.();
|
||||
const normalizedResponse = response as {
|
||||
content?: string;
|
||||
contentAfter?: string;
|
||||
contentFront?: string;
|
||||
data?:
|
||||
| string
|
||||
| {
|
||||
content?: string;
|
||||
contentAfter?: string;
|
||||
contentFront?: string;
|
||||
};
|
||||
result?: string;
|
||||
};
|
||||
const dataField = normalizedResponse?.data;
|
||||
const dataContent =
|
||||
typeof dataField === "string"
|
||||
? dataField
|
||||
: (dataField?.content ?? undefined);
|
||||
const dataContentAfter =
|
||||
typeof dataField === "string" ? undefined : dataField?.contentAfter;
|
||||
const dataContentFront =
|
||||
typeof dataField === "string" ? undefined : dataField?.contentFront;
|
||||
|
||||
const primaryAfter =
|
||||
normalizedResponse?.contentAfter ?? dataContentAfter ?? undefined;
|
||||
const primaryFront =
|
||||
normalizedResponse?.contentFront ?? dataContentFront ?? undefined;
|
||||
|
||||
let rewrittenContent = "";
|
||||
if (typeof response === "string") {
|
||||
rewrittenContent = response;
|
||||
} else if (primaryAfter) {
|
||||
rewrittenContent = primaryFront
|
||||
? `${primaryFront}\n${primaryAfter}`
|
||||
: primaryAfter;
|
||||
} else if (typeof normalizedResponse?.content === "string") {
|
||||
rewrittenContent = normalizedResponse.content;
|
||||
} else if (typeof dataContent === "string") {
|
||||
rewrittenContent = dataContent;
|
||||
} else if (typeof normalizedResponse?.result === "string") {
|
||||
rewrittenContent = normalizedResponse.result;
|
||||
} else if (primaryFront) {
|
||||
rewrittenContent = primaryFront;
|
||||
}
|
||||
if (!rewrittenContent || typeof rewrittenContent !== "string") {
|
||||
antdMessage.error("AI改写失败,请稍后重试");
|
||||
return;
|
||||
}
|
||||
onMessageContentChange(rewrittenContent.trim());
|
||||
antdMessage.success("AI改写完成,请确认内容");
|
||||
} catch (error) {
|
||||
hideLoading?.();
|
||||
console.error("AI改写失败:", error);
|
||||
antdMessage.error("AI改写失败,请稍后重试");
|
||||
} finally {
|
||||
setAiRewriting(false);
|
||||
}
|
||||
}, [
|
||||
aiPrompt,
|
||||
aiRewriting,
|
||||
aiRewriteEnabled,
|
||||
messageContent,
|
||||
onMessageContentChange,
|
||||
]);
|
||||
|
||||
const handleApplyGroup = useCallback(
|
||||
@@ -145,23 +287,47 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
);
|
||||
|
||||
const handleDeleteGroup = useCallback(
|
||||
(groupId: string) => {
|
||||
const nextGroups = savedScriptGroups.filter(
|
||||
group => group.id !== groupId,
|
||||
);
|
||||
onSavedScriptGroupsChange(nextGroups);
|
||||
if (selectedScriptGroupIds.includes(groupId)) {
|
||||
const nextSelected = selectedScriptGroupIds.filter(
|
||||
id => id !== groupId,
|
||||
);
|
||||
onSelectedScriptGroupIdsChange(nextSelected);
|
||||
async (groupId: string) => {
|
||||
if (deletingGroupIds.includes(groupId)) {
|
||||
return;
|
||||
}
|
||||
const numericGroupId = Number(groupId);
|
||||
if (Number.isNaN(numericGroupId)) {
|
||||
antdMessage.error("无法删除:缺少有效的内容库ID");
|
||||
return;
|
||||
}
|
||||
let hideLoading: ReturnType<typeof antdMessage.loading> | undefined;
|
||||
try {
|
||||
setDeletingGroupIds(prev => [...prev, groupId]);
|
||||
hideLoading = antdMessage.loading("正在删除话术组...", 0);
|
||||
await deleteContentLibrary({ id: numericGroupId });
|
||||
hideLoading?.();
|
||||
const nextGroups = savedScriptGroups.filter(
|
||||
group => group.id !== groupId,
|
||||
);
|
||||
onSavedScriptGroupsChange(nextGroups);
|
||||
if (selectedScriptGroupIds.includes(groupId)) {
|
||||
const nextSelected = selectedScriptGroupIds.filter(
|
||||
id => id !== groupId,
|
||||
);
|
||||
onSelectedScriptGroupIdsChange(nextSelected);
|
||||
}
|
||||
antdMessage.success("已删除话术组");
|
||||
} catch (error) {
|
||||
hideLoading?.();
|
||||
console.error("删除话术组失败:", error);
|
||||
antdMessage.error("删除失败,请稍后重试");
|
||||
} finally {
|
||||
setDeletingGroupIds(prev =>
|
||||
prev.filter(deletingId => deletingId !== groupId),
|
||||
);
|
||||
}
|
||||
antdMessage.success("已删除话术组");
|
||||
},
|
||||
[
|
||||
deletingGroupIds,
|
||||
onSavedScriptGroupsChange,
|
||||
savedScriptGroups,
|
||||
onSelectedScriptGroupIdsChange,
|
||||
savedScriptGroups,
|
||||
selectedScriptGroupIds,
|
||||
],
|
||||
);
|
||||
@@ -192,7 +358,8 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleSaveScriptGroup}
|
||||
disabled={currentScriptMessages.length === 0}
|
||||
disabled={currentScriptMessages.length === 0 || savingScriptGroup}
|
||||
loading={savingScriptGroup}
|
||||
>
|
||||
保存为话术组
|
||||
</Button>
|
||||
@@ -279,6 +446,8 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
icon={<DeleteOutlined />}
|
||||
className={styles.actionButton}
|
||||
onClick={() => handleDeleteGroup(group.id)}
|
||||
loading={deletingGroupIds.includes(group.id)}
|
||||
disabled={deletingGroupIds.includes(group.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,23 +476,37 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
checked={aiRewriteEnabled}
|
||||
onChange={onAiRewriteToggle}
|
||||
/>
|
||||
<span className={styles.aiRewriteLabel}>AI智能话术改写</span>
|
||||
<div className={styles.aiRewriteLabel}>AI智能话术改写</div>
|
||||
<div>
|
||||
{aiRewriteEnabled && (
|
||||
<Input
|
||||
placeholder="输入改写提示词"
|
||||
value={aiPrompt}
|
||||
onChange={event => onAiPromptChange(event.target.value)}
|
||||
className={styles.aiRewriteInput}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.aiRewriteActions}>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleAiRewrite}
|
||||
disabled={!aiRewriteEnabled}
|
||||
loading={aiRewriting}
|
||||
className={styles.aiRewriteButton}
|
||||
>
|
||||
AI改写
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleAddMessage(undefined, true)}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
{aiRewriteEnabled && (
|
||||
<Input
|
||||
placeholder="输入改写提示词"
|
||||
value={aiPrompt}
|
||||
onChange={event => onAiPromptChange(event.target.value)}
|
||||
className={styles.aiRewriteInput}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleAddMessage(undefined, true)}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -336,13 +519,18 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
min={10}
|
||||
max={20}
|
||||
range
|
||||
min={1}
|
||||
max={60}
|
||||
value={friendInterval}
|
||||
onChange={value => onFriendIntervalChange(value as number)}
|
||||
onChange={value =>
|
||||
onFriendIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>{friendInterval} - 20</span>
|
||||
<span>
|
||||
{friendInterval[0]} - {friendInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
@@ -350,13 +538,18 @@ const StepSendMessage: React.FC<StepSendMessageProps> = ({
|
||||
<div className={styles.settingControl}>
|
||||
<span>间隔时间(秒)</span>
|
||||
<Slider
|
||||
range
|
||||
min={1}
|
||||
max={12}
|
||||
max={60}
|
||||
value={messageInterval}
|
||||
onChange={value => onMessageIntervalChange(value as number)}
|
||||
onChange={value =>
|
||||
onMessageIntervalChange(value as [number, number])
|
||||
}
|
||||
style={{ flex: 1, margin: "0 16px" }}
|
||||
/>
|
||||
<span>{messageInterval} - 12</span>
|
||||
<span>
|
||||
{messageInterval[0]} - {messageInterval[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,47 @@ import StepSendMessage from "./components/StepSendMessage";
|
||||
import { ContactItem, PushType, ScriptGroup } from "./types";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import type { ContentItem } from "@/components/ContentSelection/data";
|
||||
import type { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
import { queryWorkbenchCreate } from "./api";
|
||||
|
||||
const DEFAULT_FRIEND_INTERVAL: [number, number] = [3, 10];
|
||||
const DEFAULT_MESSAGE_INTERVAL: [number, number] = [1, 3];
|
||||
|
||||
const DEFAULT_TIME_RANGE: Record<
|
||||
PushType,
|
||||
{ startTime: string; endTime: string }
|
||||
> = {
|
||||
"friend-message": { startTime: "10:00", endTime: "22:00" },
|
||||
"group-message": { startTime: "09:00", endTime: "20:00" },
|
||||
"group-announcement": { startTime: "08:30", endTime: "18:30" },
|
||||
};
|
||||
|
||||
const DEFAULT_PUSH_ORDER: Record<PushType, 1 | 2> = {
|
||||
"friend-message": 1,
|
||||
"group-message": 1,
|
||||
"group-announcement": 2,
|
||||
};
|
||||
|
||||
const DEFAULT_MAX_PER_DAY: Record<PushType, number> = {
|
||||
"friend-message": 150,
|
||||
"group-message": 200,
|
||||
"group-announcement": 80,
|
||||
};
|
||||
|
||||
const DEFAULT_AUTO_START: Record<PushType, 0 | 1> = {
|
||||
"friend-message": 1,
|
||||
"group-message": 1,
|
||||
"group-announcement": 0,
|
||||
};
|
||||
|
||||
const DEFAULT_PUSH_TYPE: Record<PushType, 0 | 1> = {
|
||||
"friend-message": 0,
|
||||
"group-message": 0,
|
||||
"group-announcement": 1,
|
||||
};
|
||||
|
||||
const isValidNumber = (value: unknown): value is number =>
|
||||
typeof value === "number" && Number.isFinite(value);
|
||||
|
||||
const CreatePushTask: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -44,11 +85,19 @@ const CreatePushTask: React.FC = () => {
|
||||
const [selectedContentLibraries, setSelectedContentLibraries] = useState<
|
||||
ContentItem[]
|
||||
>([]);
|
||||
const [friendInterval, setFriendInterval] = useState(10);
|
||||
const [messageInterval, setMessageInterval] = useState(1);
|
||||
const [friendInterval, setFriendInterval] = useState<[number, number]>([
|
||||
...DEFAULT_FRIEND_INTERVAL,
|
||||
]);
|
||||
const [messageInterval, setMessageInterval] = useState<[number, number]>([
|
||||
...DEFAULT_MESSAGE_INTERVAL,
|
||||
]);
|
||||
const [selectedTag, setSelectedTag] = useState<string>("");
|
||||
const [aiRewriteEnabled, setAiRewriteEnabled] = useState(false);
|
||||
const [aiPrompt, setAiPrompt] = useState("");
|
||||
const [creatingTask, setCreatingTask] = useState(false);
|
||||
const [selectedTrafficPools, setSelectedTrafficPools] = useState<
|
||||
PoolSelectionItem[]
|
||||
>([]);
|
||||
|
||||
const customerList = useCustomerStore(state => state.customerList);
|
||||
|
||||
@@ -131,33 +180,206 @@ const CreatePushTask: React.FC = () => {
|
||||
setSelectedAccounts([]);
|
||||
};
|
||||
|
||||
const handleSend = () => {
|
||||
const handleSend = async () => {
|
||||
if (creatingTask) {
|
||||
return;
|
||||
}
|
||||
const selectedGroups = savedScriptGroups.filter(group =>
|
||||
selectedScriptGroupIds.includes(group.id),
|
||||
);
|
||||
if (currentScriptMessages.length === 0 && selectedGroups.length === 0) {
|
||||
message.warning("请先添加话术内容或选择话术组");
|
||||
if (
|
||||
currentScriptMessages.length === 0 &&
|
||||
selectedGroups.length === 0 &&
|
||||
selectedContentLibraries.length === 0
|
||||
) {
|
||||
message.warning("请添加话术内容、选择话术组或内容库");
|
||||
return;
|
||||
}
|
||||
// TODO: 实现发送逻辑
|
||||
console.log("发送推送", {
|
||||
pushType: validPushType,
|
||||
accounts: selectedAccounts,
|
||||
contacts: selectedContacts,
|
||||
currentScript: {
|
||||
name: currentScriptName,
|
||||
messages: currentScriptMessages,
|
||||
},
|
||||
selectedScriptGroups: selectedGroups,
|
||||
friendInterval,
|
||||
messageInterval,
|
||||
selectedTag,
|
||||
aiRewriteEnabled,
|
||||
aiPrompt,
|
||||
selectedContentLibraries,
|
||||
});
|
||||
message.success("推送任务已创建");
|
||||
navigate("/pc/powerCenter/message-push-assistant");
|
||||
const manualMessages = currentScriptMessages
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
if (validPushType === "group-announcement" && manualMessages.length === 0) {
|
||||
message.warning("请先填写公告内容");
|
||||
return;
|
||||
}
|
||||
const toNumberId = (value: unknown) => {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) && !Number.isNaN(numeric)
|
||||
? numeric
|
||||
: null;
|
||||
};
|
||||
const contentGroupIds = Array.from(
|
||||
new Set(
|
||||
[
|
||||
...selectedContentLibraries
|
||||
.map(item => toNumberId(item?.id))
|
||||
.filter((id): id is number => id !== null),
|
||||
...selectedScriptGroupIds
|
||||
.map(id => toNumberId(id))
|
||||
.filter((id): id is number => id !== null),
|
||||
].filter((id): id is number => id !== null),
|
||||
),
|
||||
);
|
||||
if (
|
||||
manualMessages.length === 0 &&
|
||||
selectedGroups.length === 0 &&
|
||||
contentGroupIds.length === 0
|
||||
) {
|
||||
message.warning("缺少有效的话术内容,请重新检查");
|
||||
return;
|
||||
}
|
||||
const ownerWechatIds = Array.from(
|
||||
new Set(
|
||||
selectedAccounts
|
||||
.map(account => toNumberId(account?.id))
|
||||
.filter((id): id is number => id !== null),
|
||||
),
|
||||
);
|
||||
if (ownerWechatIds.length === 0) {
|
||||
message.error("缺少有效的推送账号信息");
|
||||
return;
|
||||
}
|
||||
const selectedContactIds = Array.from(
|
||||
new Set(
|
||||
selectedContacts.map(contact => contact?.id).filter(isValidNumber),
|
||||
),
|
||||
);
|
||||
if (selectedContactIds.length === 0) {
|
||||
message.error("缺少有效的推送对象");
|
||||
return;
|
||||
}
|
||||
const friendIntervalMin = friendInterval[0];
|
||||
const friendIntervalMax = friendInterval[1];
|
||||
const messageIntervalMin = messageInterval[0];
|
||||
const messageIntervalMax = messageInterval[1];
|
||||
const trafficPoolIds = selectedTrafficPools
|
||||
.map(pool => pool.id)
|
||||
.filter(
|
||||
id => id !== undefined && id !== null && String(id).trim() !== "",
|
||||
);
|
||||
const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType];
|
||||
const maxPerDay =
|
||||
selectedContacts.length > 0
|
||||
? selectedContacts.length
|
||||
: DEFAULT_MAX_PER_DAY[validPushType];
|
||||
const pushOrder = DEFAULT_PUSH_ORDER[validPushType];
|
||||
const normalizedPostPushTags =
|
||||
selectedTag.trim().length > 0
|
||||
? [
|
||||
toNumberId(selectedTag) !== null
|
||||
? (toNumberId(selectedTag) as number)
|
||||
: selectedTag,
|
||||
]
|
||||
: [];
|
||||
const taskName =
|
||||
currentScriptName.trim() ||
|
||||
selectedGroups[0]?.name ||
|
||||
(manualMessages[0] ? manualMessages[0].slice(0, 20) : "") ||
|
||||
`推送任务-${Date.now()}`;
|
||||
const deviceGroupIds = Array.from(
|
||||
new Set(
|
||||
selectedAccounts
|
||||
.map(account => toNumberId(account?.currentDeviceId))
|
||||
.filter((id): id is number => id !== null),
|
||||
),
|
||||
);
|
||||
if (validPushType === "friend-message" && deviceGroupIds.length === 0) {
|
||||
message.error("缺少有效的推送设备分组");
|
||||
return;
|
||||
}
|
||||
|
||||
const basePayload: Record<string, any> = {
|
||||
name: taskName,
|
||||
type: 3,
|
||||
autoStart: DEFAULT_AUTO_START[validPushType],
|
||||
status: 1,
|
||||
pushType: DEFAULT_PUSH_TYPE[validPushType],
|
||||
targetType: validPushType === "friend-message" ? 2 : 1,
|
||||
groupPushSubType: validPushType === "group-announcement" ? 2 : 1,
|
||||
startTime,
|
||||
endTime,
|
||||
maxPerDay,
|
||||
pushOrder,
|
||||
friendIntervalMin,
|
||||
friendIntervalMax,
|
||||
messageIntervalMin,
|
||||
messageIntervalMax,
|
||||
isRandomTemplate: selectedScriptGroupIds.length > 1 ? 1 : 0,
|
||||
contentGroups: contentGroupIds,
|
||||
postPushTags: normalizedPostPushTags,
|
||||
ownerWechatIds,
|
||||
enableAiRewrite: aiRewriteEnabled ? 1 : 0,
|
||||
};
|
||||
if (trafficPoolIds.length > 0) {
|
||||
basePayload.trafficPools = trafficPoolIds;
|
||||
}
|
||||
if (validPushType === "friend-message") {
|
||||
basePayload.isLoop = 0;
|
||||
basePayload.deviceGroups = deviceGroupIds;
|
||||
}
|
||||
if (manualMessages.length > 0) {
|
||||
basePayload.manualMessages = manualMessages;
|
||||
if (currentScriptName.trim()) {
|
||||
basePayload.manualScriptName = currentScriptName.trim();
|
||||
}
|
||||
}
|
||||
if (selectedScriptGroupIds.length > 0) {
|
||||
basePayload.selectedScriptGroupIds = selectedScriptGroupIds;
|
||||
}
|
||||
if (aiRewriteEnabled && aiPrompt.trim()) {
|
||||
basePayload.aiRewritePrompt = aiPrompt.trim();
|
||||
}
|
||||
if (selectedGroups.length > 0) {
|
||||
basePayload.scriptGroups = selectedGroups.map(group => ({
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
messages: group.messages,
|
||||
}));
|
||||
}
|
||||
if (validPushType === "friend-message") {
|
||||
basePayload.wechatFriends = Array.from(
|
||||
new Set(
|
||||
selectedContacts
|
||||
.map(contact => toNumberId(contact?.id))
|
||||
.filter((id): id is number => id !== null),
|
||||
),
|
||||
);
|
||||
basePayload.targetType = 2;
|
||||
} else {
|
||||
const groupIds = Array.from(
|
||||
new Set(
|
||||
selectedContacts
|
||||
.map(contact => toNumberId(contact.groupId ?? contact.id))
|
||||
.filter((id): id is number => id !== null),
|
||||
),
|
||||
);
|
||||
basePayload.wechatGroups = groupIds;
|
||||
basePayload.groupPushSubType =
|
||||
validPushType === "group-announcement" ? 2 : 1;
|
||||
basePayload.targetType = 1;
|
||||
if (validPushType === "group-announcement") {
|
||||
basePayload.announcementContent = manualMessages.join("\n");
|
||||
}
|
||||
}
|
||||
let hideLoading: ReturnType<typeof message.loading> | undefined;
|
||||
try {
|
||||
setCreatingTask(true);
|
||||
hideLoading = message.loading("正在创建推送任务...", 0);
|
||||
await queryWorkbenchCreate(basePayload);
|
||||
hideLoading?.();
|
||||
message.success("推送任务已创建");
|
||||
navigate("/pc/powerCenter/message-push-assistant");
|
||||
} catch (error) {
|
||||
hideLoading?.();
|
||||
console.error("创建推送任务失败:", error);
|
||||
const errorMessage =
|
||||
(error as any)?.message ||
|
||||
(error as any)?.response?.data?.message ||
|
||||
"创建推送任务失败,请稍后重试";
|
||||
message.error(errorMessage);
|
||||
} finally {
|
||||
setCreatingTask(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -242,6 +464,8 @@ const CreatePushTask: React.FC = () => {
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSend}
|
||||
loading={creatingTask}
|
||||
disabled={creatingTask}
|
||||
>
|
||||
一键发送
|
||||
</Button>
|
||||
@@ -266,6 +490,8 @@ const CreatePushTask: React.FC = () => {
|
||||
selectedAccounts={selectedAccounts}
|
||||
selectedContacts={selectedContacts}
|
||||
onChange={setSelectedContacts}
|
||||
selectedTrafficPools={selectedTrafficPools}
|
||||
onTrafficPoolsChange={setSelectedTrafficPools}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
帮我对接数据,以下是传参实例,三种模式都是同一界面的。
|
||||
|
||||
群发助手传参实例
|
||||
{
|
||||
"name": "群群发-新品宣传", // 任务名称
|
||||
"type": 3, // 工作台类型:3=群消息推送
|
||||
"autoStart": 1, // 保存后自动启动
|
||||
"status": 1, // 是否启用
|
||||
"pushType": 0, // 推送方式:0=定时,1=立即
|
||||
"targetType": 1, // 目标类型:1=群推送
|
||||
"groupPushSubType": 1, // 群推送子类型:1=群群发,2=群公告
|
||||
"startTime": "09:00", // 推送起始时间
|
||||
"endTime": "20:00", // 推送结束时间
|
||||
"maxPerDay": 200, // 每日最大推送群数
|
||||
"pushOrder": 1, // 推送顺序:1=最早优先,2=最新优先
|
||||
"wechatGroups": [102, 205, 318], // 选择的微信群 ID 列表
|
||||
"contentGroups": [11, 12], // 关联内容库 ID 列表
|
||||
"friendIntervalMin": 10, // 群间最小间隔(秒)
|
||||
"friendIntervalMax": 25, // 群间最大间隔(秒)
|
||||
"messageIntervalMin": 2, // 同一群消息间最小间隔(秒)
|
||||
"messageIntervalMax": 6, // 同一群消息间最大间隔(秒)
|
||||
"isRandomTemplate": 1, // 是否随机选择话术模板
|
||||
"postPushTags": [301, 302], // 推送完成后打的标签
|
||||
ownerWechatIds:[123123,1231231] //客服id
|
||||
}
|
||||
|
||||
//群公告传参实例
|
||||
{
|
||||
"name": "群公告-双11活动", // 任务名称
|
||||
"type": 3, // 群消息推送
|
||||
"autoStart": 0, // 不自动启动
|
||||
"status": 1, // 启用
|
||||
"pushType": 1, // 立即推送
|
||||
"targetType": 1, // 群推送
|
||||
"groupPushSubType": 2, // 群公告
|
||||
"startTime": "08:30", // 开始时间
|
||||
"endTime": "18:30", // 结束时间
|
||||
"maxPerDay": 80, // 每日最大公告数
|
||||
"pushOrder": 2, // 最新优先
|
||||
"wechatGroups": [5021, 5026], // 公告目标群
|
||||
"announcementContent": "…", // 公告正文
|
||||
"enableAiRewrite": 1, // 启用 AI 改写
|
||||
"aiRewritePrompt": "保持活泼口吻…", // AI 改写提示词
|
||||
"contentGroups": [21], // 关联内容库
|
||||
"friendIntervalMin": 15, // 群间最小间隔
|
||||
"friendIntervalMax": 30, // 群间最大间隔
|
||||
"messageIntervalMin": 3, // 消息间最小间隔
|
||||
"messageIntervalMax": 9, // 消息间最大间隔
|
||||
"isRandomTemplate": 0, // 不随机模板
|
||||
"postPushTags": [], // 推送后标签
|
||||
ownerWechatIds:[123123,1231231] //客服id
|
||||
}
|
||||
|
||||
//好友传参实例
|
||||
{
|
||||
"name": "好友私聊-新客转化", // 任务名称
|
||||
"type": 3, // 群消息推送
|
||||
"autoStart": 1, // 自动启动
|
||||
"status": 1, // 启用
|
||||
"pushType": 0, // 定时推送
|
||||
"targetType": 2, // 目标类型:2=好友推送
|
||||
"groupPushSubType": 1, // 固定为群群发(好友推送不支持公告)
|
||||
"startTime": "10:00", // 开始时间
|
||||
"endTime": "22:00", // 结束时间
|
||||
"maxPerDay": 150, // 每日最大推送好友数
|
||||
"pushOrder": 1, // 最早优先
|
||||
"wechatFriends": ["12312"], // 指定好友列表(可为空数组)
|
||||
"deviceGroups": [9001, 9002], // 必选:推送设备分组 ID
|
||||
"contentGroups": [41, 42], // 话术内容库
|
||||
"friendIntervalMin": 12, // 好友间最小间隔
|
||||
"friendIntervalMax": 28, // 好友间最大间隔
|
||||
"messageIntervalMin": 4, // 消息间最小间隔
|
||||
"messageIntervalMax": 10, // 消息间最大间隔
|
||||
"isRandomTemplate": 1, // 随机话术
|
||||
"postPushTags": [501], // 推送后标签
|
||||
ownerWechatIds:[123123,1231231] //客服id
|
||||
}
|
||||
|
||||
请求接口是 queryWorkbenchCreate
|
||||
@@ -7,6 +7,7 @@
|
||||
* 4. 提供回调机制通知组件更新
|
||||
*/
|
||||
|
||||
import Dexie from "dexie";
|
||||
import { db, chatSessionService, ChatSession } from "../db";
|
||||
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
|
||||
@@ -243,7 +244,9 @@ export class MessageManager {
|
||||
"userId",
|
||||
userId,
|
||||
)) as ChatSession[];
|
||||
const localSessionMap = new Map(localSessions.map(s => [s.id, s]));
|
||||
const localSessionMap = new Map(
|
||||
localSessions.map(session => [session.serverId, session]),
|
||||
);
|
||||
|
||||
// 2. 转换服务器数据为统一格式
|
||||
const serverSessions: ChatSession[] = [];
|
||||
@@ -264,16 +267,18 @@ export class MessageManager {
|
||||
serverSessions.push(...groups);
|
||||
}
|
||||
|
||||
const serverSessionMap = new Map(serverSessions.map(s => [s.id, s]));
|
||||
const serverSessionMap = new Map(
|
||||
serverSessions.map(session => [session.serverId, session]),
|
||||
);
|
||||
|
||||
// 3. 计算差异
|
||||
const toAdd: ChatSession[] = [];
|
||||
const toUpdate: ChatSession[] = [];
|
||||
const toDelete: number[] = [];
|
||||
const toDelete: string[] = [];
|
||||
|
||||
// 检查新增和更新
|
||||
for (const serverSession of serverSessions) {
|
||||
const localSession = localSessionMap.get(serverSession.id);
|
||||
const localSession = localSessionMap.get(serverSession.serverId);
|
||||
|
||||
if (!localSession) {
|
||||
toAdd.push(serverSession);
|
||||
@@ -286,8 +291,8 @@ export class MessageManager {
|
||||
|
||||
// 检查删除
|
||||
for (const localSession of localSessions) {
|
||||
if (!serverSessionMap.has(localSession.id)) {
|
||||
toDelete.push(localSession.id);
|
||||
if (!serverSessionMap.has(localSession.serverId)) {
|
||||
toDelete.push(localSession.serverId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +339,19 @@ export class MessageManager {
|
||||
serverId: `${session.type}_${session.id}`,
|
||||
}));
|
||||
|
||||
await db.chatSessions.bulkAdd(dataToInsert);
|
||||
try {
|
||||
await db.chatSessions.bulkAdd(dataToInsert);
|
||||
} catch (error) {
|
||||
if (error instanceof Dexie.BulkError) {
|
||||
console.warn(
|
||||
`批量新增会话时检测到重复主键,切换为 bulkPut 以覆盖更新。错误详情:`,
|
||||
error,
|
||||
);
|
||||
await db.chatSessions.bulkPut(dataToInsert);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,14 +374,16 @@ export class MessageManager {
|
||||
*/
|
||||
private static async batchDeleteSessions(
|
||||
userId: number,
|
||||
sessionIds: number[],
|
||||
serverIds: string[],
|
||||
) {
|
||||
if (sessionIds.length === 0) return;
|
||||
if (serverIds.length === 0) return;
|
||||
|
||||
const serverIdSet = new Set(serverIds);
|
||||
|
||||
await db.chatSessions
|
||||
.where("userId")
|
||||
.equals(userId)
|
||||
.and(session => sessionIds.includes(session.id))
|
||||
.and(session => serverIdSet.has(session.serverId))
|
||||
.delete();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user