feat: implement browser extension for media publishing and add backend support for media management

This commit is contained in:
2026-04-03 00:39:15 +08:00
parent 8958cb44c0
commit 32d6a462cd
60 changed files with 8268 additions and 26 deletions
+91 -2
View File
@@ -112,7 +112,7 @@ const enUS = {
},
media: {
title: "Media Management",
description: "No matching backend APIs exist in this repository yet.",
description: "Manage media bindings, local extension state, and one-click publishing from a single control plane.",
},
brands: {
title: "Brand Library",
@@ -196,6 +196,9 @@ const enUS = {
publish: {
unpublished: "Unpublished",
publishing: "Publishing",
success: "Published",
failed: "Failed",
partial_success: "Partial Success",
published: "Published",
publish_success: "Published",
publish_failed: "Failed",
@@ -230,6 +233,7 @@ const enUS = {
preview: "Preview",
edit: "Edit article",
editDisabled: "Only draft, failed, or completed articles can be edited",
publishDisabled: "Only completed articles can be published",
versions: "Versions",
deleteConfirm: "Delete this article?",
deleteSuccess: "Article deleted.",
@@ -384,6 +388,7 @@ const enUS = {
drawerTitle: "Article details",
preview: "Content preview",
versionHistory: "Version history",
publishedDetails: "Published details",
untitled: "Untitled article",
noContent: "This article does not have generated content yet.",
editor: {
@@ -405,7 +410,7 @@ const enUS = {
saved: "Article saved.",
generating: "This article is still generating. Try editing it later.",
locked: "Only completed articles can be edited.",
publishPending: "Publishing is not wired yet. The current action only saves content.",
publishPending: "Save the article first, then choose the target platforms.",
},
platforms: {
zhihu: "Zhihu",
@@ -419,6 +424,90 @@ const enUS = {
},
versionUntitled: "Untitled version",
},
media: {
hero: {
eyebrow: "Publisher",
},
plugin: {
title: "Browser Publisher",
ready: "Extension connected",
notInstalled: "Extension not connected",
installTitle: "Install and open the GEO Publisher extension first",
installDesc: "Binding and publishing depend on the browser's local platform sessions. The extension stores its own installation identity so future background checks can continue after the SaaS page is closed.",
backgroundHint: "The extension should not reuse the web app's access token directly. This implementation already registers a dedicated installation identity for future background scanning and inclusion checks.",
versionLabel: "Extension version",
installationLabel: "Installation ID",
connectedSummary: "Locally signed-in platforms",
registerRequired: "The extension installation is not registered yet. Please re-detect first.",
},
actions: {
redetect: "Re-detect",
bind: "Bind",
rebind: "Rebind",
unbind: "Unbind",
},
card: {
bound: "Bound",
unbound: "Unbound",
unboundHint: "{platform} has not been bound to this tenant yet.",
needsAttention: "Needs attention",
expiredHint: "This account is no longer healthy. Rebind it to restore publishing.",
localMissing: "Local session missing",
localMismatch: "Local account mismatch",
readyHint: "The correct local account is available and ready for publish or future background checks.",
accountLabel: "SaaS account",
uidLabel: "Platform UID",
localLabel: "Local state",
localConnected: "Session detected",
localDisconnected: "No session detected",
updatedAt: "Last sync",
multiAccount: "{count} accounts bound",
openPlatform: "Open platform",
},
account: {
localMissing: "The tenant account is bound, but this browser does not currently have the matching local session.",
localMismatch: "This browser is signed in to a different account. Rebind or switch the local account first.",
ready: "Ready",
selectable: "Selectable",
unavailable: "Unavailable",
},
empty: {
accounts: "No publishable media accounts are bound yet.",
},
messages: {
bindSuccess: "{platform} bound successfully.",
bindPending: "The extension did not detect a local session for this platform yet. Sign in via the extension and retry.",
unbindSuccess: "Account unbound.",
unbindConfirm: "Unbind this platform account? Publishing will no longer be available until it is bound again.",
},
publish: {
title: "Publish",
subtitle: "Multi-platform Dispatch",
description: "Choose tenant-bound accounts that are currently signed in locally. The extension executes the real publish flow and writes back the resulting links.",
platformsTitle: "Target platforms",
platformsHint: "Only tenant-bound accounts are listed here. Accounts with missing or mismatched local sessions are disabled automatically.",
coverTitle: "Cover image",
coverHint: "This version uses a cover asset URL. Upload and crop can be wired later.",
coverPlaceholder: "Enter an optional cover asset URL",
messages: {
success: "{count} publish tasks submitted.",
partial: "Publish finished with {success} succeeded and {failed} failed.",
selectPlatform: "Choose at least one available platform first.",
},
},
records: {
tabTitle: "Published details",
platform: "Platform",
status: "Status",
publishedAt: "Published at",
link: "Published link",
open: "Open",
copy: "Copy",
empty: "No publish records yet",
linkEmpty: "No link is available for this record yet.",
copySuccess: "Link copied.",
},
},
brands: {
eyebrow: "Brand Library",
title: "Brand Library",
+91 -2
View File
@@ -112,7 +112,7 @@ const zhCN = {
},
media: {
title: "媒体管理",
description: "当前仓库还没有匹配的后端接口,页面暂保持为设计占位。",
description: "统一管理媒体账号绑定、本地插件状态和一键发布执行链路。",
},
brands: {
title: "品牌词库",
@@ -196,6 +196,9 @@ const zhCN = {
publish: {
unpublished: "未发布",
publishing: "发布中",
success: "发布成功",
failed: "发布失败",
partial_success: "部分成功",
published: "发布成功",
publish_success: "发布成功",
publish_failed: "发布失败",
@@ -230,6 +233,7 @@ const zhCN = {
preview: "预览正文",
edit: "编辑文章",
editDisabled: "只有草稿、生成失败或已完成文章才可编辑",
publishDisabled: "只有已完成文章才可发布",
versions: "查看版本",
deleteConfirm: "确认删除这篇文章吗?",
deleteSuccess: "文章已删除",
@@ -391,6 +395,7 @@ const zhCN = {
drawerTitle: "文章详情",
preview: "正文预览",
versionHistory: "版本记录",
publishedDetails: "已发布详情",
untitled: "未命名文章",
noContent: "当前文章还没有正文内容",
editor: {
@@ -412,7 +417,7 @@ const zhCN = {
saved: "文章已保存",
generating: "文章还在生成中,请稍后再编辑。",
locked: "只有已完成文章才可编辑。",
publishPending: "发布能力待接入,当前仅保存正文内容。",
publishPending: "请先保存文章,再选择需要发布的平台。",
},
platforms: {
zhihu: "知乎",
@@ -426,6 +431,90 @@ const zhCN = {
},
versionUntitled: "未命名版本",
},
media: {
hero: {
eyebrow: "Publisher",
},
plugin: {
title: "浏览器发布器",
ready: "插件已连接",
notInstalled: "插件未连接",
installTitle: "请先安装并打开 GEO Publisher 插件",
installDesc: "绑定和发布都依赖浏览器本地登录态执行。插件会保存独立安装实例身份,后续即使网页关闭,也能继续运行后台检测任务。",
backgroundHint: "插件不会直接复用网页 access token。当前实现会为每个浏览器安装实例注册独立身份,为未来后台收录检测和定时扫描预留能力。",
versionLabel: "插件版本",
installationLabel: "安装实例 ID",
connectedSummary: "本地已登录平台",
registerRequired: "插件安装实例尚未注册,请先重新检测。",
},
actions: {
redetect: "重新检测",
bind: "去绑定",
rebind: "重新绑定",
unbind: "解绑",
},
card: {
bound: "已绑定",
unbound: "未绑定",
unboundHint: "{platform} 还未绑定到当前租户,可以直接发起授权。",
needsAttention: "需要处理",
expiredHint: "该账号状态异常,建议重新绑定以恢复发布能力。",
localMissing: "本地未登录",
localMismatch: "本地账号不匹配",
readyHint: "当前浏览器已检测到正确账号,可以直接用于发布和后续后台检测。",
accountLabel: "SaaS 账号",
uidLabel: "平台 UID",
localLabel: "本地状态",
localConnected: "已检测到登录态",
localDisconnected: "未检测到登录态",
updatedAt: "最近同步",
multiAccount: "已绑定 {count} 个账号",
openPlatform: "打开平台官网",
},
account: {
localMissing: "SaaS 已绑定,但当前浏览器未检测到对应登录态。",
localMismatch: "当前浏览器登录的是另一个账号,请重新授权或切换账号。",
ready: "已就绪",
selectable: "可发布",
unavailable: "不可用",
},
empty: {
accounts: "当前租户还没有绑定可发布账号。",
},
messages: {
bindSuccess: "{platform} 绑定成功",
bindPending: "插件尚未检测到该平台登录态,请先在插件中登录后重试。",
unbindSuccess: "账号解绑成功",
unbindConfirm: "确认解绑这个平台账号吗?解绑后将无法直接发布。",
},
publish: {
title: "发布",
subtitle: "Multi-platform Dispatch",
description: "选择当前浏览器已登录且租户已绑定的平台,插件会逐个平台执行真实发布并回写链接。",
platformsTitle: "发布平台",
platformsHint: "仅展示当前租户已绑定的账号。插件本地登录态异常的平台会自动禁用。",
coverTitle: "封面图",
coverHint: "当前版本先使用封面素材 URL,后续可以接入上传与裁剪。",
coverPlaceholder: "请输入封面素材 URL(可选)",
messages: {
success: "已提交 {count} 个平台发布任务",
partial: "发布执行完成,成功 {success} 个,失败 {failed} 个",
selectPlatform: "请先选择至少一个可发布的平台",
},
},
records: {
tabTitle: "已发布详情",
platform: "已发布平台",
status: "发布状态",
publishedAt: "发布时间",
link: "已发布链接",
open: "打开",
copy: "复制",
empty: "暂无发布记录",
linkEmpty: "当前记录还没有可用链接",
copySuccess: "链接已复制",
},
},
brands: {
eyebrow: "Brand Library",
title: "品牌词库",