Merge remote-tracking branch 'origin/master' into codex/push-master-20260826
Project CI / Frontend tests (push) Failing after 3m33s
Project CI / Repository checks (push) Failing after 3m42s
Project CI / Native shell tests (push) Failing after 4m39s
Project CI / Backend tests (push) Successful in 5m30s

This commit is contained in:
2026-08-26 20:20:27 +08:00
34 changed files with 322 additions and 7 deletions
@@ -0,0 +1,5 @@
import appPackage from '../../package.json';
/** Product metadata shared by the client UI and release bundle. */
export const APP_NAME = 'Genarrative AI Game Creator';
export const APP_VERSION = appPackage.version;
@@ -3,6 +3,7 @@ import {
Cable,
CheckCircle2,
CircleAlert,
Info,
LoaderCircle,
RotateCcw,
Save,
@@ -13,6 +14,8 @@ import {
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { createGameCreationAppSeedTasks } from '../../../../../packages/shared/src/contracts/gameCreationApp';
import BRAND_ICON from '../../../../../packages/shared/src/icons/taonier-product-ip.png';
import { APP_NAME, APP_VERSION } from '../../app/appMetadata';
import {
closeDialogOnBackdropMouseDown,
closeDialogOnEscape,
@@ -79,7 +82,12 @@ const defaultRuntimeConfigDraft: GameCreatorAppConfig = {
},
};
type RuntimeSettingsSection = 'general' | 'agents' | 'connections' | 'advanced';
type RuntimeSettingsSection =
| 'general'
| 'agents'
| 'connections'
| 'advanced'
| 'about';
type RuntimeConfigToast = {
tone: 'success' | 'error';
@@ -111,6 +119,12 @@ const runtimeSettingsSections = [
description: '上下文、超时与重试',
icon: SlidersHorizontal,
},
{
id: 'about',
label: '关于',
description: '客户端信息',
icon: Info,
},
] as const satisfies ReadonlyArray<{
id: RuntimeSettingsSection;
label: string;
@@ -1258,6 +1272,44 @@ export function RuntimeConfigDialog({
) : null}
</section>
) : null}
{activeSection === 'about' ? (
<section
className="runtime-settings-about"
aria-label="关于客户端"
>
<div className="runtime-settings-about-hero">
<div
className="runtime-settings-about-mark"
aria-hidden="true"
>
<img
src={BRAND_ICON}
alt=""
data-testid="runtime-settings-app-logo"
/>
</div>
<div>
<span className="runtime-settings-about-kicker">
GENARRATIVE
</span>
<h4>{APP_NAME}</h4>
<p></p>
</div>
</div>
<dl className="runtime-settings-about-meta">
<div>
<dt></dt>
<dd data-testid="runtime-settings-app-version">
v{APP_VERSION}
</dd>
</div>
<div>
<dt></dt>
<dd></dd>
</div>
</dl>
</section>
) : null}
</div>
</main>
</div>
+105
View File
@@ -3212,6 +3212,101 @@ h2 {
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
}
.runtime-settings-about {
grid-column: 1 / -1;
display: grid;
gap: 14px;
max-width: 680px;
}
.runtime-settings-about-hero {
display: flex;
align-items: center;
gap: 18px;
min-height: 158px;
padding: 24px;
border: 1px solid var(--platform-subpanel-border);
border-radius: 16px;
background: var(--runtime-settings-subpanel-fill);
}
.runtime-settings-about-mark {
display: grid;
flex: 0 0 auto;
width: 72px;
height: 72px;
border: 1px solid var(--platform-nav-active-border);
border-radius: 20px;
background: var(--platform-nav-active-fill);
color: var(--platform-accent);
font-size: 32px;
font-weight: 900;
letter-spacing: -0.08em;
place-items: center;
box-shadow: var(--platform-nav-active-shadow);
}
.runtime-settings-about-mark img {
width: 100%;
height: 100%;
object-fit: contain;
transform: scale(1.35);
}
.runtime-settings-about-hero > div:last-child {
display: grid;
gap: 5px;
min-width: 0;
}
.runtime-settings-about-kicker {
color: var(--platform-accent);
font-size: 10px;
font-weight: 900;
letter-spacing: 0.16em;
}
.runtime-settings-about-hero h4 {
margin: 0;
color: var(--platform-text-strong);
font-size: 20px;
letter-spacing: -0.03em;
}
.runtime-settings-about-hero p {
margin: 0;
color: var(--platform-text-soft);
font-size: 12px;
}
.runtime-settings-about-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 0;
}
.runtime-settings-about-meta > div {
display: grid;
gap: 7px;
padding: 14px;
border: 1px solid var(--platform-subpanel-border);
border-radius: 12px;
background: var(--runtime-settings-subpanel-fill);
}
.runtime-settings-about-meta dt {
color: var(--platform-text-soft);
font-size: 10px;
}
.runtime-settings-about-meta dd {
margin: 0;
color: var(--platform-text-strong);
font-size: 14px;
font-weight: 800;
}
.runtime-agent-list {
grid-column: 1 / -1;
display: grid;
@@ -4034,6 +4129,16 @@ iframe.preview-frame {
grid-template-columns: 1fr;
}
.runtime-settings-about-hero {
align-items: flex-start;
flex-direction: column;
min-height: 0;
}
.runtime-settings-about-meta {
grid-template-columns: 1fr;
}
.runtime-agent-card-summary > span:last-child {
max-width: 48%;
text-align: right;
@@ -0,0 +1,81 @@
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
// TODO: 后续改为由服务器下发灵感资源;届时保留此组件的展示与预览交互,移除本地目录扫描。
const INSPIRATION_IMAGES = Object.entries(
import.meta.glob('./assets/inspiration/*.{webp,png,jpg,jpeg}', {
eager: true,
import: 'default',
query: '?url',
}),
)
.sort(([left], [right]) =>
left.localeCompare(right, undefined, { numeric: true }),
)
.map(([, image]) => image as string);
export default function InspirationGallery() {
const [selectedImage, setSelectedImage] = useState<string | null>(null);
useEffect(() => {
if (!selectedImage) {
return;
}
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setSelectedImage(null);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
document.body.style.overflow = previousOverflow;
window.removeEventListener('keydown', handleKeyDown);
};
}, [selectedImage]);
return (
<>
<div className="columns-3 gap-3 max-[760px]:columns-2 max-[460px]:columns-1">
{INSPIRATION_IMAGES.map((image, index) => (
<button
className="mb-3 block w-full cursor-zoom-in break-inside-avoid overflow-hidden rounded-lg border-0 bg-transparent p-0 shadow-(--platform-panel-shadow) transition duration-200 hover:-translate-y-0.5 hover:shadow-(--platform-profile-action-shadow) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--platform-warm-text)"
type="button"
key={image}
aria-label={`查看灵感图片 ${index + 1}`}
onClick={() => setSelectedImage(image)}
>
<img
className="block h-auto w-full"
src={image}
alt=""
loading="lazy"
decoding="async"
/>
</button>
))}
</div>
{selectedImage
? createPortal(
<div
className="fixed inset-0 z-[1000] grid cursor-zoom-out place-items-center bg-black/80 p-6"
role="dialog"
aria-modal="true"
aria-label="查看灵感图片"
onClick={() => setSelectedImage(null)}
>
<img
className="block max-h-[92vh] max-w-[92vw] cursor-default object-contain shadow-2xl"
src={selectedImage}
alt="放大的灵感图片"
onClick={(event) => event.stopPropagation()}
/>
</div>,
document.body,
)
: null}
</>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Some files were not shown because too many files have changed in this diff Show More