ai创作未使用接口清除,添加@选取

This commit is contained in:
sjy
2026-07-01 10:41:19 +08:00
parent c6df04a895
commit 291b240805
5 changed files with 200 additions and 53 deletions
+165 -50
View File
@@ -139,13 +139,46 @@ const AIChatPage: React.FC = () => {
setEngineOptions,
setEnginesele,
setInputValue,
currentMedia,
setCurrentMedia,
} = useAppStore();
const [uploading, setUploading] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [currentMedia, setCurrentMedia] = useState<{ name: string; type: 'image' | 'video'; url: string }[]>([]);
// @ 提及相关状态
const [mentionVisible, setMentionVisible] = useState(false);
const mentionInputRef = useRef<any>(null);
const [mentionPosition, setMentionPosition] = useState({ top: 0, left: 0 });
// 数字转中文数字(一、二、三、四)
const numberToChinese = (num: number): string => {
const map = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
if (num <= 10) return map[num];
if (num < 20) return '十' + map[num % 10];
if (num < 100) {
const tens = Math.floor(num / 10);
const ones = num % 10;
return map[tens] + '十' + (ones ? map[ones] : '');
}
return String(num);
};
// 根据媒体列表生成标签(图片一、图片二、视频一、视频二...)
const generateMediaLabels = (media: { type: 'image' | 'video' }[]) => {
let imgCount = 0;
let vidCount = 0;
return media.map((m) => {
if (m.type === 'image') {
imgCount++;
return `图片${numberToChinese(imgCount)}`;
} else {
vidCount++;
return `视频${numberToChinese(vidCount)}`;
}
});
};
const [previewVisible, setPreviewVisible] = useState<boolean>(false);
const [previewUrl, setPreviewUrl] = useState<string>('');
@@ -396,15 +429,7 @@ const AIChatPage: React.FC = () => {
})
.catch(() => {
});
getCreditRatios()
.then((data: any) => {
// console.log('积分', data);
setCreditRatios(data.video || []);
setCimage(data.image || []);
})
.catch((error) => {
});
calculateCredits().then((data: any) => {
// console.log('积分计算', data);
// 保存积分计算数据
@@ -416,36 +441,7 @@ const AIChatPage: React.FC = () => {
setGen_list(mess_list)
setTotalnumber(total)
})
getParameters()
.then((data) => {
let supportedSizes = (data as any).items?.[0]?.supportedSizes || {};
let supportedResolutions = [];
let twokwidth = [];
let fourkwidth = [];
for (let key in supportedSizes["2K"]) {
supportedResolutions.push(key);
twokwidth.push(supportedSizes["2K"][key]);
}
const newRatioOptions = supportedResolutions.map((res: any, index: number) => ({
value: res,
label: String(index),
}));
setRatioOptions(newRatioOptions);
for (let key in supportedSizes["4K"]) {
fourkwidth.push(supportedSizes["4K"][key]);
}
const newWidthandHeight = [twokwidth, fourkwidth];
setWidthandHeight(newWidthandHeight);
setResolutionOptions([
{ value: '2K', label: '高清 2K' },
{ value: '4K', label: '超清 4K' },
]);
})
.catch(() => { });
}, []);
@@ -799,11 +795,15 @@ const AIChatPage: React.FC = () => {
const uploadFn = isImage ? uploadImage : uploadVideo;
const res = await uploadFn(file);
// 添加到统一的媒体列表
setCurrentMedia((prev) => [...prev, {
const mediaType: 'image' | 'video' = isImage ? 'image' : 'video';
const newList = [...currentMedia, {
name: file.name,
type: isImage ? 'image' : 'video',
type: mediaType,
url: res.url,
}]);
label: '',
}];
const labels = generateMediaLabels(newList);
setCurrentMedia(newList.map((m, i) => ({ ...m, label: labels[i] })));
message.success(`${isImage ? '图片' : '视频'}上传成功`);
} catch (error) {
message.error('上传失败');
@@ -817,7 +817,9 @@ const AIChatPage: React.FC = () => {
const handleRemoveMedia = (index: number) => {
setCurrentMedia((prev) => prev.filter((_, i) => i !== index));
const newList = currentMedia.filter((_, i) => i !== index);
const labels = generateMediaLabels(newList);
setCurrentMedia(newList.map((m, i) => ({ ...m, label: labels[i] })));
};
@@ -828,6 +830,63 @@ const AIChatPage: React.FC = () => {
}
};
// 检测光标前的 @ 符号
const checkMention = (textarea: HTMLTextAreaElement, value: string) => {
const cursorPos = textarea.selectionStart;
const textBeforeCursor = value.slice(0, cursorPos);
const atMatch = textBeforeCursor.match(/@([^@\s]*)$/);
if (atMatch && currentMedia.length > 0) {
setMentionVisible(true);
return true;
}
setMentionVisible(false);
return false;
};
// 输入框变化处理
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setInputValue(value);
const textarea = e.target;
checkMention(textarea, value);
};
// 键盘事件处理(ESC 关闭提及、Tab/Enter 选择)
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (mentionVisible) {
if (e.key === 'Escape') {
setMentionVisible(false);
}
}
};
// 插入 @ 提及
const insertMention = (label: string) => {
const textarea = mentionInputRef.current?.resizableTextArea?.textArea;
if (!textarea) return;
const cursorPos = textarea.selectionStart;
const textBefore = inputValue.slice(0, cursorPos);
const textAfter = inputValue.slice(cursorPos);
const atIndex = textBefore.lastIndexOf('@');
if (atIndex === -1) {
setMentionVisible(false);
return;
}
const newValue = textBefore.slice(0, atIndex) + `@${label} ` + textAfter;
setInputValue(newValue);
setMentionVisible(false);
setTimeout(() => {
const pos = atIndex + label.length + 2;
textarea.focus();
textarea.setSelectionRange(pos, pos);
}, 0);
};
const handleClosePreview = () => {
@@ -1444,7 +1503,7 @@ const AIChatPage: React.FC = () => {
{currentMedia.length > 0 && (
<div style={{ display: 'flex', gap: 8, marginBottom: 12, overflowX: 'auto' }}>
{currentMedia.map((media, idx) => (
<div key={`media-${idx}`} style={{ position: 'relative', width: media.type === 'video' ? 120 : 80, flexShrink: 0 }}>
<div key={`media-${idx}`} style={{ position: 'relative', width: media.type === 'video' ? 120 : 80, flexShrink: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
{media.type === 'image' ? (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
@@ -1457,6 +1516,9 @@ const AIChatPage: React.FC = () => {
style={{ width: '100%', height: 80, objectFit: 'cover', borderRadius: 8 }}
/>
)}
<span style={{ fontSize: 11, color: '#64748b', fontWeight: 500 }}>
{media.label}
</span>
{/* 删除已上传媒体按钮 */}
<button
onClick={() => handleRemoveMedia(idx)}
@@ -1495,6 +1557,7 @@ const AIChatPage: React.FC = () => {
border: '1px solid rgba(99, 102, 241, 0.08)',
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)',
transition: 'all 0.2s ease',
position: 'relative',
}}>
{/* 上传按钮 */}
<Upload
@@ -1539,8 +1602,10 @@ const AIChatPage: React.FC = () => {
{/* 文本输入框 */}
<TextArea
ref={mentionInputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onChange={handleInputChange}
onKeyDown={handleInputKeyDown}
onKeyPress={handleKeyPress}
placeholder="上传最多4张参考图,输入提示词描述您想生成的画面..."
autoSize={{ minRows: 1, maxRows: 4 }}
@@ -1554,14 +1619,64 @@ const AIChatPage: React.FC = () => {
fontSize: 14,
lineHeight: 1.5,
color: '#1e293b',
// resize: 'none',
// boxShadow: 'none',
// padding: '8px 12px',
}}
disabled={loading}
/>
{/* @ 提及下拉列表 */}
{mentionVisible && currentMedia.length > 0 && (
<div
style={{
position: 'absolute',
bottom: '100%',
left: 70,
marginBottom: 8,
zIndex: 1000,
background: '#fff',
borderRadius: 12,
boxShadow: '0 8px 28px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06)',
border: '1px solid #e2e8f0',
padding: 6,
minWidth: 160,
maxHeight: 260,
overflowY: 'auto',
}}
>
{currentMedia.map((media, idx) => (
<div
key={idx}
onClick={() => insertMention(media.label)}
style={{
padding: '8px 12px',
borderRadius: 6,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 8,
fontSize: 13,
color: '#334155',
transition: 'all 0.15s',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.08)';
e.currentTarget.style.color = '#6366f1';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = '#334155';
}}
>
{media.type === 'image' ? (
<PictureOutlined style={{ fontSize: 14, color: '#6366f1' }} />
) : (
<VideoCameraOutlined style={{ fontSize: 14, color: '#8b5cf6' }} />
)}
<span>{media.label}</span>
</div>
))}
</div>
)}
{/* 发送按钮 */}
<Button
type="primary"