Merge remote-tracking branch 'origin/master' into codex/push-master-20260826
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 34 KiB |