UI 修复: 回复页(ReplyBar)加往返预算配置入口
jianf 反馈:回复页面/对话页面/发件页面无法配置对话预算。 - 发件页(ComposePage):已有 maxRounds 输入框 ✅ - 对话页(MailView 会话头部):已有 BudgetEditor ✅ - 回复页(ReplyBar):缺失 ← 本轮补上 ReplyBar 底部按钮区加预算徽标(点击展开编辑): - 显示 已用/上限 来回 或「预算不限」,用尽时红色 - 点击 → 数字输入 + 保存/取消 - 复用 useSessionStore.setBudget(同一会话) - 177 测试全过,已部署
This commit is contained in:
@ -676,6 +676,9 @@ function ReplyBar({
|
||||
// 抄送默认收起:多数回复不需要它,常驻一行输入框只会挤掉正文空间。
|
||||
// 原邮件带抄送时自动展开并预填 —— 「回复全部」是人在这种场景下的默认预期
|
||||
const [ccOpen, setCcOpen] = useState(false);
|
||||
const [budgetEditing, setBudgetEditing] = useState(false);
|
||||
const [budgetBusy, setBudgetBusy] = useState(false);
|
||||
const [maxRoundsDraft, setMaxRoundsDraft] = useState('');
|
||||
const [attachments, setAttachments] = useState<PendingAttachment[]>([]);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -684,6 +687,8 @@ function ReplyBar({
|
||||
const fetchSessions = useSessionStore(s => s.fetchSessions);
|
||||
const fetchContacts = useContactStore(s => s.fetchContacts);
|
||||
const selectSession = useSessionStore(s => s.selectSession);
|
||||
const budget = useSessionStore(s => s.budget);
|
||||
const setBudget = useSessionStore(s => s.setBudget);
|
||||
const me = useAuthStore(s => s.user?.username || '');
|
||||
|
||||
if (!replyTo) return null;
|
||||
@ -774,6 +779,58 @@ function ReplyBar({
|
||||
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
||||
{error && <span className="text-xs text-red-600 min-w-0 break-words">{error}</span>}
|
||||
<div className="flex-1" />
|
||||
{budget && !budgetEditing && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setMaxRoundsDraft(budget.unlimited ? '' : String(budget.max_rounds));
|
||||
setBudgetEditing(true);
|
||||
}}
|
||||
title="本任务往返预算:Agent 主动发信上限(自动转发与权限询问不占)"
|
||||
className={`tap inline-flex items-center gap-1 text-[10px] px-2 py-1 rounded border ${
|
||||
budget.unlimited
|
||||
? 'border-gray-200 text-gray-500 hover:border-blue-300'
|
||||
: budget.remaining === 0
|
||||
? 'border-red-200 text-red-600'
|
||||
: 'border-gray-200 text-gray-500 hover:border-blue-300'
|
||||
}`}
|
||||
>
|
||||
<GaugeIcon className="w-3 h-3" />
|
||||
{budget.unlimited
|
||||
? '预算不限'
|
||||
: `${budget.used_rounds}/${budget.max_rounds} 来回${budget.remaining === 0 ? ' · 已用尽' : ''}`}
|
||||
</button>
|
||||
)}
|
||||
{budget && budgetEditing && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="text-[10px] text-gray-500">预算</span>
|
||||
<input
|
||||
value={maxRoundsDraft}
|
||||
onChange={e => setMaxRoundsDraft(e.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder="不限"
|
||||
autoFocus
|
||||
className="w-14 text-xs border border-gray-300 rounded px-1.5 py-0.5 focus:outline-none focus:ring-2 focus:ring-blue-100"
|
||||
/>
|
||||
<button
|
||||
disabled={budgetBusy || (maxRoundsDraft.trim() !== '' && !/^\d+$/.test(maxRoundsDraft.trim()))}
|
||||
onClick={async () => {
|
||||
setBudgetBusy(true);
|
||||
await setBudget({ max_rounds: maxRoundsDraft.trim() === '' ? 0 : Number(maxRoundsDraft.trim()) });
|
||||
setBudgetBusy(false);
|
||||
setBudgetEditing(false);
|
||||
}}
|
||||
className="tap px-2 py-0.5 text-[10px] rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBudgetEditing(false)}
|
||||
className="tap text-[10px] text-gray-400 hover:text-gray-700"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setBody('');
|
||||
|
||||
Reference in New Issue
Block a user