fix wooden fish author and title display

This commit is contained in:
kdletters
2026-05-28 14:31:13 +08:00
parent 41568099c4
commit c8b36cf799
10 changed files with 176 additions and 22 deletions

View File

@@ -17,6 +17,7 @@ export type AuthUser = {
export type PublicUserSummary = { export type PublicUserSummary = {
id: string; id: string;
publicUserCode: string; publicUserCode: string;
username: string;
displayName: string; displayName: string;
avatarUrl: string | null; avatarUrl: string | null;
}; };

View File

@@ -20,6 +20,7 @@ pub fn map_public_user_summary_payload(user: AuthUser) -> PublicUserSummaryPaylo
PublicUserSummaryPayload { PublicUserSummaryPayload {
id: user.id, id: user.id,
public_user_code: user.public_user_code, public_user_code: user.public_user_code,
username: user.username,
display_name: user.display_name, display_name: user.display_name,
avatar_url: user.avatar_url, avatar_url: user.avatar_url,
} }

View File

@@ -67,6 +67,7 @@ const DEFAULT_HIT_OBJECT_REFERENCE_BYTES: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/../../../public/wooden-fish/default-hit-object.png" "/../../../public/wooden-fish/default-hit-object.png"
)); ));
const WOODEN_FISH_AUTHOR_FALLBACK_DISPLAY_NAME: &str = "玩家";
pub async fn create_wooden_fish_session( pub async fn create_wooden_fish_session(
State(state): State<AppState>, State(state): State<AppState>,
@@ -80,7 +81,7 @@ pub async fn create_wooden_fish_session(
let owner_user_id = authenticated.claims().user_id().to_string(); let owner_user_id = authenticated.claims().user_id().to_string();
let session_id = build_prefixed_uuid_id("wooden-fish-session-"); let session_id = build_prefixed_uuid_id("wooden-fish-session-");
let now = current_utc_micros(); let now = current_utc_micros();
let draft = build_wooden_fish_draft(&payload); let draft = build_wooden_fish_draft(&payload, &state).await?;
let session = WoodenFishSessionSnapshotResponse { let session = WoodenFishSessionSnapshotResponse {
session_id, session_id,
owner_user_id, owner_user_id,
@@ -145,6 +146,7 @@ pub async fn execute_wooden_fish_action(
let Json(mut payload) = let Json(mut payload) =
wooden_fish_json(payload, &request_context, WOODEN_FISH_CREATION_PROVIDER)?; wooden_fish_json(payload, &request_context, WOODEN_FISH_CREATION_PROVIDER)?;
let owner_user_id = authenticated.claims().user_id().to_string(); let owner_user_id = authenticated.claims().user_id().to_string();
let author_display_name = resolve_author_display_name(&state, &authenticated);
maybe_generate_hit_object_asset( maybe_generate_hit_object_asset(
&state, &state,
&request_context, &request_context,
@@ -156,7 +158,7 @@ pub async fn execute_wooden_fish_action(
maybe_generate_hit_sound_asset(&mut payload); maybe_generate_hit_sound_asset(&mut payload);
let response = state let response = state
.spacetime_client() .spacetime_client()
.execute_wooden_fish_action(session_id, owner_user_id, payload) .execute_wooden_fish_action(session_id, owner_user_id, author_display_name, payload)
.await .await
.map_err(|error| { .map_err(|error| {
wooden_fish_error_response( wooden_fish_error_response(
@@ -366,12 +368,20 @@ pub async fn get_wooden_fish_gallery_detail(
)) ))
} }
fn build_wooden_fish_draft(payload: &WoodenFishWorkspaceCreateRequest) -> WoodenFishDraftResponse { async fn build_wooden_fish_draft(
WoodenFishDraftResponse { payload: &WoodenFishWorkspaceCreateRequest,
state: &AppState,
) -> Result<WoodenFishDraftResponse, Response> {
Ok(WoodenFishDraftResponse {
template_id: WOODEN_FISH_TEMPLATE_ID.to_string(), template_id: WOODEN_FISH_TEMPLATE_ID.to_string(),
template_name: WOODEN_FISH_TEMPLATE_NAME.to_string(), template_name: WOODEN_FISH_TEMPLATE_NAME.to_string(),
profile_id: None, profile_id: None,
work_title: payload.work_title.trim().to_string(), work_title: resolve_wooden_fish_work_title(
state,
&payload.work_description,
&payload.hit_object_prompt,
)
.await?,
work_description: payload.work_description.trim().to_string(), work_description: payload.work_description.trim().to_string(),
theme_tags: normalize_tags(payload.theme_tags.clone()), theme_tags: normalize_tags(payload.theme_tags.clone()),
hit_object_prompt: clean_string(&payload.hit_object_prompt, DEFAULT_HIT_OBJECT_PROMPT), hit_object_prompt: clean_string(&payload.hit_object_prompt, DEFAULT_HIT_OBJECT_PROMPT),
@@ -391,14 +401,13 @@ fn build_wooden_fish_draft(payload: &WoodenFishWorkspaceCreateRequest) -> Wooden
.or_else(|| Some(default_wooden_fish_hit_sound_asset())), .or_else(|| Some(default_wooden_fish_hit_sound_asset())),
cover_image_src: None, cover_image_src: None,
generation_status: WoodenFishGenerationStatus::Draft, generation_status: WoodenFishGenerationStatus::Draft,
} })
} }
fn validate_workspace_request( fn validate_workspace_request(
request_context: &RequestContext, request_context: &RequestContext,
payload: &WoodenFishWorkspaceCreateRequest, payload: &WoodenFishWorkspaceCreateRequest,
) -> Result<(), Response> { ) -> Result<(), Response> {
ensure_non_empty(request_context, &payload.work_title, "workTitle")?;
if payload.template_id.trim() != WOODEN_FISH_TEMPLATE_ID { if payload.template_id.trim() != WOODEN_FISH_TEMPLATE_ID {
return Err(wooden_fish_error_response( return Err(wooden_fish_error_response(
request_context, request_context,
@@ -412,6 +421,77 @@ fn validate_workspace_request(
Ok(()) Ok(())
} }
fn resolve_author_display_name(
state: &AppState,
authenticated: &AuthenticatedAccessToken,
) -> String {
state
.auth_user_service()
.get_user_by_id(authenticated.claims().user_id())
.ok()
.flatten()
.map(|user| user.display_name)
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| WOODEN_FISH_AUTHOR_FALLBACK_DISPLAY_NAME.to_string())
}
async fn resolve_wooden_fish_work_title(
state: &AppState,
work_description: &str,
hit_object_prompt: &str,
) -> Result<String, Response> {
let description = clean_string(work_description, hit_object_prompt);
if description.is_empty() {
return Ok(WOODEN_FISH_TEMPLATE_NAME.to_string());
}
let Some(llm_client) = state.llm_client() else {
return Ok(WOODEN_FISH_TEMPLATE_NAME.to_string());
};
let request = platform_llm::LlmTextRequest::new(vec![
platform_llm::LlmMessage::system(
"你是中文作品标题编辑。请根据敲木鱼作品描述生成一个适合卡片展示的简短中文标题,只输出纯文本,不要 JSON、标点解释或引号。",
),
platform_llm::LlmMessage::user(format!(
"作品描述:{description}\n\n请生成 2 到 8 个中文字符为主的标题。"
)),
])
.with_model(crate::llm_model_routing::CREATION_TEMPLATE_LLM_MODEL)
.with_responses_api();
let response = llm_client.request_text(request).await;
match response {
Ok(response) => {
let title = normalize_wooden_fish_generated_work_title(response.content.as_str());
if title.is_empty() {
Ok(WOODEN_FISH_TEMPLATE_NAME.to_string())
} else {
Ok(title)
}
}
Err(_) => Ok(WOODEN_FISH_TEMPLATE_NAME.to_string()),
}
}
fn normalize_wooden_fish_generated_work_title(value: &str) -> String {
let normalized = value
.trim()
.trim_matches(|ch: char| {
ch.is_ascii_punctuation()
|| matches!(
ch,
'' | '。' | '、' | '' | '' | '' | '' | '“' | '”' | '《' | '》'
)
})
.chars()
.filter(|ch| !ch.is_control())
.collect::<String>();
let chars = normalized.chars().collect::<Vec<_>>();
if chars.len() <= 8 {
normalized
} else {
chars.into_iter().take(8).collect()
}
}
async fn maybe_generate_hit_object_asset( async fn maybe_generate_hit_object_asset(
state: &AppState, state: &AppState,
request_context: &RequestContext, request_context: &RequestContext,
@@ -585,7 +665,10 @@ async fn generate_wooden_fish_image_assets(
prompt: &str, prompt: &str,
hit_object_reference_image_src: Option<&str>, hit_object_reference_image_src: Option<&str>,
) -> Result<WoodenFishGeneratedImageAssets, AppError> { ) -> Result<WoodenFishGeneratedImageAssets, AppError> {
let settings = require_openai_image_settings(state)?; let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?; let http_client = build_openai_image_http_client(&settings)?;
let clean_reference_image_src = hit_object_reference_image_src let clean_reference_image_src = hit_object_reference_image_src
.map(str::trim) .map(str::trim)

View File

@@ -32,6 +32,7 @@ pub struct AuthUserPayload {
pub struct PublicUserSummaryPayload { pub struct PublicUserSummaryPayload {
pub id: String, pub id: String,
pub public_user_code: String, pub public_user_code: String,
pub username: String,
pub display_name: String, pub display_name: String,
pub avatar_url: Option<String>, pub avatar_url: Option<String>,
} }

View File

@@ -85,6 +85,7 @@ impl SpacetimeClient {
&self, &self,
session_id: String, session_id: String,
owner_user_id: String, owner_user_id: String,
author_display_name: String,
payload: WoodenFishActionRequest, payload: WoodenFishActionRequest,
) -> Result<WoodenFishActionResponse, SpacetimeClientError> { ) -> Result<WoodenFishActionResponse, SpacetimeClientError> {
let current = self let current = self
@@ -93,6 +94,7 @@ impl SpacetimeClient {
let (procedure, _) = build_wooden_fish_action_plan( let (procedure, _) = build_wooden_fish_action_plan(
&current, &current,
&owner_user_id, &owner_user_id,
&author_display_name,
&payload, &payload,
current_unix_micros(), current_unix_micros(),
)?; )?;
@@ -416,6 +418,7 @@ enum WoodenFishAssetRefresh {
fn build_wooden_fish_action_plan( fn build_wooden_fish_action_plan(
current: &WoodenFishSessionSnapshotResponse, current: &WoodenFishSessionSnapshotResponse,
owner_user_id: &str, owner_user_id: &str,
author_display_name: &str,
payload: &WoodenFishActionRequest, payload: &WoodenFishActionRequest,
now_micros: i64, now_micros: i64,
) -> Result<(WoodenFishActionProcedure, WoodenFishDraftResponse), SpacetimeClientError> { ) -> Result<(WoodenFishActionProcedure, WoodenFishDraftResponse), SpacetimeClientError> {
@@ -440,6 +443,7 @@ fn build_wooden_fish_action_plan(
WoodenFishActionProcedure::Compile(build_compile_input( WoodenFishActionProcedure::Compile(build_compile_input(
current, current,
owner_user_id, owner_user_id,
author_display_name,
&profile_id, &profile_id,
&mut draft, &mut draft,
WoodenFishAssetRefresh::Preserve, WoodenFishAssetRefresh::Preserve,
@@ -450,6 +454,7 @@ fn build_wooden_fish_action_plan(
WoodenFishActionProcedure::Compile(build_compile_input( WoodenFishActionProcedure::Compile(build_compile_input(
current, current,
owner_user_id, owner_user_id,
author_display_name,
&profile_id, &profile_id,
&mut draft, &mut draft,
WoodenFishAssetRefresh::HitObject, WoodenFishAssetRefresh::HitObject,
@@ -460,6 +465,7 @@ fn build_wooden_fish_action_plan(
WoodenFishActionProcedure::Compile(build_compile_input( WoodenFishActionProcedure::Compile(build_compile_input(
current, current,
owner_user_id, owner_user_id,
author_display_name,
&profile_id, &profile_id,
&mut draft, &mut draft,
WoodenFishAssetRefresh::HitSound, WoodenFishAssetRefresh::HitSound,
@@ -577,6 +583,7 @@ fn merge_action_into_draft(
fn build_compile_input( fn build_compile_input(
current: &WoodenFishSessionSnapshotResponse, current: &WoodenFishSessionSnapshotResponse,
owner_user_id: &str, owner_user_id: &str,
author_display_name: &str,
profile_id: &str, profile_id: &str,
draft: &mut WoodenFishDraftResponse, draft: &mut WoodenFishDraftResponse,
refresh: WoodenFishAssetRefresh, refresh: WoodenFishAssetRefresh,
@@ -611,7 +618,7 @@ fn build_compile_input(
session_id: current.session_id.clone(), session_id: current.session_id.clone(),
owner_user_id: owner_user_id.to_string(), owner_user_id: owner_user_id.to_string(),
profile_id: profile_id.to_string(), profile_id: profile_id.to_string(),
author_display_name: "敲木鱼玩家".to_string(), author_display_name: author_display_name.trim().to_string(),
work_title: draft.work_title.clone(), work_title: draft.work_title.clone(),
work_description: draft.work_description.clone(), work_description: draft.work_description.clone(),
theme_tags_json: Some(json_string(&draft.theme_tags)?), theme_tags_json: Some(json_string(&draft.theme_tags)?),

View File

@@ -187,6 +187,7 @@ const authServiceMocks = vi.hoisted(() => ({
async (publicUserCode: string): Promise<PublicUserSummary> => ({ async (publicUserCode: string): Promise<PublicUserSummary> => ({
id: `public-user-${publicUserCode}`, id: `public-user-${publicUserCode}`,
publicUserCode, publicUserCode,
username: 'author_user',
displayName: '公开作者', displayName: '公开作者',
avatarUrl: null, avatarUrl: null,
}), }),
@@ -195,6 +196,7 @@ const authServiceMocks = vi.hoisted(() => ({
async (userId: string): Promise<PublicUserSummary> => ({ async (userId: string): Promise<PublicUserSummary> => ({
id: userId, id: userId,
publicUserCode: `code-${userId}`, publicUserCode: `code-${userId}`,
username: 'author_user',
displayName: '公开作者', displayName: '公开作者',
avatarUrl: null, avatarUrl: null,
}), }),

View File

@@ -328,6 +328,7 @@ const {
async (code: string): Promise<PublicUserSummary> => ({ async (code: string): Promise<PublicUserSummary> => ({
id: `id-${code}`, id: `id-${code}`,
publicUserCode: code, publicUserCode: code,
username: 'author_user',
displayName: '公开作者', displayName: '公开作者',
avatarUrl: null, avatarUrl: null,
}), }),
@@ -336,6 +337,7 @@ const {
async (userId: string): Promise<PublicUserSummary> => ({ async (userId: string): Promise<PublicUserSummary> => ({
id: userId, id: userId,
publicUserCode: `code-${userId}`, publicUserCode: `code-${userId}`,
username: 'author_user',
displayName: '公开作者', displayName: '公开作者',
avatarUrl: null, avatarUrl: null,
}), }),
@@ -3285,6 +3287,7 @@ test('mobile recommend meta loads real author avatar from public user summary',
mockGetPublicAuthUserById.mockResolvedValueOnce({ mockGetPublicAuthUserById.mockResolvedValueOnce({
id: 'user-2', id: 'user-2',
publicUserCode: 'SY-00000002', publicUserCode: 'SY-00000002',
username: 'puzzle_user',
displayName: '拼图玩家', displayName: '拼图玩家',
avatarUrl: 'data:image/png;base64,AUTHOR', avatarUrl: 'data:image/png;base64,AUTHOR',
}); });

View File

@@ -611,6 +611,7 @@ function WorldCard({
onClick, onClick,
className, className,
authorAvatarUrl, authorAvatarUrl,
authorUsername,
feedCardKey, feedCardKey,
enableCoverCarousel = false, enableCoverCarousel = false,
isCoverCarouselActive = false, isCoverCarouselActive = false,
@@ -620,6 +621,7 @@ function WorldCard({
onClick: () => void; onClick: () => void;
className?: string; className?: string;
authorAvatarUrl?: string | null; authorAvatarUrl?: string | null;
authorUsername?: string | null;
feedCardKey?: string; feedCardKey?: string;
enableCoverCarousel?: boolean; enableCoverCarousel?: boolean;
isCoverCarouselActive?: boolean; isCoverCarouselActive?: boolean;
@@ -653,7 +655,10 @@ function WorldCard({
const remixCount = getPlatformWorldRemixCount(entry); const remixCount = getPlatformWorldRemixCount(entry);
const likeCount = getPlatformWorldLikeCount(entry); const likeCount = getPlatformWorldLikeCount(entry);
const typeLabel = describePublicGalleryCardKind(entry); const typeLabel = describePublicGalleryCardKind(entry);
const authorName = entry.authorDisplayName.trim() || '玩家'; const authorName = resolvePublicEntryAuthorDisplayText(
entry,
authorUsername,
);
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName); const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? ''; const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
const cardLabel = `${entry.worldName}${typeLabel}${formatCompactCount(playCount)}游玩,${formatCompactCount(remixCount)}改造,${formatCompactCount(likeCount)}点赞`; const cardLabel = `${entry.worldName}${typeLabel}${formatCompactCount(playCount)}游玩,${formatCompactCount(remixCount)}改造,${formatCompactCount(likeCount)}点赞`;
@@ -935,6 +940,7 @@ function RecommendRuntimePreviewCard({
function RecommendSwipeCard({ function RecommendSwipeCard({
entry, entry,
authorAvatarUrl, authorAvatarUrl,
authorUsername,
isActive, isActive,
visual, visual,
shareState, shareState,
@@ -948,6 +954,7 @@ function RecommendSwipeCard({
}: { }: {
entry: PlatformPublicGalleryCard; entry: PlatformPublicGalleryCard;
authorAvatarUrl?: string | null; authorAvatarUrl?: string | null;
authorUsername?: string | null;
isActive: boolean; isActive: boolean;
visual: ReactNode; visual: ReactNode;
shareState?: 'idle' | 'copied' | 'failed'; shareState?: 'idle' | 'copied' | 'failed';
@@ -972,6 +979,7 @@ function RecommendSwipeCard({
<RecommendRuntimeMeta <RecommendRuntimeMeta
entry={entry} entry={entry}
authorAvatarUrl={authorAvatarUrl} authorAvatarUrl={authorAvatarUrl}
authorUsername={authorUsername}
isActive={isActive} isActive={isActive}
shareState={shareState} shareState={shareState}
onDragPointerDown={onDragPointerDown} onDragPointerDown={onDragPointerDown}
@@ -990,6 +998,7 @@ function RecommendSwipeCard({
function RecommendRuntimeMeta({ function RecommendRuntimeMeta({
entry, entry,
authorAvatarUrl, authorAvatarUrl,
authorUsername,
onDragPointerDown, onDragPointerDown,
onDragPointerMove, onDragPointerMove,
onDragPointerUp, onDragPointerUp,
@@ -1002,6 +1011,7 @@ function RecommendRuntimeMeta({
}: { }: {
entry: PlatformPublicGalleryCard; entry: PlatformPublicGalleryCard;
authorAvatarUrl?: string | null; authorAvatarUrl?: string | null;
authorUsername?: string | null;
onDragPointerDown?: (event: PointerEvent<HTMLElement>) => void; onDragPointerDown?: (event: PointerEvent<HTMLElement>) => void;
onDragPointerMove?: (event: PointerEvent<HTMLElement>) => void; onDragPointerMove?: (event: PointerEvent<HTMLElement>) => void;
onDragPointerUp?: (event: PointerEvent<HTMLElement>) => void; onDragPointerUp?: (event: PointerEvent<HTMLElement>) => void;
@@ -1014,7 +1024,10 @@ function RecommendRuntimeMeta({
}) { }) {
const likeCount = getPlatformWorldLikeCount(entry); const likeCount = getPlatformWorldLikeCount(entry);
const remixCount = getPlatformWorldRemixCount(entry); const remixCount = getPlatformWorldRemixCount(entry);
const authorName = entry.authorDisplayName.trim() || '玩家'; const authorName = resolvePublicEntryAuthorDisplayText(
entry,
authorUsername,
);
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName); const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? ''; const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
const displayName = formatPlatformWorkDisplayName(entry.worldName); const displayName = formatPlatformWorkDisplayName(entry.worldName);
@@ -1890,28 +1903,28 @@ async function getPublicWorkAuthorSummary(
function describePublicGalleryCardKind(entry: PlatformPublicGalleryCard) { function describePublicGalleryCardKind(entry: PlatformPublicGalleryCard) {
if (isBigFishGalleryEntry(entry)) { if (isBigFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('大鱼吃小鱼');
} }
if (isPuzzleGalleryEntry(entry)) { if (isPuzzleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('拼图');
} }
if (isMatch3DGalleryEntry(entry)) { if (isMatch3DGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('抓大鹅');
} }
if (isSquareHoleGalleryEntry(entry)) { if (isSquareHoleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('方洞挑战');
} }
if (isJumpHopGalleryEntry(entry)) { if (isJumpHopGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('???'); return formatPlatformWorkDisplayTag('跳一跳');
} }
if (isWoodenFishGalleryEntry(entry)) { if (isWoodenFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('???'); return formatPlatformWorkDisplayTag('敲木鱼');
} }
if (isVisualNovelGalleryEntry(entry)) { if (isVisualNovelGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('视觉小说');
} }
if (isBarkBattleGalleryEntry(entry)) { if (isBarkBattleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('??'); return formatPlatformWorkDisplayTag('汪汪声浪');
} }
if (isEdutainmentGalleryEntry(entry)) { if (isEdutainmentGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag(entry.templateName); return formatPlatformWorkDisplayTag(entry.templateName);
@@ -1922,6 +1935,17 @@ function getPublicAuthorAvatarLabel(authorDisplayName: string) {
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩'; return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
} }
function resolvePublicEntryAuthorDisplayText(
entry: PlatformPublicGalleryCard,
authorUsername?: string | null,
) {
if (isWoodenFishGalleryEntry(entry)) {
return authorUsername?.trim() || entry.authorDisplayName.trim() || '玩家';
}
return entry.authorDisplayName.trim() || '玩家';
}
function getPlatformWorldLikeCount(entry: PlatformWorldCardLike) { function getPlatformWorldLikeCount(entry: PlatformWorldCardLike) {
return Math.max(0, Math.round(entry.likeCount ?? 0)); return Math.max(0, Math.round(entry.likeCount ?? 0));
} }
@@ -4178,6 +4202,17 @@ export function RpgEntryHomeView({
}, },
[publicAuthorSummariesByKey], [publicAuthorSummariesByKey],
); );
const getPublicEntryAuthorUsername = useCallback(
(entry: PlatformPublicGalleryCard) => {
const authorLookupKey = buildPublicWorkAuthorLookupKey(entry);
if (!authorLookupKey) {
return null;
}
return publicAuthorSummariesByKey[authorLookupKey]?.username?.trim() || null;
},
[publicAuthorSummariesByKey],
);
const activeCategoryGroup = const activeCategoryGroup =
categoryGroups.find((group) => group.tag === selectedCategoryTag) ?? categoryGroups.find((group) => group.tag === selectedCategoryTag) ??
categoryGroups[0] ?? categoryGroups[0] ??
@@ -5523,6 +5558,9 @@ export function RpgEntryHomeView({
authorAvatarUrl={getPublicEntryAuthorAvatarUrl( authorAvatarUrl={getPublicEntryAuthorAvatarUrl(
previousRecommendEntry, previousRecommendEntry,
)} )}
authorUsername={getPublicEntryAuthorUsername(
previousRecommendEntry,
)}
isActive={false} isActive={false}
visual={ visual={
<RecommendRuntimePreviewCard <RecommendRuntimePreviewCard
@@ -5540,6 +5578,9 @@ export function RpgEntryHomeView({
authorAvatarUrl={getPublicEntryAuthorAvatarUrl( authorAvatarUrl={getPublicEntryAuthorAvatarUrl(
activeRecommendEntry, activeRecommendEntry,
)} )}
authorUsername={getPublicEntryAuthorUsername(
activeRecommendEntry,
)}
isActive isActive
visual={ visual={
<div className="platform-recommend-runtime-viewport"> <div className="platform-recommend-runtime-viewport">
@@ -5564,6 +5605,9 @@ export function RpgEntryHomeView({
authorAvatarUrl={getPublicEntryAuthorAvatarUrl( authorAvatarUrl={getPublicEntryAuthorAvatarUrl(
nextRecommendEntry, nextRecommendEntry,
)} )}
authorUsername={getPublicEntryAuthorUsername(
nextRecommendEntry,
)}
isActive={false} isActive={false}
visual={ visual={
<RecommendRuntimePreviewCard <RecommendRuntimePreviewCard
@@ -5717,6 +5761,7 @@ export function RpgEntryHomeView({
onClick={() => onOpenGalleryDetail(entry)} onClick={() => onOpenGalleryDetail(entry)}
className="w-full" className="w-full"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
feedCardKey={cardKey} feedCardKey={cardKey}
/> />
); );
@@ -5782,6 +5827,7 @@ export function RpgEntryHomeView({
onClick={() => onOpenGalleryDetail(entry)} onClick={() => onOpenGalleryDetail(entry)}
className="w-full" className="w-full"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
feedCardKey={cardKey} feedCardKey={cardKey}
enableCoverCarousel={mobileFeedCarouselEnabled} enableCoverCarousel={mobileFeedCarouselEnabled}
isCoverCarouselActive={ isCoverCarouselActive={
@@ -5888,6 +5934,7 @@ export function RpgEntryHomeView({
onClick={() => openRecommendGalleryDetail(entry)} onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0" className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
/> />
))} ))}
</div> </div>
@@ -5915,6 +5962,7 @@ export function RpgEntryHomeView({
onClick={() => openRecommendGalleryDetail(entry)} onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0" className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
/> />
))} ))}
{onOpenChildMotionDemo ? ( {onOpenChildMotionDemo ? (
@@ -5975,6 +6023,7 @@ export function RpgEntryHomeView({
onClick={() => openRecommendGalleryDetail(entry)} onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0" className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
/> />
))} ))}
</div> </div>
@@ -6490,6 +6539,7 @@ export function RpgEntryHomeView({
onClick={() => openRecommendGalleryDetail(entry)} onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0" className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
/> />
))} ))}
</div> </div>
@@ -6660,6 +6710,7 @@ export function RpgEntryHomeView({
onClick={() => openRecommendGalleryDetail(entry)} onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0" className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)} authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
authorUsername={getPublicEntryAuthorUsername(entry)}
/> />
))} ))}
</div> </div>

View File

@@ -219,6 +219,7 @@ export type PlatformWoodenFishGalleryCard = {
sourceSessionId?: string | null; sourceSessionId?: string | null;
publicWorkCode: string; publicWorkCode: string;
ownerUserId: string; ownerUserId: string;
authorUsername?: string | null;
authorDisplayName: string; authorDisplayName: string;
worldName: string; worldName: string;
subtitle: string; subtitle: string;
@@ -562,6 +563,10 @@ export function mapWoodenFishWorkToPlatformGalleryCard(
? summary.publicWorkCode ? summary.publicWorkCode
: buildWoodenFishPublicWorkCode(summary.profileId), : buildWoodenFishPublicWorkCode(summary.profileId),
ownerUserId: summary.ownerUserId, ownerUserId: summary.ownerUserId,
authorUsername:
'authorUsername' in summary && typeof summary.authorUsername === 'string'
? summary.authorUsername
: null,
authorDisplayName: authorDisplayName:
'authorDisplayName' in summary ? summary.authorDisplayName : '玩家', 'authorDisplayName' in summary ? summary.authorDisplayName : '玩家',
worldName: summary.workTitle, worldName: summary.workTitle,

View File

@@ -40,7 +40,6 @@ type WoodenFishWorkspaceFormState = {
floatingWords: string[]; floatingWords: string[];
}; };
const DEFAULT_WORK_TITLE = '今日敲木鱼';
const DEFAULT_THEME_TAGS = ['敲木鱼', '解压']; const DEFAULT_THEME_TAGS = ['敲木鱼', '解压'];
const DEFAULT_FLOATING_WORDS = ['幸运']; const DEFAULT_FLOATING_WORDS = ['幸运'];
const MAX_FLOATING_WORD_COUNT = 8; const MAX_FLOATING_WORD_COUNT = 8;
@@ -309,8 +308,9 @@ export function WoodenFishWorkspace({
try { try {
const payload: WoodenFishWorkspaceCreateRequest = { const payload: WoodenFishWorkspaceCreateRequest = {
templateId: 'wooden-fish', templateId: 'wooden-fish',
workTitle: DEFAULT_WORK_TITLE, workTitle: '',
workDescription: '', workDescription:
formState.hitObjectPrompt.trim() || WOODEN_FISH_DEFAULT_HIT_OBJECT_PROMPT,
themeTags: DEFAULT_THEME_TAGS, themeTags: DEFAULT_THEME_TAGS,
hitObjectPrompt: hitObjectPrompt:
formState.hitObjectPrompt.trim() || WOODEN_FISH_DEFAULT_HIT_OBJECT_PROMPT, formState.hitObjectPrompt.trim() || WOODEN_FISH_DEFAULT_HIT_OBJECT_PROMPT,