@@ -23,6 +23,7 @@ const appState = {
selectedProjectId : "" ,
selectedAssistantId : "" ,
lastSeenAt : SESSION _STORE . getLastSeenAt ( Date . now ( ) ) ,
trackingCursorMap : { } ,
trackingAccounts : [ ] ,
trackingDigest : null ,
reviews : [ ] ,
@@ -66,7 +67,6 @@ const ACTIVE_PLATFORMS = [
{ value : "kuaishou" , label : "快手" } ,
{ value : "wechat_video" , label : "微信视频号" }
] ;
const ACTIVE _PLATFORM _CHIPS = [ "全平台" , "抖音" , "小红书" , "B站" , "快手" , "视频号" ] ;
const makePlatformRoutes = StoryForgePlatformRuntime . makePlatformRoutes ;
const PLATFORM _REGISTRY = {
@@ -79,29 +79,25 @@ const PLATFORM_REGISTRY = {
xiaohongshu : {
label : "小红书" ,
shortLabel : "小红书" ,
workbenchReady : fals e,
pendingText : "小红书工作台当前还没有完整接入,请先停留在导入和通用流程。" ,
workbenchReady : tru e,
routes : makePlatformRoutes ( "xiaohongshu" )
} ,
bilibili : {
label : "哔哩哔哩" ,
shortLabel : "B站" ,
workbenchReady : fals e,
pendingText : "B站工作台当前还没有完整接入, 请先停留在导入和通用流程。" ,
workbenchReady : tru e,
routes : makePlatformRoutes ( "bilibili" )
} ,
kuaishou : {
label : "快手" ,
shortLabel : "快手" ,
workbenchReady : fals e,
pendingText : "快手工作台当前还没有完整接入,请先停留在导入和通用流程。" ,
workbenchReady : tru e,
routes : makePlatformRoutes ( "kuaishou" )
} ,
wechat _video : {
label : "微信视频号" ,
shortLabel : "视频号" ,
workbenchReady : fals e,
pendingText : "微信视频号工作台当前还没有完整接入,请先停留在导入和通用流程。" ,
workbenchReady : tru e,
routes : makePlatformRoutes ( "wechat_video" )
}
} ;
@@ -352,6 +348,71 @@ function setLastSeenAt(value) {
appState . lastSeenAt = SESSION _STORE . setLastSeenAt ( value ) ;
}
function compareDateDesc ( leftValue , rightValue ) {
return new Date ( rightValue || 0 ) . getTime ( ) - new Date ( leftValue || 0 ) . getTime ( ) ;
}
function buildAssistantNameMap ( items ) {
return new Map (
safeArray ( items )
. filter ( ( item ) => item ? . id )
. map ( ( item ) => [ item . id , item . name || "" ] )
) ;
}
function getTrackingCursorForPlatform ( platform = getCurrentPlatformValue ( ) ) {
const normalizedPlatform = normalizePlatformValue ( platform , "" ) ;
return appState . trackingCursorMap ? . [ normalizedPlatform ] || "" ;
}
function getTrackingSinceIso ( platform = getCurrentPlatformValue ( ) ) {
const cursor = getTrackingCursorForPlatform ( platform ) || appState . lastSeenAt || Date . now ( ) ;
const date = new Date ( cursor ) ;
if ( Number . isNaN ( date . getTime ( ) ) ) return new Date ( Date . now ( ) - 86400000 ) . toISOString ( ) ;
return date . toISOString ( ) ;
}
function enrichTrackingAccounts ( items , accountIndex , assistantNameMap , fallbackPlatform ) {
return safeArray ( items )
. map ( ( item ) => {
const account = accountIndex . get ( item . tracked _account _id ) || item . account || null ;
const normalizedPlatform = normalizePlatformValue (
item . platform || account ? . platform || fallbackPlatform || "" ,
fallbackPlatform || "douyin"
) ;
return {
... item ,
platform : normalizedPlatform ,
assistant _name : item . assistant _name || assistantNameMap . get ( item . assistant _id ) || "" ,
account
} ;
} )
. sort ( ( left , right ) => compareDateDesc ( left . updated _at || left . created _at , right . updated _at || right . created _at ) ) ;
}
function enrichTrackingDigestItems ( items , accountIndex , fallbackPlatform ) {
return safeArray ( items )
. map ( ( item ) => {
const account = item . account || accountIndex . get ( item . tracked _account _id ) || null ;
const platform = normalizePlatformValue (
item . platform || item . video ? . platform || account ? . platform || fallbackPlatform || "" ,
fallbackPlatform || "douyin"
) ;
const performanceScore = Number ( item . video ? . score ? . performance _score || 0 ) ;
return {
... item ,
platform ,
account ,
summary : item . summary || item . summary _text || item . video ? . description || item . video ? . title || "已发现更新内容" ,
is _high _value : typeof item . is _high _value === "boolean" ? item . is _high _value : performanceScore >= 60
} ;
} )
. sort ( ( left , right ) => compareDateDesc (
left . video ? . published _at || left . created _at ,
right . video ? . published _at || right . created _at
) ) ;
}
function markSeenNow ( ) {
setLastSeenAt ( Date . now ( ) ) ;
}
@@ -1348,12 +1409,6 @@ async function loadPlatformAccount(platform, accountId, requestToken = 0) {
}
}
function getTrackingSinceIso ( ) {
const date = new Date ( appState . lastSeenAt || Date . now ( ) ) ;
if ( Number . isNaN ( date . getTime ( ) ) ) return new Date ( Date . now ( ) - 86400000 ) . toISOString ( ) ;
return date . toISOString ( ) ;
}
async function bootstrap ( ) {
renderAll ( ) ;
if ( ! appState . session ) {
@@ -1367,17 +1422,19 @@ async function bootstrap() {
appState . dashboard = null ;
appState . accounts = [ ] ;
appState . contentSources = [ ] ;
appState . trackingAccounts = [ ] ;
appState . trackingDigest = null ;
appState . trackingCursorMap = { } ;
appState . documents = [ ] ;
renderAll ( ) ;
return ;
}
appState . backendCapabilities = await loadBackendCapabilities ( appState . session . backendUrl ) . catch ( ( ) => null ) ;
const preferredPlatform = getPreferredPlatform ( ) ;
const dashboard = await storyforgeFetch ( "/v2/me/dashboard" ) ;
appState . dashboard = dashboard ;
const runtimePlatforms = getRuntimePlatformValues ( ) ;
const preferredPlatform = getCurrentPlatformValue ( ) ;
setCurrentPlatform ( preferredPlatform ) ;
const accountListPath = getWorkbenchRoute ( preferredPlatform , "accounts" ) ;
const trackingAccountsPath = getWorkbenchRoute ( preferredPlatform , "trackingAccounts" ) ;
const trackingDigestPath = getWorkbenchRoute ( preferredPlatform , "trackingDigest" ) ;
const supportsTrackingDigest = trackingDigestPath && backendSupports ( trackingDigestPath ) ;
const supportsReviews = backendSupports ( "/v2/reviews" ) ;
const supportsIntegrationHealth = backendSupports ( "/v2/integrations/health" ) ;
const supportsLocalModels = backendSupports ( "/v2/integrations/local-models" ) ;
@@ -1385,11 +1442,43 @@ async function bootstrap() {
const supportsLiveRecorderSources = backendSupports ( "/v2/live-recorder/sources" ) ;
const supportsLiveRecorderStatus = backendSupports ( "/v2/live-recorder/status" ) ;
const supportsLiveRecorderFiles = backendSupports ( "/v2/live-recorder/files" ) ;
const [ dashboard , contentSources, accounts , trackingAccounts Payload, reviews , integrationHealth , localModelCatalog , liveRecorderSourcesPayload , liveRecorderStatus , liveRecorderFilesPayload ] = await Promise . all ( [
storyforgeFetch ( "/v2/me/dashboard" ) ,
const [ contentSources , platform Payloads , reviews , integrationHealth , localModelCatalog , liveRecorderSourcesPayload , liveRecorderStatus , liveRecorderFilesPayload ] = await Promise . all ( [
storyforgeFetch ( "/v2/content-sources" ) . catch ( ( ) => [ ] ) ,
accountListPath ? storyforgeFetch ( accountListPath ) . catch ( ( ) => [ ] ) : Promise . resolve ( [ ] ) ,
trackingA ccounts Path ? storyforgeFetch ( trackingAccountsPath ) . catch ( ( ) => ( { items : [ ] , cursor _last _seen _at : "" } ) ) : Promise . resolve ( { items : [ ] , cursor _last _seen _at : "" } ) ,
Promise . all ( runtimePlatforms . map ( async ( platform ) => {
const a ccountList Path = getWorkbenchRoute ( platform , "accounts" ) ;
const trackingAccountsPath = getWorkbenchRoute ( platform , "trackingAccounts" ) ;
const trackingDigestPath = getWorkbenchRoute ( platform , "trackingDigest" ) ;
const supportsAccounts = accountListPath && backendSupports ( accountListPath ) ;
const supportsTrackingAccounts = trackingAccountsPath && backendSupports ( trackingAccountsPath ) ;
const supportsTrackingDigest = trackingDigestPath && backendSupports ( trackingDigestPath ) ;
const accounts = supportsAccounts
? await storyforgeFetch ( accountListPath ) . catch ( ( ) => [ ] )
: [ ] ;
const trackingAccountsPayload = supportsTrackingAccounts
? await storyforgeFetch ( trackingAccountsPath ) . catch ( ( ) => ( { items : [ ] , cursor _last _seen _at : "" } ) )
: { items : [ ] , cursor _last _seen _at : "" } ;
const trackingCursorLastSeenAt = trackingAccountsPayload ? . cursor _last _seen _at || "" ;
const trackingDigest = supportsTrackingDigest
? await storyforgeFetch ( ` ${ trackingDigestPath } ?since= ${ encodeURIComponent ( trackingCursorLastSeenAt || getTrackingSinceIso ( platform ) ) } &limit=24 ` ) . catch ( ( ) => ( {
items : [ ] ,
tracked _accounts : [ ] ,
cursor _last _seen _at : trackingCursorLastSeenAt
} ) )
: {
items : [ ] ,
tracked _accounts : [ ] ,
cursor _last _seen _at : trackingCursorLastSeenAt
} ;
return {
platform ,
accounts : safeArray ( accounts ) . map ( ( item ) => ( {
... item ,
platform : normalizePlatformValue ( item ? . platform || platform , platform )
} ) ) ,
trackingAccountsPayload ,
trackingDigest
} ;
} ) ) ,
supportsReviews ? storyforgeFetch ( "/v2/reviews" ) . catch ( ( ) => [ ] ) : Promise . resolve ( [ ] ) ,
supportsIntegrationHealth ? storyforgeFetch ( "/v2/integrations/health" ) . catch ( ( ) => null ) : Promise . resolve ( null ) ,
supportsLocalModels ? storyforgeFetch ( "/v2/integrations/local-models" ) . catch ( ( ) => null ) : Promise . resolve ( null ) ,
@@ -1397,27 +1486,45 @@ async function bootstrap() {
supportsLiveRecorderStatus ? storyforgeFetch ( "/v2/live-recorder/status" ) . catch ( ( ) => null ) : Promise . resolve ( null ) ,
supportsLiveRecorderFiles ? storyforgeFetch ( "/v2/live-recorder/files?limit=16" ) . catch ( ( ) => ( { items : [ ] } ) ) : Promise . resolve ( { items : [ ] } )
] ) ;
const trackingCursorLastSeenAt = trackingAccountsPayload ? . cursor _last _seen _at || "" ;
if ( trackingCursorLastSeenAt ) {
setLastSeenAt ( trackingCursorLastSeenAt ) ;
const mergedAccounts = safeArray ( platformPayloads )
. flatMap ( ( entry ) => safeArray ( entry . accounts ) )
. sort ( ( a , b ) => {
const platformCompare = platformLabel ( getAccountPlatform ( a ) ) . localeCompare ( platformLabel ( getAccountPlatform ( b ) ) , "zh-Hans-CN" ) ;
if ( platformCompare !== 0 ) return platformCompare ;
return getAccountName ( a ) . localeCompare ( getAccountName ( b ) , "zh-Hans-CN" ) ;
} ) ;
const accountIndex = new Map ( mergedAccounts . map ( ( item ) => [ item . id , item ] ) ) ;
const assistantNameMap = buildAssistantNameMap ( dashboard . assistants ) ;
const trackingCursorMap = Object . fromEntries (
safeArray ( platformPayloads )
. map ( ( entry ) => [ entry . platform , entry . trackingAccountsPayload ? . cursor _last _seen _at || "" ] )
. filter ( ( [ , value ] ) => value )
) ;
const currentCursor = trackingCursorMap [ preferredPlatform ] || pickLatestIso ( Object . values ( trackingCursorMap ) ) || "" ;
if ( currentCursor ) {
setLastSeenAt ( currentCursor ) ;
}
const trackingSince = trackingCursorLastSeenAt || getTrackingSinceIso ( ) ;
const trackingDigest = trackingDigestPath
? await storyforgeFetch ( ` ${ trackingDigestPath } ?since= ${ encodeURIComponent ( trackingSince ) } &limit=24 ` ) . catch ( ( ) => ( {
items : [ ] ,
tracked _accounts : [ ] ,
cursor _last _seen _at : trackingCursorLastSeenAt
} ) )
: ( {
items : [ ] ,
tracked _accounts : [ ] ,
cursor _last _seen _at : trackingCursorLastSeenAt
} ) ;
appState . dashboard = dashboard ;
appState . contentSources = safeArray ( contentSources ) ;
appState . accounts = safeArray ( a ccounts) ;
appState . trackingAccounts = safeArray ( trackingAccountsPayload . items || trackingAccountsPayload ) ;
appState . trackingDigest = trackingDigest ;
appState . accounts = mergedA ccounts;
appState . trackingCursorMap = trackingCursorMap ;
appState . trackingAccounts = safeArray ( platformPayloads ) . flatMap ( ( entry ) =>
enrichTrackingAccounts (
entry . trackingAccountsPayload ? . items || entry . trackingAccountsPayload ,
accountIndex ,
assistantNameMap ,
entry . platform
)
) ;
appState . trackingDigest = {
cursor _last _seen _at : currentCursor ,
items : safeArray ( platformPayloads ) . flatMap ( ( entry ) => enrichTrackingDigestItems ( entry . trackingDigest ? . items , accountIndex , entry . platform ) ) ,
tracked _accounts : safeArray ( platformPayloads ) . flatMap ( ( entry ) =>
safeArray ( entry . trackingDigest ? . tracked _accounts ) . map ( ( item ) => ( {
... item ,
platform : normalizePlatformValue ( item . platform || entry . platform , entry . platform )
} ) )
)
} ;
appState . reviews = safeArray ( reviews ) ;
appState . liveRecorderSources = safeArray ( liveRecorderSourcesPayload ? . items || liveRecorderSourcesPayload ) ;
appState . liveRecorderStatus = liveRecorderStatus ;
@@ -1439,8 +1546,9 @@ async function bootstrap() {
}
const selectedAssistantExists = safeArray ( dashboard . assistants ) . some ( ( item ) => item . id === appState . selectedAssistantId ) ;
appState . selectedAssistantId = selectedAssistantExists ? appState . selectedAssistantId : ( dashboard . assistants ? . [ 0 ] ? . id || "" ) ;
const selected AccountExist s = appState . accounts . some ( ( item ) => ite m. id === appState . selectedAccountId ) ;
const nextAccountId = selectedAccountExists ? appState . selectedAccountI d : appState . accounts [ 0 ] ? . id || "" ;
const platform Accounts = getAccountsForPlatfor m( preferredPlatform ) ;
const selectedAccountExists = platformAccounts . some ( ( item ) => item . i d === appState . selectedAccountId ) ;
const nextAccountId = selectedAccountExists ? appState . selectedAccountId : platformAccounts [ 0 ] ? . id || appState . accounts [ 0 ] ? . id || "" ;
if ( nextAccountId ) {
const nextAccount = appState . accounts . find ( ( item ) => item . id === nextAccountId ) || null ;
await loadPlatformAccount ( getAccountPlatform ( nextAccount ) , nextAccountId ) ;
@@ -1461,7 +1569,7 @@ async function bootstrap() {
}
async function markTrackingDigestRead ( ) {
const platform = getPrefe rred Platform ( ) ;
const platform = getCu rrent PlatformValue ( ) ;
const trackingCursorPath = getWorkbenchRoute ( platform , "trackingCursor" ) ;
if ( ! trackingCursorPath || ! backendSupports ( trackingCursorPath ) ) {
rememberAction ( "当前后端暂不支持" , "这套 live collector 还没有接入跟踪已读游标。" , "orange" ) ;
@@ -1473,11 +1581,24 @@ async function markTrackingDigestRead() {
method : "POST" ,
body : { last _seen _at : nextSeenAt }
} ) ;
appState . trackingCursorMap = {
... ( appState . trackingCursorMap || { } ) ,
[ platform ] : nextSeenAt
} ;
if ( appState . trackingDigest ) {
appState . trackingDigest = {
... appState . trackingDigest ,
items : safeArray ( appState . trackingDigest . items ) . filter ( ( item ) => item . platform !== platform ) ,
cursor _last _seen _at : platform === getCurrentPlatformValue ( )
? nextSeenAt
: ( appState . trackingDigest . cursor _last _seen _at || "" )
} ;
}
setLastSeenAt ( nextSeenAt ) ;
}
async function refreshTrackingAccountsAction ( ) {
const platform = getPrefe rred Platform ( ) ;
const platform = getCu rrent PlatformValue ( ) ;
const trackingRefreshPath = getWorkbenchRoute ( platform , "trackingRefresh" ) ;
if ( ! trackingRefreshPath || ! backendSupports ( trackingRefreshPath ) ) {
rememberAction ( "当前后端暂不支持" , "这套 live collector 还没有接入批量跟踪同步。" , "orange" ) ;
@@ -1505,7 +1626,8 @@ async function refreshTrackedAccountAction(trackedAccountId) {
if ( ! trackedAccountId ) {
throw new Error ( "trackedAccountId is required" ) ;
}
const platfor m = getPreferredPlatform ( ) ;
const trackedIte m = getTrackingAccounts ( ) . find ( ( item ) => item . tracked _account _id === trackedAccountId ) ;
const platform = trackedItem ? . platform || getCurrentPlatformValue ( ) ;
const trackingRefreshPath = getWorkbenchRoute ( platform , "trackingAccountRefresh" , trackedAccountId ) ;
if ( ! trackingRefreshPath || ! backendSupports ( ` /v2/ ${ platform } /tracking/accounts/{tracked_account_id}/refresh ` ) ) {
rememberAction ( "当前后端暂不支持" , "这套 live collector 还没有接入单账号跟踪同步。" , "orange" ) ;
@@ -1642,12 +1764,102 @@ function getCurrentProjectSourcesForAccount(account, projectId) {
return getContentSourcesForAccount ( account ) . filter ( ( source ) => source . project _id === projectId ) ;
}
function getCurrentPlatformValue ( ) {
const available = getRuntimePlatformValues ( ) ;
const fallback = available [ 0 ] || "douyin" ;
const current = normalizePlatformValue ( appState . currentPlatform , "" ) ;
if ( current && available . includes ( current ) ) return current ;
return normalizePlatformValue ( getPreferredPlatform ( ) , fallback ) ;
}
function getAccountsForPlatform ( platform ) {
const normalizedPlatform = normalizePlatformValue ( platform , getCurrentPlatformValue ( ) ) ;
return safeArray ( appState . accounts ) . filter ( ( item ) => getAccountPlatform ( item ) === normalizedPlatform ) ;
}
function parseIsoTime ( value ) {
const text = String ( value || "" ) . trim ( ) ;
if ( ! text ) return 0 ;
const timestamp = new Date ( text ) . getTime ( ) ;
return Number . isNaN ( timestamp ) ? 0 : timestamp ;
}
function sortItemsByIsoDesc ( items , fieldName ) {
return items
. slice ( )
. sort ( ( left , right ) => parseIsoTime ( right ? . [ fieldName ] ) - parseIsoTime ( left ? . [ fieldName ] ) ) ;
}
function pickLatestIso ( values ) {
return values
. map ( ( value ) => String ( value || "" ) . trim ( ) )
. filter ( Boolean )
. sort ( ( left , right ) => parseIsoTime ( right ) - parseIsoTime ( left ) ) [ 0 ] || "" ;
}
function createEmptyTrackingDigest ( cursorLastSeenAt = "" ) {
return {
items : [ ] ,
tracked _accounts : [ ] ,
cursor _last _seen _at : cursorLastSeenAt
} ;
}
function getAssistantById ( assistantId ) {
if ( ! assistantId ) return null ;
return safeArray ( appState . dashboard ? . assistants ) . find ( ( item ) => item . id === assistantId ) || null ;
}
function getTrackedAccountDisplay ( item ) {
const account = item ? . account
|| safeArray ( appState . accounts ) . find ( ( row ) => row . id === item ? . tracked _account _id )
|| null ;
const assistant = getAssistantById ( item ? . assistant _id || "" ) ;
const platform = normalizePlatformValue ( item ? . platform || account ? . platform || getCurrentPlatformValue ( ) , getCurrentPlatformValue ( ) ) ;
return {
... item ,
account ,
platform ,
assistant _name : item ? . assistant _name || assistant ? . name || "" ,
note : item ? . note || ""
} ;
}
function isTrackedAccount ( accountId ) {
return safeArray ( appState . trackingAccounts ) . some ( ( item ) => item . tracked _account _id === accountId ) ;
}
function getTrackingDigestItems ( limit = 6 ) {
return safeArray ( appState . trackingDigest ? . items ) . slice ( 0 , limit ) ;
function getTrackingAccounts ( ) {
return sortItemsByIsoDesc (
safeArray ( appState . trackingAccounts ) . map ( ( item ) => getTrackedAccountDisplay ( item ) ) ,
"updated_at"
) ;
}
function getTrackingDigestItems ( limit = 6 , options = { } ) {
const targetPlatform = normalizePlatformValue ( options . platform || "" , "" ) ;
const fallbackPlatform = targetPlatform || getCurrentPlatformValue ( ) ;
return safeArray ( appState . trackingDigest ? . items )
. map ( ( item ) => {
const tracked = getTrackedAccountDisplay ( item ) ;
const summary = item ? . summary || item ? . summary _text || item ? . video ? . description || item ? . video ? . title || item ? . description || "" ;
const video = item ? . video || { } ;
const isHighValue = item ? . is _high _value != null
? Boolean ( item . is _high _value )
: Number ( video ? . score ? . performance _score || 0 ) >= 60 ;
return {
... item ,
account : item ? . account || tracked . account ,
platform : tracked . platform || fallbackPlatform ,
assistant _name : item ? . assistant _name || tracked . assistant _name || "" ,
summary ,
borrowing _points : safeArray ( item ? . borrowing _points ) ,
is _high _value : isHighValue
} ;
} )
. filter ( ( item ) => ! targetPlatform || item . platform === targetPlatform )
. sort ( ( left , right ) => parseIsoTime ( right ? . created _at || right ? . video ? . published _at ) - parseIsoTime ( left ? . created _at || left ? . video ? . published _at ) )
. slice ( 0 , limit ) ;
}
function getSelectedAccount ( ) {
@@ -2655,6 +2867,18 @@ function renderEmptyState(title, description) {
return ` <div class="panel pad"><div class="empty-state"><strong> ${ escapeHtml ( title ) } </strong><p> ${ escapeHtml ( description ) } </p></div></div> ` ;
}
function renderPlatformSwitchChips ( currentPlatform ) {
return getPlatformOptions ( ) . map ( ( item ) => `
<span
class="chip clickable-tag ${ item . value === currentPlatform ? "active" : "" } "
data-action="select-platform"
data-platform=" ${ escapeHtml ( item . value ) } "
>
${ escapeHtml ( getPlatformShortLabel ( item . value ) ) }
</span>
` ) . join ( "" ) ;
}
function renderDashboardScreen ( ) {
if ( ! appState . session ) {
return screenShell (
@@ -2677,12 +2901,12 @@ function renderDashboardScreen() {
const jobs = safeArray ( dashboard . recent _jobs ) ;
const assistants = safeArray ( dashboard . assistants ) ;
const accounts = safeArray ( appState . accounts ) ;
const trackedAccounts = safeArray ( appState . t rackingAccounts) ;
const trackedAccounts = getT rackingAccounts( );
const digestItems = getTrackingDigestItems ( 3 ) ;
const actions = [ ] ;
if ( ! projects . length ) actions . push ( "先新建一个项目" ) ;
if ( ! assistants . length ) actions . push ( "先创建第一个 Agent" ) ;
if ( ! accounts . length ) actions . push ( "先导入一个抖音 主页或作品" ) ;
if ( ! accounts . length ) actions . push ( "先导入一个平台 主页或作品" ) ;
if ( ! trackedAccounts . length && accounts . length ) actions . push ( "挑 1 个重点账号加入跟踪" ) ;
if ( jobs . some ( ( item ) => item . status !== "completed" ) ) actions . push ( "处理进行中的生产任务" ) ;
if ( ! actions . length ) actions . push ( "继续补高分对标并安排生产" ) ;
@@ -2771,7 +2995,7 @@ function renderDashboardScreen() {
<h4> ${ escapeHtml ( item . account ? . nickname || "未命名账号" ) } · ${ escapeHtml ( item . video ? . title || item . video ? . description || "最新作品" ) } </h4>
<p> ${ escapeHtml ( item . summary || ` 最近发布时间 ${ formatDateTime ( item . video ? . published _at ) } ,适合继续交给 Agent 做借鉴点标注。 ` ) } </p>
<div class="task-meta">
<span class="tag">抖音 </span>
<span class="tag">${ escapeHtml ( getPlatformShortLabel ( item . platform || item . account ? . platform || getCurrentPlatformValue ( ) ) ) } </span>
<span class="tag green"> ${ escapeHtml ( item . is _high _value ? "高价值" : "可学习" ) } </span>
${ item . assistant _name ? ` <span class="tag"> ${ escapeHtml ( item . assistant _name ) } </span> ` : "" }
</div>
@@ -2864,7 +3088,9 @@ function renderDiscoveryScreen() {
return screenShell("找对标", "连接后端后才能加载真实对标账号。", ` $ { button ( "连接后端" , "open-auth" , "primary" ) } ` , renderEmptyState("对标库未加载", "登录后这里会显示当前平台的账号列表和详情。"));
}
const query = appState.discoveryQuery.toLowerCase();
const accounts = safeArray(appState.accounts).filter((account) => {
const currentPlatform = getCurrentPlatformValue();
const currentPlatformLabel = getPlatformShortLabel(currentPlatform);
const accounts = getAccountsForPlatform(currentPlatform).filter((account) => {
if (!query) return true;
return [getAccountName(account), account.signature, getAccountProfileUrl(account), getAccountHandle(account), ...safeArray(account.tags), ...safeArray(account.keywords)]
.join(" ")
@@ -2872,9 +3098,9 @@ function renderDiscoveryScreen() {
.includes(query);
});
const selected = getSelectedAccount();
const current Platform = getAccountPlatform(selected) || getPreferredPlatform() ;
const currentPlatformLabel = getPlatformShortLabel( currentPlatform) ;
const workbenchReason = !isWorkbenchPlatform(current Platform) ? getPendingWorkbenchReason(current Platform) : "";
const selected Platform = getAccountPlatform(selected);
const effectivePlatform = selectedPlatform || currentPlatform;
const workbenchReason = !isWorkbenchPlatform(effective Platform) ? getPendingWorkbenchReason(effective Platform) : "";
const reports = safeArray(appState.selectedWorkspace?.recent_reports);
const linkedAccounts = safeArray(appState.selectedWorkspace?.linked_accounts);
const videos = safeArray(appState.selectedVideos?.items);
@@ -2909,10 +3135,7 @@ function renderDiscoveryScreen() {
< / d i v >
< div class = "side-stack" >
< div class = "chip-row" >
< span class = "chip active" > 真实接口 < / s p a n >
< span class = "chip" > 工作台详情 < / s p a n >
< span class = "chip" > 高分作品 < / s p a n >
< span class = "chip" > 绑定关系 < / s p a n >
$ { renderPlatformSwitchChips ( currentPlatform ) }
< / d i v >
< / d i v >
< / d i v >
@@ -3105,7 +3328,7 @@ function renderTrackingScreen() {
if (!appState.dashboard) {
return screenShell("跟踪账号", "登录后才能生成真实日报。", ` $ { button ( "连接后端" , "open-auth" , "primary" ) } ` , renderEmptyState("日报未加载", "当前还没有可用的对标账号数据。"));
}
const currentPlatform = getPrefe rred Platform();
const currentPlatform = getCu rrent PlatformValue ();
const trackingAccountsPath = getWorkbenchRoute(currentPlatform, "trackingAccounts");
if (!trackingAccountsPath || !backendSupports(trackingAccountsPath)) {
return screenShell(
@@ -3115,9 +3338,10 @@ function renderTrackingScreen() {
renderEmptyState("跟踪能力暂未接入", ` 这套后端还没有接入 $ { platformLabel ( currentPlatform ) } 跟踪接口 , 等 live collector 同步后这里会自动切成真实日报 。 ` )
);
}
const trackedAccounts = safeArray(appState.trackingAccounts );
const digestItems = getTrackingDigestItems(12);
const cursorLabel = appState.lastSeenAt ? formatDateTime( appState.lastSeenAt) : "尚未记录" ;
const trackedAccounts = getTrackingAccounts().filter((item) => item.platform === currentPlatform );
const digestItems = getTrackingDigestItems(12, { platform: currentPlatform } );
const platformCursor = getTrackingCursorForPlatform(currentPlatform) || appState.lastSeenAt;
const cursorLabel = platformCursor ? formatDateTime(platformCursor) : "尚未记录";
return screenShell(
"跟踪账号",
` 这里已经接上真实$ { getPlatformShortLabel ( currentPlatform ) } 跟踪对象和按上次打开后的更新日报 。 ` ,
@@ -3125,11 +3349,9 @@ function renderTrackingScreen() {
`
< div class = "hero-card" >
< h3 > 日报逻辑 < / h 3 >
< p > 按上次打开后汇总 。 上次打开距今 $ { escapeHtml ( daysSince ( appState . lastSeenAt ) ) } 天 , 本次优先展示有更新且值得借鉴的内容 。 < / p >
< p > 按上次打开后汇总 。 上次打开距今 $ { escapeHtml ( daysSince ( platformCursor ) ) } 天 , 本次优先展示有更新且值得借鉴的内容 。 < / p >
< div class = "chip-row" style = "margin-top:14px;" >
< span class = "chip active" > 按上次打开汇总 < / s p a n >
< span class = "chip" > Agent 标借鉴点 < / s p a n >
< span class = "chip" > 高价值内容可进学习集 < / s p a n >
$ { renderPlatformSwitchChips ( currentPlatform ) }
< span class = "chip" > 上次已读 $ { escapeHtml ( cursorLabel ) } < / s p a n >
< / d i v >
< / d i v >
@@ -3140,7 +3362,7 @@ function renderTrackingScreen() {
< div class = "mobile-only compact-summary-row" >
< span class = "tag blue" > 跟踪 $ { escapeHtml ( formatNumber ( trackedAccounts . length ) ) } < / s p a n >
< span class = "tag green" > 日报 $ { escapeHtml ( formatNumber ( digestItems . length ) ) } < / s p a n >
< span class = "tag" > $ { escapeHtml ( daysSince ( appState . lastSeenAt ) ) } 天窗口 < / s p a n >
< span class = "tag" > $ { escapeHtml ( daysSince ( platformCursor ) ) } 天窗口 < / s p a n >
< / d i v >
< div class = "list" >
$ { trackedAccounts . map ( ( item ) => `
@@ -3167,7 +3389,7 @@ function renderTrackingScreen() {
<h4> ${ escapeHtml ( item . account ? . nickname || "账号" ) } · ${ escapeHtml ( item . video ? . title || item . video ? . description || "最新作品" ) } </h4>
<p> ${ escapeHtml ( item . summary || ` 发布时间 ${ formatDateTime ( item . video ? . published _at ) } ,建议继续判断借鉴点。 ` ) } </p>
<div class="task-meta">
<span class="tag">抖音 </span>
<span class="tag">${ escapeHtml ( getPlatformShortLabel ( item . platform || currentPlatform ) ) } </span>
<span class="tag green"> ${ escapeHtml ( item . is _high _value ? "高价值" : "可学习" ) } </span>
${ item . assistant _name ? ` <span class="tag"> ${ escapeHtml ( item . assistant _name ) } </span> ` : "" }
${ item . video ? . share _url ? ` <a class="tag" href=" ${ escapeHtml ( item . video . share _url ) } " target="_blank" rel="noreferrer">打开作品</a> ` : "" }
@@ -3618,7 +3840,15 @@ function renderTopbar() {
topPills[2].textContent = ` 任务 $ { formatNumber ( appState . dashboard ? . recent _jobs ? . length || 0 ) } ` ;
}
if (platforms) {
platforms.innerHTML = getPlatformChips().map((label, index) => ` < span class = "chip ${index === 0 ? " active " : " "}" > $ { escapeHtml ( label ) } < / s p a n > ` ) . j o i n ( " " ) ;
const currentPlatform = getCurrentPlatformValue();
platforms.innerHTML = [
` < span class = "chip" > 已接入平台 < / s p a n > ` ,
... getPlatformOptions ( ) . map ( ( item ) => `
<span class="chip clickable-tag ${ item . value === currentPlatform ? "active" : "" } " data-action="select-platform" data-platform=" ${ escapeHtml ( item . value ) } ">
${ escapeHtml ( getPlatformShortLabel ( item . value ) ) }
</span>
` )
] . join ( "" ) ;
}
}
@@ -5652,6 +5882,21 @@ document.addEventListener("click", async (event) => {
renderAll ( ) ;
return ;
}
if ( name === "select-platform" ) {
const nextPlatform = normalizePlatformValue ( action . dataset . platform || "" , "" ) ;
if ( ! nextPlatform || nextPlatform === getCurrentPlatformValue ( ) ) {
renderAll ( ) ;
return ;
}
setCurrentPlatform ( nextPlatform ) ;
setBusy ( true , ` 正在切换到 ${ platformLabel ( nextPlatform ) } ... ` ) ;
try {
await bootstrap ( ) ;
} finally {
setBusy ( false , "" ) ;
}
return ;
}
if ( name === "select-account" ) {
const accountId = action . dataset . accountId ;
if ( ! accountId ) return ;