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 [ccOpen, setCcOpen] = useState(false);
|
||||||
|
const [budgetEditing, setBudgetEditing] = useState(false);
|
||||||
|
const [budgetBusy, setBudgetBusy] = useState(false);
|
||||||
|
const [maxRoundsDraft, setMaxRoundsDraft] = useState('');
|
||||||
const [attachments, setAttachments] = useState<PendingAttachment[]>([]);
|
const [attachments, setAttachments] = useState<PendingAttachment[]>([]);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -684,6 +687,8 @@ function ReplyBar({
|
|||||||
const fetchSessions = useSessionStore(s => s.fetchSessions);
|
const fetchSessions = useSessionStore(s => s.fetchSessions);
|
||||||
const fetchContacts = useContactStore(s => s.fetchContacts);
|
const fetchContacts = useContactStore(s => s.fetchContacts);
|
||||||
const selectSession = useSessionStore(s => s.selectSession);
|
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 || '');
|
const me = useAuthStore(s => s.user?.username || '');
|
||||||
|
|
||||||
if (!replyTo) return null;
|
if (!replyTo) return null;
|
||||||
@ -774,6 +779,58 @@ function ReplyBar({
|
|||||||
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
<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>}
|
{error && <span className="text-xs text-red-600 min-w-0 break-words">{error}</span>}
|
||||||
<div className="flex-1" />
|
<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
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBody('');
|
setBody('');
|
||||||
|
|||||||
Reference in New Issue
Block a user