增加后台管理项目;
This commit is contained in:
@@ -10,14 +10,13 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@wecom/jssdk": "^2.3.3",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"axios": "^1.13.2",
|
||||
"daisyui": "^5.5.5",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwindcss": "^3.4.0"
|
||||
"tailwindcss": "^4.1.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
||||
719
frontend/pnpm-lock.yaml
generated
719
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useWeCom } from './hooks/useWeCom';
|
||||
import { workflowService, customerService } from './services/wecomService';
|
||||
import WorkflowProgress from './components/WorkflowProgress';
|
||||
import RolePromptDrawer from './components/RolePromptDrawer';
|
||||
import { getStepNames } from './constants/workflowSteps';
|
||||
import { getSceneDescription } from './constants/wecomScenes';
|
||||
|
||||
@@ -24,6 +25,9 @@ function App() {
|
||||
// 执行计数
|
||||
const [executionCount, setExecutionCount] = useState(0);
|
||||
|
||||
// 角色提示词抽屉状态
|
||||
const [rolePromptDrawerOpen, setRolePromptDrawerOpen] = useState(false);
|
||||
|
||||
const messagesEndRef = useRef(null);
|
||||
const hasAutoExecutedRef = useRef(false);
|
||||
|
||||
@@ -346,11 +350,21 @@ function App() {
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
className="hidden sm:inline px-3 py-1 bg-white/20 rounded text-sm hover:bg-white/30 transition-colors"
|
||||
className="hidden sm:inline px-3 py-1 bg-white/20 rounded text-sm hover:bg-white/30 transition-colors flex items-center space-x-1"
|
||||
onClick={executeSmartReplyWorkflow}
|
||||
disabled={workflowStatus.isRunning || !weCom.sdkReady}
|
||||
>
|
||||
{workflowStatus.isRunning ? '执行中...' : '执行工作流'}
|
||||
<span>{workflowStatus.isRunning ? '⏳' : '🔄️'}</span>
|
||||
<span>{workflowStatus.isRunning ? '执行中...' : '执行工作流'}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="hidden sm:inline px-3 py-1 bg-white/20 rounded text-sm hover:bg-white/30 transition-colors flex items-center space-x-1"
|
||||
onClick={() => setRolePromptDrawerOpen(true)}
|
||||
title="配置AI角色提示词"
|
||||
>
|
||||
<span>🤖</span>
|
||||
<span>角色配置</span>
|
||||
</button>
|
||||
|
||||
{/* 移动端操作按钮 */}
|
||||
@@ -363,6 +377,13 @@ function App() {
|
||||
>
|
||||
{workflowStatus.isRunning ? '⏳' : '🔄️'}
|
||||
</button>
|
||||
<button
|
||||
className="w-8 h-8 flex items-center justify-center bg-white/20 rounded hover:bg-white/30 transition-colors"
|
||||
onClick={() => setRolePromptDrawerOpen(true)}
|
||||
title="角色配置"
|
||||
>
|
||||
🤖
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -439,33 +460,41 @@ function App() {
|
||||
) : (
|
||||
// 聊天消息列表
|
||||
<div className="max-w-4xl mx-auto space-y-3 sm:space-y-4">
|
||||
{messages.map((message, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} ${
|
||||
message.role === 'system' ? 'justify-center' : ''
|
||||
}`}
|
||||
>
|
||||
<div className={`max-w-[85%] sm:max-w-[70%] rounded-2xl px-3 py-2 sm:px-4 sm:py-3 ${
|
||||
message.role === 'user'
|
||||
? 'bg-blue-600 text-white rounded-br-none'
|
||||
: message.role === 'system'
|
||||
? 'bg-gray-100 text-gray-700 border border-gray-200'
|
||||
: 'bg-white border border-gray-200 rounded-bl-none shadow-sm'
|
||||
}`}>
|
||||
<div className="text-sm leading-relaxed break-words">
|
||||
{message.content}
|
||||
</div>
|
||||
{/* <div className={`text-xs mt-1 ${
|
||||
message.role === 'user' ? 'text-blue-100' :
|
||||
message.role === 'system' ? 'text-gray-500' :
|
||||
'text-gray-500'
|
||||
{messages.map((message, index) => {
|
||||
// 确定justify类名
|
||||
let justifyClass = 'justify-start'; // 默认左对齐
|
||||
if (message.role === 'user') {
|
||||
justifyClass = 'justify-end'; // 用户消息右对齐
|
||||
} else if (message.role === 'system') {
|
||||
justifyClass = 'justify-center'; // 系统消息居中
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex ${justifyClass}`}
|
||||
>
|
||||
<div className={`max-w-[85%] sm:max-w-[70%] rounded-2xl px-3 py-2 sm:px-4 sm:py-3 ${
|
||||
message.role === 'user'
|
||||
? 'bg-blue-600 text-white rounded-br-none'
|
||||
: message.role === 'system'
|
||||
? 'bg-gray-100 text-gray-700 border border-gray-200'
|
||||
: 'bg-white border border-gray-200 rounded-bl-none shadow-sm'
|
||||
}`}>
|
||||
{message.timestamp}
|
||||
</div> */}
|
||||
<div className="text-sm leading-relaxed break-words">
|
||||
{message.content}
|
||||
</div>
|
||||
{/* <div className={`text-xs mt-1 ${
|
||||
message.role === 'user' ? 'text-blue-100' :
|
||||
message.role === 'system' ? 'text-gray-500' :
|
||||
'text-gray-500'
|
||||
}`}>
|
||||
{message.timestamp}
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 加载状态 */}
|
||||
{workflowStatus.isRunning && (
|
||||
@@ -591,6 +620,12 @@ function App() {
|
||||
</div>
|
||||
</footer>
|
||||
)}
|
||||
|
||||
{/* 角色提示词配置抽屉 */}
|
||||
<RolePromptDrawer
|
||||
isOpen={rolePromptDrawerOpen}
|
||||
onClose={() => setRolePromptDrawerOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
448
frontend/src/components/RolePromptDrawer.jsx
Normal file
448
frontend/src/components/RolePromptDrawer.jsx
Normal file
@@ -0,0 +1,448 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://mp.yizhezhe.com/api';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const RolePromptDrawer = ({ isOpen, onClose }) => {
|
||||
// 引用滚动容器
|
||||
const drawerContentRef = useRef(null);
|
||||
|
||||
// 表单数据状态
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
skills: '',
|
||||
restrictions: ''
|
||||
});
|
||||
|
||||
// 加载和保存状态
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
const [saveStatus, setSaveStatus] = useState({ type: null, message: '' });
|
||||
// Popover确认状态
|
||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||
|
||||
// 加载角色配置
|
||||
const loadRoleConfig = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await apiClient.get('/glm/role');
|
||||
const result = response.data;
|
||||
|
||||
if (result.code === 200 && result.result) {
|
||||
const roleConfig = result.result;
|
||||
// 直接使用字符串(后端已返回字符串格式)
|
||||
setFormData({
|
||||
name: roleConfig.name || '',
|
||||
description: roleConfig.description || '',
|
||||
skills: roleConfig.skills || '',
|
||||
restrictions: roleConfig.restrictions || ''
|
||||
});
|
||||
} else {
|
||||
throw new Error(result.message || '加载角色配置失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载角色配置失败:', error);
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: `加载失败: ${error.response?.data?.message || error.message}`
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存角色配置
|
||||
const handleSave = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 验证数据
|
||||
if (!formData.name.trim() || !formData.description.trim()) {
|
||||
setSaveStatus({ type: 'error', message: '角色名称和描述不能为空' });
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
setSaveStatus({ type: 'loading', message: '保存中...' });
|
||||
|
||||
try {
|
||||
// 直接发送字符串(后端期望字符串格式)
|
||||
const roleConfigToSave = {
|
||||
name: formData.name.trim(),
|
||||
description: formData.description.trim(),
|
||||
skills: formData.skills.trim(),
|
||||
restrictions: formData.restrictions.trim()
|
||||
};
|
||||
|
||||
const response = await apiClient.put('/glm/role', roleConfigToSave);
|
||||
const result = response.data;
|
||||
|
||||
if (result.code === 200) {
|
||||
setSaveStatus({ type: 'success', message: '保存成功!' });
|
||||
|
||||
// 滚动到顶部,让用户看到提示
|
||||
if (drawerContentRef.current) {
|
||||
drawerContentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 3秒后清除成功消息
|
||||
setTimeout(() => {
|
||||
setSaveStatus({ type: null, message: '' });
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(result.message || '保存角色配置失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存角色配置失败:', error);
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: `保存失败: ${error.response?.data?.message || error.message}`
|
||||
});
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 重置按钮点击 - 显示确认Popover
|
||||
const handleResetButtonClick = () => {
|
||||
setShowResetConfirm(true);
|
||||
};
|
||||
|
||||
// 取消重置
|
||||
const handleCancelReset = () => {
|
||||
setShowResetConfirm(false);
|
||||
};
|
||||
|
||||
// 确认重置到默认角色配置
|
||||
const handleConfirmReset = async () => {
|
||||
setShowResetConfirm(false);
|
||||
|
||||
setIsResetting(true);
|
||||
setSaveStatus({ type: 'loading', message: '重置到默认配置中...' });
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/glm/role/reset');
|
||||
const result = response.data;
|
||||
|
||||
if (result.code === 200) {
|
||||
setSaveStatus({ type: 'success', message: '已重置为默认角色配置!' });
|
||||
|
||||
// 重新加载角色配置
|
||||
await loadRoleConfig();
|
||||
|
||||
// 滚动到顶部,让用户看到提示
|
||||
if (drawerContentRef.current) {
|
||||
drawerContentRef.current.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// 3秒后清除成功消息
|
||||
setTimeout(() => {
|
||||
setSaveStatus({ type: null, message: '' });
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(result.message || '重置角色配置失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重置角色配置失败:', error);
|
||||
setSaveStatus({
|
||||
type: 'error',
|
||||
message: `重置失败: ${error.response?.data?.message || error.message}`
|
||||
});
|
||||
} finally {
|
||||
setIsResetting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
loadRoleConfig();
|
||||
setSaveStatus({ type: null, message: '' });
|
||||
};
|
||||
|
||||
// 当抽屉打开时加载数据
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadRoleConfig();
|
||||
setSaveStatus({ type: null, message: '' });
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// 点击外部关闭Popover
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (showResetConfirm && !event.target.closest('.reset-confirm-container')) {
|
||||
setShowResetConfirm(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [showResetConfirm]);
|
||||
|
||||
// ESC键关闭Popover
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event) => {
|
||||
if (event.key === 'Escape' && showResetConfirm) {
|
||||
setShowResetConfirm(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
return () => document.removeEventListener('keydown', handleEscKey);
|
||||
}, [showResetConfirm]);
|
||||
|
||||
// 处理输入变化
|
||||
const handleInputChange = (field, value) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed right-0 top-0 h-full w-full lg:w-3/5 z-50 bg-white border-l border-gray-200 shadow-2xl overflow-y-auto">
|
||||
<div ref={drawerContentRef} className="p-4 lg:p-6 h-full overflow-y-auto">
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between mb-4 lg:mb-6 pb-3 lg:pb-4 border-b border-gray-200">
|
||||
<div>
|
||||
<h3 className="text-lg lg:text-xl font-bold text-gray-800">角色提示词配置</h3>
|
||||
<p className="text-xs lg:text-sm text-gray-500 mt-1">配置AI角色的名称、描述、技能和限制</p>
|
||||
</div>
|
||||
<button
|
||||
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
||||
onClick={onClose}
|
||||
disabled={isSaving}
|
||||
title="关闭"
|
||||
>
|
||||
<svg className="w-4 h-4 lg:w-5 lg:h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 状态提示 */}
|
||||
{saveStatus.type && (
|
||||
<div className={`mb-3 lg:mb-4 p-2 lg:p-3 rounded-lg ${
|
||||
saveStatus.type === 'success' ? 'bg-green-50 border border-green-200 text-green-800' :
|
||||
saveStatus.type === 'error' ? 'bg-red-50 border border-red-200 text-red-800' :
|
||||
'bg-blue-50 border border-blue-200 text-blue-800'
|
||||
}`}>
|
||||
<div className="flex items-center">
|
||||
{saveStatus.type === 'loading' && (
|
||||
<span className="loading loading-spinner loading-xs lg:loading-sm mr-2"></span>
|
||||
)}
|
||||
<span className="text-xs lg:text-sm">{saveStatus.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 加载状态 */}
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-48 lg:h-64">
|
||||
<div className="text-center">
|
||||
<span className="loading loading-spinner loading-md lg:loading-lg text-gray-400"></span>
|
||||
<p className="mt-3 lg:mt-4 text-sm lg:text-base text-gray-500">加载角色配置中...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* 表单内容 */
|
||||
<form onSubmit={handleSave} className="space-y-4 lg:space-y-6">
|
||||
{/* 角色名称 */}
|
||||
<div className="space-y-1 lg:space-y-2">
|
||||
<label className="block text-xs lg:text-sm font-medium text-gray-700">
|
||||
角色名称
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 text-xs lg:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors focus:outline-none"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||
placeholder="例如:兜兜"
|
||||
required
|
||||
disabled={isSaving}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 角色描述 */}
|
||||
<div className="space-y-1 lg:space-y-2">
|
||||
<label className="block text-xs lg:text-sm font-medium text-gray-700">
|
||||
角色描述
|
||||
<span className="text-gray-500 font-normal ml-1">(AI角色的背景、性格特点等)</span>
|
||||
</label>
|
||||
<textarea
|
||||
className="w-full px-3 py-2 text-xs lg:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors h-32 lg:h-40 focus:outline-none"
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
placeholder="描述AI角色的背景、性格特点等..."
|
||||
required
|
||||
disabled={isSaving}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 技能列表 */}
|
||||
<div className="space-y-1 lg:space-y-2">
|
||||
<label className="block text-xs lg:text-sm font-medium text-gray-700">
|
||||
技能列表
|
||||
<span className="text-gray-500 font-normal ml-1">(每行输入一个技能)</span>
|
||||
</label>
|
||||
<textarea
|
||||
className="w-full px-3 py-2 text-xs lg:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors font-mono h-36 lg:h-48 focus:outline-none"
|
||||
value={formData.skills}
|
||||
onChange={(e) => handleInputChange('skills', e.target.value)}
|
||||
placeholder="亲切温暖的沟通 轻松友好的语气 简洁明了的描述"
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
每行输入一个技能,空行会被自动过滤
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 限制列表 */}
|
||||
<div className="space-y-1 lg:space-y-2">
|
||||
<label className="block text-xs lg:text-sm font-medium text-gray-700">
|
||||
限制列表
|
||||
<span className="text-gray-500 font-normal ml-1">(每行输入一个限制)</span>
|
||||
</label>
|
||||
<textarea
|
||||
className="w-full px-3 py-2 text-xs lg:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-200 focus:border-blue-200 transition-colors font-mono h-36 lg:h-48 focus:outline-none"
|
||||
value={formData.restrictions}
|
||||
onChange={(e) => handleInputChange('restrictions', e.target.value)}
|
||||
placeholder="回复必须基于提供的知识库中的内容 每条回复应保持在80字以内 只讨论与宝妈相关的主题"
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
每行输入一个限制,空行会被自动过滤
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex justify-between items-center pt-4 lg:pt-6 border-t border-gray-200">
|
||||
{/* 左边:重置到默认按钮 */}
|
||||
<div className="relative reset-confirm-container">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 lg:px-4 lg:py-2 text-xs lg:text-sm font-medium text-white bg-orange-500 hover:bg-orange-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={handleResetButtonClick}
|
||||
disabled={isSaving || isResetting}
|
||||
>
|
||||
{isResetting ? (
|
||||
<span className="flex items-center">
|
||||
<span className="loading loading-spinner loading-xs mr-1 lg:mr-2"></span>
|
||||
重置中...
|
||||
</span>
|
||||
) : (
|
||||
'重置到默认'
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Popover确认气泡 */}
|
||||
{showResetConfirm && (
|
||||
<div className="absolute bottom-full mb-2 left-0 bg-white border border-gray-300 rounded-lg shadow-xl p-4 w-64 z-50">
|
||||
{/* 三角形箭头 - 调整到左边 */}
|
||||
<div className="absolute -bottom-2 left-4 w-4 h-4 bg-white border-r border-b border-gray-300 transform rotate-45"></div>
|
||||
|
||||
<p className="text-sm text-gray-700 mb-3">
|
||||
确定要重置为默认角色配置吗?当前配置将会被覆盖。
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
onClick={handleCancelReset}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 text-xs font-medium text-white bg-orange-500 hover:bg-orange-600 rounded-lg transition-colors"
|
||||
onClick={handleConfirmReset}
|
||||
>
|
||||
确定重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右边:其他操作按钮 */}
|
||||
<div className="flex space-x-2 lg:space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 lg:px-4 lg:py-2 text-xs lg:text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
onClick={handleReset}
|
||||
disabled={isSaving || isResetting}
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1.5 lg:px-4 lg:py-2 text-xs lg:text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
onClick={onClose}
|
||||
disabled={isSaving || isResetting}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-3 py-1.5 lg:px-4 lg:py-2 text-xs lg:text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={isSaving || isResetting}
|
||||
>
|
||||
{isSaving ? (
|
||||
<span className="flex items-center">
|
||||
<span className="loading loading-spinner loading-xs mr-1 lg:mr-2"></span>
|
||||
保存中...
|
||||
</span>
|
||||
) : (
|
||||
'保存配置'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* 使用说明 */}
|
||||
{!isLoading && (
|
||||
<div className="mt-6 lg:mt-8 pt-4 lg:pt-6 border-t border-gray-200">
|
||||
<div className="text-xs lg:text-sm text-gray-600">
|
||||
<h4 className="font-medium mb-2 lg:mb-3 text-gray-700">使用说明</h4>
|
||||
<ul className="space-y-1 lg:space-y-2">
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-1 lg:mr-2 text-xs">•</span>
|
||||
<span className="text-xs lg:text-sm">角色配置将影响AI的回复风格和内容限制</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-1 lg:mr-2 text-xs">•</span>
|
||||
<span className="text-xs lg:text-sm">技能列表定义AI的能力特点,每行输入一个技能</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-1 lg:mr-2 text-xs">•</span>
|
||||
<span className="text-xs lg:text-sm">限制列表确保AI回复符合业务要求,每行输入一个限制</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-1 lg:mr-2 text-xs">•</span>
|
||||
<span className="text-xs lg:text-sm">保存后配置将立即生效,影响后续AI回复</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-orange-500 mr-1 lg:mr-2 text-xs">•</span>
|
||||
<span className="text-xs lg:text-sm">点击"重置到默认"可将角色配置恢复为系统默认值</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RolePromptDrawer;
|
||||
@@ -1,6 +1,5 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
@@ -41,7 +40,6 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply antialiased;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -50,12 +48,12 @@
|
||||
/* 触摸友好的按钮 */
|
||||
@layer components {
|
||||
.touch-button {
|
||||
@apply active:scale-95 transition-transform duration-150;
|
||||
transform: scale(0.95);
|
||||
transition: transform 150ms;
|
||||
}
|
||||
|
||||
/* 移动端优化输入框 */
|
||||
.mobile-input {
|
||||
@apply text-base;
|
||||
font-size: 16px !important; /* 防止iOS缩放 */
|
||||
}
|
||||
}
|
||||
@@ -85,7 +83,13 @@
|
||||
|
||||
/* 移动端优化模态框 */
|
||||
.mobile-modal {
|
||||
@apply fixed inset-0 z-50 flex items-center justify-center p-4;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
daisyui: {
|
||||
themes: ["light", "dark", "cupcake"],
|
||||
},
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
react(),
|
||||
],
|
||||
base: '/chat_fe',
|
||||
server: {
|
||||
port: 3010,
|
||||
|
||||
Reference in New Issue
Block a user