Preserve latest active sidebar and channel copy during work restoration
This commit is contained in:
1 parent
d16c889411
commit
40930aecac
3 files changed
+41
-12
No files matched your search
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
- Implemented project work-entry persistence, deterministic Vite/static startup, content/project matching before address reuse, bounded legacy browser-receipt recovery without Pi startup, and explicit assistant opt-in with domain-specific error copy.
|
- Implemented project work-entry persistence, deterministic Vite/static startup, content/project matching before address reuse, bounded legacy browser-receipt recovery without Pi startup, and explicit assistant opt-in with domain-specific error copy.
|
||||||
- Preserve manual opens across slow health probes/local startup; serialize project checks and metadata writes; retain successful newly started server addresses when project selection changes; stop only unused app-owned children superseded by another server.
|
- Preserve manual opens across slow health probes/local startup; serialize project checks and metadata writes; retain successful newly started server addresses when project selection changes; stop only unused app-owned children superseded by another server.
|
||||||
- Imported only already-activated product/README deltas from sidebar source commits 8cd7b597 and 14fcea389, preserving the currently effective lower-footer placement. Peer source records stay read-only. This is preservation of the latest trial baseline, not an additional sidebar redesign.
|
- Imported only currently activated product deltas from sidebar sources 8cd7b597/14fcea389/cca23df7 and the later active Renderer d0451e9a, preserving footer placement, WeChat status copy and default expanded module sidebar. A three-line ModuleSelection edit appeared concurrently in this owned checkout; its bytes were independently confirmed identical to the current activated d0451e9a source before inclusion, and were never reset/stashed or guessed. Peer source records stay read-only. This is preservation of the latest trial baseline, not an additional sidebar redesign.
|
||||||
- Exact production build and isolated real Electron full-restart/stopped-service/native-render acceptance passed. Current temporary-app activation and actual Alice acceptance pending.
|
- Exact production build and isolated real Electron full-restart/stopped-service/native-render acceptance passed. Current temporary-app activation and actual Alice acceptance pending.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
- 134 focused tests in 6 files passed after root integration; includes real Vite/static launch, resolved base configuration, fresh store reload, imported-folder metadata creation, content/port ownership, explicit helper selection, project switching and slow manual supersession.
|
- 134 focused tests in 6 files passed after root integration; includes real Vite/static launch, resolved base configuration, fresh store reload, imported-folder metadata creation, content/port ownership, explicit helper selection, project switching and slow manual supersession.
|
||||||
- Standard typecheck, changed-file ESLint, git diff --check passed. Independent review reproduced races and a Vite base issue before correction; final root tests cover those corrections.
|
- Standard typecheck, changed-file ESLint, git diff --check passed. Independent review reproduced races and a Vite base issue before correction; final root tests cover those corrections.
|
||||||
- Final exact production build from product commit 0f26b6d passed. Isolated Electron acceptance 1/1 passed: real Main quit/relaunch, persisted URL reuse, controlled original service stop, real app-owned static restart and native HTML rendering, with unchanged conversation list and no model session. Fixture guest account uses actual native presentation route rather than fabricating cloud login; actual guarded Code UI is verified on the existing trial below.
|
- Final exact production build from product commit 0f26b6d passed. Isolated Electron acceptance 1/1 passed: real Main quit/relaunch, persisted URL reuse, controlled original service stop, real app-owned static restart and native HTML rendering, with unchanged conversation list and no model session. Fixture guest account uses actual native presentation route rather than fabricating cloud login; actual guarded Code UI is verified on the existing trial below.
|
||||||
- Full normal unit suite: 297 files passed, 3333 tests passed/3 skipped; 8 failures in 3 unrelated existing setup/cloud-knowledge/design-image-preview files. Exact previous-renderer baseline comparison is running to establish inherited failures; pressure suite runs separately because the normal script stops at failed first phase.
|
- Full normal unit suite: 297 files passed, 3333 tests passed/3 skipped; 8 failures in 3 unrelated existing setup/cloud-knowledge/design-image-preview files. Exact previous-renderer baseline 14fcea389 reproduced all same 8 failures; none was introduced by this task. Separate pressure test passed 1/1 because the normal script stops at failed first phase.
|
||||||
- Early isolated Electron attempts exposed imported metadata creation and fixture activity/viewport setup gaps, which were corrected. No real account, model, game source or cloud state was mutated by tests.
|
- Early isolated Electron attempts exposed imported metadata creation and fixture activity/viewport setup gaps, which were corrected. No real account, model, game source or cloud state was mutated by tests.
|
||||||
|
|
||||||
## Follow-ups
|
## Follow-ups
|
||||||
|
|||||||
@@ -65,9 +65,18 @@ const qrLabels: Record<string, string> = {
|
|||||||
const blockerText: Record<string, string> = {
|
const blockerText: Record<string, string> = {
|
||||||
target_unavailable: '当前智能体暂时不能使用,可以更换一个智能体。',
|
target_unavailable: '当前智能体暂时不能使用,可以更换一个智能体。',
|
||||||
publish_required: '请先让智能体开始使用。',
|
publish_required: '请先让智能体开始使用。',
|
||||||
worker_offline: '微信连接暂时不可用,请稍后刷新。',
|
worker_offline: '智能体的微信服务暂时离线,暂时无法自动回复。',
|
||||||
channel_sync_degraded: '正在确认微信的使用状态。',
|
channel_sync_degraded: '微信使用状态尚未同步,请刷新状态后再继续。',
|
||||||
};
|
};
|
||||||
|
function blockerMessage(blocker: string, account: CloudUserChannelAccount) {
|
||||||
|
const text = blockerText[blocker];
|
||||||
|
return blocker === 'worker_offline' &&
|
||||||
|
account.health === 'connected' &&
|
||||||
|
account.target_agent_slug &&
|
||||||
|
!account.blockers.includes('wechat_disconnect_pending')
|
||||||
|
? text + '微信登录和智能体选择已保留,服务恢复后请刷新状态。'
|
||||||
|
: text;
|
||||||
|
}
|
||||||
const unsettled = (account: CloudUserChannelAccount) =>
|
const unsettled = (account: CloudUserChannelAccount) =>
|
||||||
account.blockers.includes('wechat_disconnect_pending') ||
|
account.blockers.includes('wechat_disconnect_pending') ||
|
||||||
Boolean(account.sync_state && account.sync_state !== 'ready') ||
|
Boolean(account.sync_state && account.sync_state !== 'ready') ||
|
||||||
@@ -75,17 +84,28 @@ const unsettled = (account: CloudUserChannelAccount) =>
|
|||||||
(['paused', 'disabled'].includes(account.desired_state ?? '') && account.enabled) ||
|
(['paused', 'disabled'].includes(account.desired_state ?? '') && account.enabled) ||
|
||||||
(account.desired_state === 'enabled' && !account.enabled);
|
(account.desired_state === 'enabled' && !account.enabled);
|
||||||
function accountState(account: CloudUserChannelAccount, uncertain: boolean, targetDisabled = false) {
|
function accountState(account: CloudUserChannelAccount, uncertain: boolean, targetDisabled = false) {
|
||||||
if (uncertain || unsettled(account)) return { text: '正在确认', tone: 'bg-[#fff0d8] text-[#876033]' };
|
if (uncertain || unsettled(account)) return { text: '状态待确认', tone: 'bg-[#fff0d8] text-[#876033]' };
|
||||||
if (!account.target_agent_slug) return { text: '未绑定', tone: 'bg-[#f0edf8] text-[#75618e]' };
|
if (!account.target_agent_slug) return { text: '未绑定', tone: 'bg-[#f0edf8] text-[#75618e]' };
|
||||||
if (['login_required', 'verification_required'].includes(account.health))
|
if (['login_required', 'verification_required'].includes(account.health))
|
||||||
return { text: '需要重新扫码', tone: 'bg-[#fff0d8] text-[#876033]' };
|
return { text: '需要重新扫码', tone: 'bg-[#fff0d8] text-[#876033]' };
|
||||||
if (targetDisabled) return { text: '智能体已停用', tone: 'bg-[#fff0d8] text-[#876033]' };
|
if (targetDisabled) return { text: '智能体已停用', tone: 'bg-[#fff0d8] text-[#876033]' };
|
||||||
|
if (account.health === 'connected' && account.blockers.includes('worker_offline'))
|
||||||
|
return { text: '微信服务离线', tone: 'bg-[#fff0d8] text-[#876033]' };
|
||||||
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
||||||
return { text: '正在确认', tone: 'bg-[#fff0d8] text-[#876033]' };
|
return { text: '状态待确认', tone: 'bg-[#fff0d8] text-[#876033]' };
|
||||||
return account.enabled
|
return account.enabled
|
||||||
? { text: '使用中', tone: 'bg-[#e7f4ec] text-[#3f7458]' }
|
? { text: '使用中', tone: 'bg-[#e7f4ec] text-[#3f7458]' }
|
||||||
: { text: '已暂停', tone: 'bg-[#f0edf8] text-[#75618e]' };
|
: { text: '已暂停', tone: 'bg-[#f0edf8] text-[#75618e]' };
|
||||||
}
|
}
|
||||||
|
function bindingReadinessMessage(account: CloudUserChannelAccount) {
|
||||||
|
if (unsettled(account)) return '微信使用状态尚未确认,请刷新状态后再继续。';
|
||||||
|
if (account.health !== 'connected')
|
||||||
|
return '微信登录状态尚未确认,请先完成微信登录,再检查连接。';
|
||||||
|
const blocker = usableBlockers(account).find((item) => blockerText[item]);
|
||||||
|
return blocker
|
||||||
|
? blockerMessage(blocker, account)
|
||||||
|
: '暂时无法完成微信绑定,请检查连接后再继续。';
|
||||||
|
}
|
||||||
async function loadAgents(): Promise<CloudAgentDraft[]> {
|
async function loadAgents(): Promise<CloudAgentDraft[]> {
|
||||||
const items: CloudAgentDraft[] = [];
|
const items: CloudAgentDraft[] = [];
|
||||||
let cursor: string | null = null;
|
let cursor: string | null = null;
|
||||||
@@ -347,7 +367,7 @@ export function ChannelAccounts() {
|
|||||||
if (!account || account.target_agent_slug !== target)
|
if (!account || account.target_agent_slug !== target)
|
||||||
throw new Error('绑定的智能体已发生变化,请返回上一步确认。');
|
throw new Error('绑定的智能体已发生变化,请返回上一步确认。');
|
||||||
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
||||||
throw new Error('微信还没有准备好,请稍后重试完成绑定。');
|
throw new Error(bindingReadinessMessage(account));
|
||||||
if (ownerPending.current) throw new Error('微信退出还在确认中,请先处理原来的操作。');
|
if (ownerPending.current) throw new Error('微信退出还在确认中,请先处理原来的操作。');
|
||||||
if (agentsRef.current.some((agent) => agent.slug === target && agent.enabled === false))
|
if (agentsRef.current.some((agent) => agent.slug === target && agent.enabled === false))
|
||||||
throw new Error('当前智能体已停用,请更换智能体后再使用。');
|
throw new Error('当前智能体已停用,请更换智能体后再使用。');
|
||||||
@@ -361,7 +381,7 @@ export function ChannelAccounts() {
|
|||||||
account = await verifyFinal(id, { target, enabled: true });
|
account = await verifyFinal(id, { target, enabled: true });
|
||||||
}
|
}
|
||||||
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
if (account.health !== 'connected' || unsettled(account) || usableBlockers(account).length)
|
||||||
throw new Error('微信还没有准备好,请稍后重试完成绑定。');
|
throw new Error(bindingReadinessMessage(account));
|
||||||
intents.current.delete('enable');
|
intents.current.delete('enable');
|
||||||
return account;
|
return account;
|
||||||
});
|
});
|
||||||
@@ -785,7 +805,7 @@ export function ChannelAccounts() {
|
|||||||
.filter((item) => blockerText[item])
|
.filter((item) => blockerText[item])
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<p key={item} className="mb-3 text-xs leading-5 text-[#876033]">
|
<p key={item} className="mb-3 text-xs leading-5 text-[#876033]">
|
||||||
{blockerText[item]}
|
{blockerMessage(item, account)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
<div className="mt-auto flex gap-2">
|
<div className="mt-auto flex gap-2">
|
||||||
@@ -990,10 +1010,16 @@ export function ChannelAccounts() {
|
|||||||
{qr?.status === 'confirmed' || selected?.health === 'connected' ? (
|
{qr?.status === 'confirmed' || selected?.health === 'connected' ? (
|
||||||
<div role="status" className="rounded-2xl bg-[#e7f4ec] p-5 text-center text-[#3f7458]">
|
<div role="status" className="rounded-2xl bg-[#e7f4ec] p-5 text-center text-[#3f7458]">
|
||||||
<CheckCircle2 className="mx-auto h-9 w-9" />
|
<CheckCircle2 className="mx-auto h-9 w-9" />
|
||||||
<p className="mt-3 font-semibold">{busy ? '正在完成微信绑定…' : '微信登录已确认'}</p>
|
<p className="mt-3 font-semibold">
|
||||||
|
{busy
|
||||||
|
? '正在检查连接并完成绑定…'
|
||||||
|
: selected?.health === 'connected'
|
||||||
|
? '微信登录已确认'
|
||||||
|
: '扫码已确认'}
|
||||||
|
</p>
|
||||||
<p className="mt-2 text-sm">
|
<p className="mt-2 text-sm">
|
||||||
{connectionFailed
|
{connectionFailed
|
||||||
? '还没有完成绑定,请查看上方提示。'
|
? '绑定尚未完成,请按上方提示处理,再检查连接。'
|
||||||
: '正在确认智能体和自动回复状态。'}
|
: '正在确认智能体和自动回复状态。'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1052,7 +1078,7 @@ export function ChannelAccounts() {
|
|||||||
disabled={disabled || checkingIds.includes(flow.accountId)}
|
disabled={disabled || checkingIds.includes(flow.accountId)}
|
||||||
onClick={() => void finishBinding(flow.accountId, flow.target)}
|
onClick={() => void finishBinding(flow.accountId, flow.target)}
|
||||||
>
|
>
|
||||||
重试完成绑定
|
检查连接并完成绑定
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{checkingIds.includes(flow.accountId) && (
|
{checkingIds.includes(flow.accountId) && (
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { aiModules, isAiModuleAllowed, type AiModuleId } from '@/lib/ai-modules'
|
|||||||
import { getAuthUserDisplayName } from '@/lib/auth-user-display';
|
import { getAuthUserDisplayName } from '@/lib/auth-user-display';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { getUserProfileDisplayName } from '@/stores/user-profile';
|
import { getUserProfileDisplayName } from '@/stores/user-profile';
|
||||||
|
|
||||||
const moduleSelectionContent: Record<AiModuleId, { label: string; image: string; imageAlt: string }> = {
|
const moduleSelectionContent: Record<AiModuleId, { label: string; image: string; imageAlt: string }> = {
|
||||||
@@ -23,6 +24,7 @@ const moduleSelectionContent: Record<AiModuleId, { label: string; image: string;
|
|||||||
|
|
||||||
export function ModuleSelection({ authRequired = true }: { authRequired?: boolean }) {
|
export function ModuleSelection({ authRequired = true }: { authRequired?: boolean }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||||
const authUser = useAuthStore((state) => state.user);
|
const authUser = useAuthStore((state) => state.user);
|
||||||
const moduleAccess = useAuthStore((state) => state.moduleAccess);
|
const moduleAccess = useAuthStore((state) => state.moduleAccess);
|
||||||
const authenticated = useAuthStore((state) => state.isAuthenticated());
|
const authenticated = useAuthStore((state) => state.isAuthenticated());
|
||||||
@@ -46,6 +48,7 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (useSettingsStore.getState().sidebarCollapsed) setSidebarCollapsed(false);
|
||||||
navigate(route === '/cloud-agents' ? `${route}?view=mine` : route);
|
navigate(route === '/cloud-agents' ? `${route}?view=mine` : route);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user