refactor(api): 删除运营链路与 runtime_use_lease

删除任务链(drafts/confirmations/tasks/attempts)、creator 运营链(策略/事件/操作/会话/消息/监听器/素材/关系)、runtime_use_lease 机制及其 API/UI;taskstate 包与对应测试一并移除。
This commit is contained in:
2026-09-28 17:14:00 +08:00
parent c2bccba352
commit c567080248
44 changed files with 135 additions and 8367 deletions
+19 -278
View File
@@ -1,18 +1,11 @@
// 编辑社媒账号:语义对齐 web.archived AccountEditPage。
// 布局改用 antd Tabs(账号资料 / 自动响应策略)——归档版三区块信息量超过单页栅格,Tabs 是 antd 标准组织方式。
// 编辑社媒账号:账号资料维护 + 登录二维码/身份核验。
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { Alert, App, Button, Card, Checkbox, Descriptions, Flex, Form, Input, InputNumber, Popconfirm, Select, Space, Tabs, Tag, Typography } from 'antd';
import { creatorAction, creatorCreate, creatorGet, creatorRequest, creatorUpdate, getList } from '@/services/api';
import { Alert, App, Button, Card, Descriptions, Flex, Form, Input, InputNumber, Select, Space, Typography } from 'antd';
import { creatorAction, creatorGet, creatorUpdate, getList } from '@/services/api';
import { conflictMessage, dateTime } from '@/utils/helpers';
const platformLabelMap: Record<string, string> = { douyin: '抖音' };
const eventTypeOptions = [
{ value: 'comment', label: '评论' },
{ value: 'like', label: '点赞' },
{ value: 'repost', label: '转发' },
{ value: 'follow', label: '关注' },
];
function profileForm(profile: any) {
return {
@@ -22,9 +15,7 @@ function profileForm(profile: any) {
real_name: profile.real_name || '',
identity_number: profile.identity_number || '',
note: profile.note || '',
business_status: profile.business_status || 'normal',
reply_requirements: profile.reply_requirements || '',
cooldown_seconds: profile.cooldown_seconds || 86400 };
business_status: profile.business_status || 'normal' };
}
export default function Page() {
@@ -35,14 +26,6 @@ export default function Page() {
const [busy, setBusy] = useState(false);
const [loginQR, setLoginQR] = useState<any>(null);
const [form] = Form.useForm<any>();
const [strategyForm] = Form.useForm<any>();
const [strategies, setStrategies] = useState<any[]>([]);
const [strategyError, setStrategyError] = useState<any>(null);
const [editingStrategyID, setEditingStrategyID] = useState('');
const [relations, setRelations] = useState<any[]>([]);
const [relationError, setRelationError] = useState<any>(null);
const [listener, setListener] = useState<any>(null);
const [listenerError, setListenerError] = useState<any>(null);
const { message: messageApi } = App.useApp();
const selected = profiles.find((item) => item.id === selectedID);
@@ -70,27 +53,6 @@ export default function Page() {
load();
}, [load]);
useEffect(() => {
if (!selectedID) return;
creatorGet(`/creator/accounts/${encodeURIComponent(selectedID)}/strategies`)
.then((result) => setStrategies(result))
.catch((loadError) => setStrategyError(loadError));
}, [selectedID]);
useEffect(() => {
if (!selectedID) return;
creatorGet(`/creator/listeners?account_id=${encodeURIComponent(selectedID)}`)
.then((result) => setListener(result[0] || null))
.catch((loadError) => setListenerError(loadError));
}, [selectedID]);
useEffect(() => {
if (!selectedID) return;
creatorGet(`/creator/relations?big_account_id=${encodeURIComponent(selectedID)}`)
.then((result) => setRelations(result))
.catch((loadError) => setRelationError(loadError));
}, [selectedID]);
async function save(values: any) {
if (!selected) return;
setBusy(true);
@@ -146,96 +108,6 @@ export default function Page() {
}
}
async function toggleBig() {
if (!selected) return;
setBusy(true);
try {
const result = await creatorAction(`/creator/accounts/${encodeURIComponent(selected.id)}/big-account`, {
enabled: !selected.big_account });
setProfiles((items) => items.map((item) => (item.id === selected.id ? result : item)));
form.setFieldsValue(profileForm(result));
messageApi.success(result.big_account ? '已开启大号模式。' : '已关闭大号模式。');
} catch (actionError) {
messageApi.error(conflictMessage(actionError, '大号模式更新失败'));
} finally {
setBusy(false);
}
}
async function createStrategy(values: any) {
if (!selected) return;
setBusy(true);
try {
const payload = {
...values,
position: Number(values.position),
candidate_texts: String(values.candidate_texts || '')
.split(',')
.map((item: string) => item.trim())
.filter(Boolean) };
await creatorCreate('/creator/relations', {
big_account_id: selected.id,
small_account_id: values.execution_account_id,
enabled: true });
const result = editingStrategyID
? await creatorUpdate(`/creator/strategies/${encodeURIComponent(editingStrategyID)}`, payload)
: await creatorCreate(`/creator/accounts/${encodeURIComponent(selected.id)}/strategies`, payload);
setStrategies((items) =>
(editingStrategyID ? items.map((item) => (item.id === editingStrategyID ? result : item)) : [...items, result]).sort(
(left: any, right: any) => left.position - right.position,
),
);
strategyForm.resetFields();
setEditingStrategyID('');
messageApi.success('自动响应策略已保存。');
} catch (createError) {
messageApi.error(conflictMessage(createError, '策略保存失败;请确认执行账号已绑定且平台一致'));
} finally {
setBusy(false);
}
}
async function setStrategyEnabled(strategy: any, enabled: boolean) {
setBusy(true);
try {
const result = await creatorAction(`/creator/strategies/${encodeURIComponent(strategy.id)}/${enabled ? 'enable' : 'disable'}`);
setStrategies((items) => items.map((item) => (item.id === strategy.id ? result : item)));
} catch (actionError) {
messageApi.error(conflictMessage(actionError, '策略状态更新失败'));
} finally {
setBusy(false);
}
}
async function unlinkRelation(relation: any) {
setBusy(true);
try {
await creatorCreate('/creator/relations', {
big_account_id: relation.big_account_id,
small_account_id: relation.small_account_id,
enabled: false });
setRelations((items) => items.filter((item) => item.small_account_id !== relation.small_account_id));
messageApi.success('账号关系已解除;已有策略不会自动恢复。');
} catch (actionError) {
messageApi.error(conflictMessage(actionError, '账号关系解除失败'));
} finally {
setBusy(false);
}
}
async function deleteStrategy(strategy: any) {
setBusy(true);
try {
await creatorRequest(`/creator/strategies/${encodeURIComponent(strategy.id)}`, { method: 'DELETE' });
setStrategies((items) => items.filter((item) => item.id !== strategy.id));
messageApi.success('策略已删除。');
} catch (actionError) {
messageApi.error(conflictMessage(actionError, '策略删除失败'));
} finally {
setBusy(false);
}
}
if (pending) return <Card loading />;
if (error && !selected)
return (
@@ -248,14 +120,21 @@ export default function Page() {
);
if (!selected) return <Alert type="warning" showIcon title="账号不存在。" />;
const smallAccounts = profiles.filter(
(profile) => profile.id !== selected.id && profile.authorization_kind === 'owned' && profile.platform === selected.platform,
);
const profileTab = (
return (
<div>
<Flex>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 12 }}>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Typography.Title level={4} style={{ marginBottom: 0 }}>
{selected.name || selected.platform_account_key}
</Typography.Title>
<Typography.Text type="secondary">
{platformLabelMap[selected.platform] || selected.platform} · {selected.platform_account_key}
</Typography.Text>
</div>
<Button onClick={() => history.push('/accounts')}>返回社媒账号</Button>
</div>
<Flex vertical gap={16}>
<Flex>
<Descriptions column={1} size="small" style={{ flex: 1, minWidth: 260 }}>
<Descriptions.Item label="登录核验">
{selected.login_status === 'logged_in' ? '已登录' : '需人工确认'}
@@ -263,12 +142,6 @@ export default function Page() {
{selected.login_reason ? ` · ${selected.login_reason}` : ''}
</Descriptions.Item>
<Descriptions.Item label="密码凭据">{selected.password_configured ? '已配置' : '未配置'}</Descriptions.Item>
<Descriptions.Item label="监听">
{listener ? listener.status : '尚未启动'}
{listener?.boundary_at ? ` · 边界 ${dateTime(listener.boundary_at)}` : ' · 等待平台边界'}
{listener?.reason ? ` · ${listener.reason}` : ''}
</Descriptions.Item>
{listenerError ? <Descriptions.Item label="监听状态">{conflictMessage(listenerError, '请重试')}</Descriptions.Item> : null}
</Descriptions>
<Space wrap>
<Button disabled={busy || selected.platform !== 'douyin'} onClick={requestLoginQR}>
@@ -277,11 +150,8 @@ export default function Page() {
<Button disabled={busy || selected.platform !== 'douyin'} onClick={verifyLogin}>
核验浏览器身份
</Button>
<Button type={selected.big_account ? 'primary' : 'default'} onClick={toggleBig} disabled={busy}>
{selected.big_account ? '大号模式已开启' : '开启大号模式'}
</Button>
</Space>
</div>
</Flex>
{loginQR ? (
<Card size="small" title="人工登录" extra={<Button size="small" onClick={requestLoginQR} disabled={busy}>刷新登录画面</Button>}>
<Typography.Text type="secondary">
@@ -332,16 +202,10 @@ export default function Page() {
<Form.Item name="identity_number" label="实名证件号">
<Input />
</Form.Item>
<Form.Item name="cooldown_seconds" label="自动响应冷却(秒)">
<InputNumber min={1} style={{ width: 160 }} />
</Form.Item>
</Space>
<Form.Item name="note" label="账号备注">
<Input.TextArea />
</Form.Item>
<Form.Item name="reply_requirements" label="统一 AI 回复要求" extra="不填写时,带文案策略必须配置候选文本。">
<Input.TextArea />
</Form.Item>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography.Text type="secondary">最近更新:{selected.updated_at ? dateTime(selected.updated_at) : '—'}</Typography.Text>
<Button type="primary" htmlType="submit" loading={busy}>
@@ -352,127 +216,4 @@ export default function Page() {
</Flex>
</div>
);
const strategyTab = selected.big_account ? (
<div>
<Flex>
<Typography.Text type="secondary">先绑定同平台小号,再保存单动作策略;没有平台执行证据时只记录为不确定。</Typography.Text>
<Form form={strategyForm} layout="vertical" onFinish={createStrategy} style={{ background: '#fafafa', padding: 16, borderRadius: 8 }}>
<Space size={16} wrap style={{ display: 'flex' }}>
<Form.Item name="execution_account_id" label="执行小号" required rules={[{ required: true, message: '请先选择同平台小号' }]} extra="这里只显示自有账号;监测账号不能作为小号。">
<Select style={{ width: 220 }} placeholder="选择同平台小号" options={smallAccounts.map((p) => ({ value: p.id, label: p.name || p.platform_account_key }))} />
</Form.Item>
<Form.Item name="position" label="顺序" initialValue={1}>
<InputNumber min={1} style={{ width: 100 }} />
</Form.Item>
<Form.Item name="action" label="动作" initialValue="reply_comment">
<Select
style={{ width: 160 }}
options={[
{ value: 'reply_comment', label: '回复评论' },
{ value: 'dm', label: '私信' },
{ value: 'like_comment', label: '点赞评论' },
{ value: 'like_work', label: '点赞作品' },
{ value: 'follow', label: '关注' },
{ value: 'repost', label: '带文案转发' },
]}
/>
</Form.Item>
<Form.Item name="candidate_texts" label="候选文案" extra="逗号分隔;留空时使用大号统一 AI 要求。">
<Input style={{ width: 320 }} />
</Form.Item>
</Space>
<Form.Item name="event_types" label="触发事件" initialValue={['comment']} rules={[{ required: true, message: '至少选择一种触发事件。' }]}>
<Checkbox.Group options={eventTypeOptions} />
</Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={busy}>
{busy ? '保存中…' : editingStrategyID ? '保存策略修改' : '绑定并保存策略'}
</Button>
{editingStrategyID ? (
<Button onClick={() => setEditingStrategyID('')} disabled={busy}>
取消编辑
</Button>
) : null}
</Space>
</Form>
{strategyError ? <Alert type="warning" showIcon title={`策略读取失败:${conflictMessage(strategyError, '请只读重试')}`} /> : null}
{relationError ? <Alert type="warning" showIcon title={`账号关系读取失败:${conflictMessage(relationError, '请只读重试')}`} /> : null}
{relations.map((relation) => (
<Card key={`${relation.big_account_id}:${relation.small_account_id}`} size="small">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>执行账号:{relation.small_account_id}</span>
<Button size="small" onClick={() => unlinkRelation(relation)} disabled={busy}>
解除绑定
</Button>
</div>
</Card>
))}
{strategies.map((strategy) => (
<Card key={strategy.id} size="small">
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
<Space wrap>
<span>
#{strategy.position} · {strategy.action} · {strategy.execution_account_id} · {(strategy.event_types || []).join('、')}
</span>
<Tag color={strategy.enabled ? 'success' : 'default'}>{strategy.enabled ? '启用' : '停用'}</Tag>
</Space>
<Space>
<Button
size="small"
onClick={() => {
setEditingStrategyID(strategy.id);
strategyForm.setFieldsValue({
execution_account_id: strategy.execution_account_id,
position: strategy.position,
enabled: strategy.enabled,
event_types: strategy.event_types || [],
action: strategy.action,
candidate_texts: (strategy.candidate_texts || []).join(', ') });
}}
disabled={busy}
>
编辑 / 调序
</Button>
<Button size="small" onClick={() => setStrategyEnabled(strategy, !strategy.enabled)} disabled={busy}>
{strategy.enabled ? '停用' : '启用'}
</Button>
<Popconfirm title="确定删除该策略吗?" onConfirm={() => deleteStrategy(strategy)}>
<Button size="small" danger disabled={busy}>
删除
</Button>
</Popconfirm>
</Space>
</div>
</Card>
))}
{!strategies.length && !strategyError ? <Typography.Text type="secondary">尚未配置策略。</Typography.Text> : null}
</Flex>
</div>
) : (
<Typography.Text type="secondary">开启大号模式后可配置有序自动响应策略。</Typography.Text>
);
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<Typography.Title level={4} style={{ marginBottom: 0 }}>
{selected.name || selected.platform_account_key}
</Typography.Title>
<Typography.Text type="secondary">
{platformLabelMap[selected.platform] || selected.platform} · {selected.platform_account_key}
</Typography.Text>
</div>
<Button onClick={() => history.push('/accounts')}>返回社媒账号</Button>
</div>
<Tabs
defaultActiveKey="profile"
items={[
{ key: 'profile', label: '账号资料', children: profileTab },
{ key: 'strategy', label: '自动响应策略', children: strategyTab },
]}
/>
</div>
);
}
-4
View File
@@ -135,10 +135,6 @@ export function creatorUpdate(path: string, data: unknown) {
return request(path, jsonOptions('PUT', data));
}
export function creatorSyncConversation(id: string, limit = 200) {
return request(`/creator/conversations/${encodeURIComponent(id)}/sync?limit=${encodeURIComponent(limit)}`, { method: 'POST' });
}
// SSE 订阅:语义对齐归档版 dataProvider.creatorSubscribe(fetch 流式读取,按 data: 帧分发)。
export async function creatorSubscribe(
path: string,