fix(client): migrate digital storage keys
This commit is contained in:
@@ -7,8 +7,10 @@
|
|||||||
<script>
|
<script>
|
||||||
window.__QIMINGCLAW_EMBEDDED_DIGITAL__ = true;
|
window.__QIMINGCLAW_EMBEDDED_DIGITAL__ = true;
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem("zeroclaw_token", "qimingclaw_embedded_digital_employee");
|
window.localStorage.setItem("qimingclaw_digital_token", "qimingclaw_embedded_digital_employee");
|
||||||
window.sessionStorage.setItem("sgrobot_logged_in", "1");
|
window.localStorage.removeItem("zeroclaw_token");
|
||||||
|
window.sessionStorage.setItem("qimingclaw_digital_logged_in", "1");
|
||||||
|
window.sessionStorage.removeItem("sgrobot_logged_in");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,11 +122,12 @@ interface RoleContextValue {
|
|||||||
|
|
||||||
const RoleContext = createContext<RoleContextValue | null>(null);
|
const RoleContext = createContext<RoleContextValue | null>(null);
|
||||||
|
|
||||||
const STORAGE_KEY = 'sgrobot-console-role';
|
const STORAGE_KEY = 'qimingclaw:digital-console-role';
|
||||||
|
const LEGACY_STORAGE_KEY = 'sgrobot-console-role';
|
||||||
|
|
||||||
function getStoredRole(): ConsoleRole {
|
function getStoredRole(): ConsoleRole {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(STORAGE_KEY);
|
const stored = localStorage.getItem(STORAGE_KEY) ?? localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||||
if (stored && stored in ROLE_PROFILES) return stored as ConsoleRole;
|
if (stored && stored in ROLE_PROFILES) return stored as ConsoleRole;
|
||||||
} catch { /* localStorage blocked */ }
|
} catch { /* localStorage blocked */ }
|
||||||
return 'sales';
|
return 'sales';
|
||||||
@@ -137,7 +138,10 @@ export function RoleProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
const setRole = useCallback((newRole: ConsoleRole) => {
|
const setRole = useCallback((newRole: ConsoleRole) => {
|
||||||
setRoleState(newRole);
|
setRoleState(newRole);
|
||||||
try { localStorage.setItem(STORAGE_KEY, newRole); } catch { /* noop */ }
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, newRole);
|
||||||
|
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||||
|
} catch { /* noop */ }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const profile = ROLE_PROFILES[role];
|
const profile = ROLE_PROFILES[role];
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export async function apiFetch<T = unknown>(
|
|||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
clearToken();
|
clearToken();
|
||||||
window.dispatchEvent(new Event('zeroclaw-unauthorized'));
|
window.dispatchEvent(new Event('qimingclaw-digital-unauthorized'));
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
const TOKEN_KEY = 'zeroclaw_token';
|
const TOKEN_KEY = 'qimingclaw_digital_token';
|
||||||
|
const LEGACY_TOKEN_KEY = 'zeroclaw_token';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the stored authentication token.
|
* Retrieve the stored authentication token.
|
||||||
*/
|
*/
|
||||||
export function getToken(): string | null {
|
export function getToken(): string | null {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(TOKEN_KEY);
|
return localStorage.getItem(TOKEN_KEY) ?? localStorage.getItem(LEGACY_TOKEN_KEY);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,7 @@ export function getToken(): string | null {
|
|||||||
export function setToken(token: string): void {
|
export function setToken(token: string): void {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(TOKEN_KEY, token);
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
|
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||||
} catch {
|
} catch {
|
||||||
// localStorage may be unavailable (e.g. in some private browsing modes)
|
// localStorage may be unavailable (e.g. in some private browsing modes)
|
||||||
}
|
}
|
||||||
@@ -28,6 +30,7 @@ export function setToken(token: string): void {
|
|||||||
export function clearToken(): void {
|
export function clearToken(): void {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(TOKEN_KEY);
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__SGROBOT_BASE__?: string;
|
|
||||||
__QIMINGCLAW_DIGITAL_API_BASE__?: string;
|
__QIMINGCLAW_DIGITAL_API_BASE__?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ const EVENT_STATUS_FILTERS: Array<{ id: EventStatusFilter; label: string }> = [
|
|||||||
{ id: 'success', label: '已完成' },
|
{ id: 'success', label: '已完成' },
|
||||||
{ id: 'danger', label: '异常' },
|
{ id: 'danger', label: '异常' },
|
||||||
];
|
];
|
||||||
const WORKDAY_GUIDE_DISMISSED_STORAGE_KEY = 'sgrobot:digital-workday-guide-dismissed-date';
|
const WORKDAY_GUIDE_DISMISSED_STORAGE_KEY = 'qimingclaw:digital-workday-guide-dismissed-date';
|
||||||
|
const LEGACY_WORKDAY_GUIDE_DISMISSED_STORAGE_KEY = 'sgrobot:digital-workday-guide-dismissed-date';
|
||||||
const MAIN_RUN_DETAIL_LIMIT = 80;
|
const MAIN_RUN_DETAIL_LIMIT = 80;
|
||||||
const EVENT_HISTORY_RENDER_LIMIT = 300;
|
const EVENT_HISTORY_RENDER_LIMIT = 300;
|
||||||
const WORKDAY_GUIDE_STEPS = [
|
const WORKDAY_GUIDE_STEPS = [
|
||||||
@@ -778,7 +779,8 @@ function resolveEmployeeVisualState(args: {
|
|||||||
function readGuideDismissedDate(): string | null {
|
function readGuideDismissedDate(): string | null {
|
||||||
if (typeof window === 'undefined') return null;
|
if (typeof window === 'undefined') return null;
|
||||||
try {
|
try {
|
||||||
return window.sessionStorage.getItem(WORKDAY_GUIDE_DISMISSED_STORAGE_KEY);
|
return window.sessionStorage.getItem(WORKDAY_GUIDE_DISMISSED_STORAGE_KEY)
|
||||||
|
?? window.sessionStorage.getItem(LEGACY_WORKDAY_GUIDE_DISMISSED_STORAGE_KEY);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -788,6 +790,7 @@ function rememberGuideDismissedDate(date: string): void {
|
|||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.setItem(WORKDAY_GUIDE_DISMISSED_STORAGE_KEY, date);
|
window.sessionStorage.setItem(WORKDAY_GUIDE_DISMISSED_STORAGE_KEY, date);
|
||||||
|
window.sessionStorage.removeItem(LEGACY_WORKDAY_GUIDE_DISMISSED_STORAGE_KEY);
|
||||||
} catch {
|
} catch {
|
||||||
// Session storage can be unavailable in restricted browser contexts.
|
// Session storage can be unavailable in restricted browser contexts.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const TABS: { id: DigitalTab; label: string }[] = [
|
|||||||
{ id: 'reports', label: '日报' },
|
{ id: 'reports', label: '日报' },
|
||||||
{ id: 'settings', label: '设置' },
|
{ id: 'settings', label: '设置' },
|
||||||
];
|
];
|
||||||
const DIGITAL_EMPLOYEE_CLOSE_MESSAGE = 'sgrobot:digital-close';
|
const DIGITAL_EMPLOYEE_CLOSE_MESSAGE = 'qimingclaw:digital-close';
|
||||||
|
|
||||||
function isDigitalTab(value: string | null): value is DigitalTab {
|
function isDigitalTab(value: string | null): value is DigitalTab {
|
||||||
return Boolean(value && TABS.some(tab => tab.id === value));
|
return Boolean(value && TABS.some(tab => tab.id === value));
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { useRole, ROLE_PROFILES, type ConsoleRole } from '@/contexts/RoleContext
|
|||||||
import { t } from '@/lib/i18n';
|
import { t } from '@/lib/i18n';
|
||||||
|
|
||||||
const ROLES: ConsoleRole[] = ['sales', 'engineering', 'hr', 'finance', 'executive'];
|
const ROLES: ConsoleRole[] = ['sales', 'engineering', 'hr', 'finance', 'executive'];
|
||||||
const STORAGE_KEY = 'sgrobot-role-preferences';
|
const STORAGE_KEY = 'qimingclaw:digital-role-preferences';
|
||||||
|
const LEGACY_STORAGE_KEY = 'sgrobot-role-preferences';
|
||||||
|
|
||||||
type RolePreferences = {
|
type RolePreferences = {
|
||||||
workStart: string;
|
workStart: string;
|
||||||
@@ -58,7 +59,7 @@ const inputStyle: CSSProperties = {
|
|||||||
|
|
||||||
function loadPreferences(): RolePreferences {
|
function loadPreferences(): RolePreferences {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(STORAGE_KEY) ?? localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||||
if (!raw) return defaultPreferences;
|
if (!raw) return defaultPreferences;
|
||||||
return { ...defaultPreferences, ...JSON.parse(raw) };
|
return { ...defaultPreferences, ...JSON.parse(raw) };
|
||||||
} catch {
|
} catch {
|
||||||
@@ -84,7 +85,10 @@ export default function OpsSettings() {
|
|||||||
|
|
||||||
const savePreferences = (next: RolePreferences) => {
|
const savePreferences = (next: RolePreferences) => {
|
||||||
setPreferences(next);
|
setPreferences(next);
|
||||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); } catch { /* localStorage blocked */ }
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
||||||
|
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||||
|
} catch { /* localStorage blocked */ }
|
||||||
markSaved();
|
markSaved();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,13 +7,15 @@
|
|||||||
<script>
|
<script>
|
||||||
window.__QIMINGCLAW_EMBEDDED_DIGITAL__ = true;
|
window.__QIMINGCLAW_EMBEDDED_DIGITAL__ = true;
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem("zeroclaw_token", "qimingclaw_embedded_digital_employee");
|
window.localStorage.setItem("qimingclaw_digital_token", "qimingclaw_embedded_digital_employee");
|
||||||
window.sessionStorage.setItem("sgrobot_logged_in", "1");
|
window.localStorage.removeItem("zeroclaw_token");
|
||||||
|
window.sessionStorage.setItem("qimingclaw_digital_logged_in", "1");
|
||||||
|
window.sessionStorage.removeItem("sgrobot_logged_in");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="./assets/index-CqNAW7JI.js"></script>
|
<script type="module" crossorigin src="./assets/index-C2-aThxb.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-ReBtI0Fy.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-ReBtI0Fy.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ crates/agent-electron-client/src/preload/webviewPerfBridge.ts
|
|||||||
- 任务中心的 `/api/governance/command` 已接入 qimingclaw 兼容层:`create_plan`、`submit_plan`、`approve_plan`、`activate_plan`、`create_schedule`、`run_schedule_now` 等命令返回本地可解释结果,其中激活/立即运行会映射到 adapter 的 dispatch/workday 状态。
|
- 任务中心的 `/api/governance/command` 已接入 qimingclaw 兼容层:`create_plan`、`submit_plan`、`approve_plan`、`activate_plan`、`create_schedule`、`run_schedule_now` 等命令返回本地可解释结果,其中激活/立即运行会映射到 adapter 的 dispatch/workday 状态。
|
||||||
- 日报下载不再打开 sgRobot 风格 PDF 后端直链;生成日报后会基于当前 qimingclaw workday 投影在浏览器内导出本地文本日报。
|
- 日报下载不再打开 sgRobot 风格 PDF 后端直链;生成日报后会基于当前 qimingclaw workday 投影在浏览器内导出本地文本日报。
|
||||||
- 任务中心在加载到 qimingclaw 真实计划时不再混入示例 supplement;只有没有任何真实计划或请求失败时才使用 qimingclaw 客户端职责语义的本地示例兜底。
|
- 任务中心在加载到 qimingclaw 真实计划时不再混入示例 supplement;只有没有任何真实计划或请求失败时才使用 qimingclaw 客户端职责语义的本地示例兜底。
|
||||||
|
- 嵌入页 localStorage/sessionStorage 的运行态 key 已切换到 `qimingclaw:*` / `qimingclaw_digital_*` 命名;旧 sgRobot/zeroclaw key 仅作为迁移读取或清理,不再作为新状态写入目标。
|
||||||
- 同步状态通过 bridge 返回最近失败 outbox 明细,首页可直接看到实体类型、outbox ID、操作类型、重试次数、失败时间和下次自动重试时间。
|
- 同步状态通过 bridge 返回最近失败 outbox 明细,首页可直接看到实体类型、outbox ID、操作类型、重试次数、失败时间和下次自动重试时间。
|
||||||
- 本地 SQLite 额外记录 `digital_sync_attempts`,每次 outbox 推送成功、失败或部分 ack 缺失都会留下一次尝试历史,默认保留最近 30 天。
|
- 本地 SQLite 额外记录 `digital_sync_attempts`,每次 outbox 推送成功、失败或部分 ack 缺失都会留下一次尝试历史,默认保留最近 30 天。
|
||||||
- 点击失败 outbox 可打开详情弹窗,查看完整错误、实体 ID、退避状态、最近重试历史,并可复制或导出诊断 JSON;也可触发手动 `flushSync` 立即重试。若已配置管理端同步地址,详情弹窗可直接打开对应的管理端同步记录深链。
|
- 点击失败 outbox 可打开详情弹窗,查看完整错误、实体 ID、退避状态、最近重试历史,并可复制或导出诊断 JSON;也可触发手动 `flushSync` 立即重试。若已配置管理端同步地址,详情弹窗可直接打开对应的管理端同步记录深链。
|
||||||
|
|||||||
Reference in New Issue
Block a user