Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ body,

--shadow-app-card: 0 18px 45px rgb(29 37 31 / 9%);
--shadow-app-panel: 0 22px 60px rgb(29 37 31 / 12%);
--shadow-app-composer-focus: 0 18px 48px rgb(29 37 31 / 14%);
--shadow-app-page: 0 26px 80px rgb(29 37 31 / 10%);
--shadow-app-header: inset 0 1px 0 rgb(255 255 255 / 55%);
--shadow-app-menu: 0 8px 24px rgb(29 37 31 / 10%);
Expand Down
21 changes: 2 additions & 19 deletions frontend/src/pages/quick-start/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,6 @@ describe('QuickStartPage', () => {
expect(screen.getByRole('button', { name: /16-bit 日式 RPG/u })).toBeTruthy()
expect(screen.getByRole('button', { name: /暗黑哥特像素/u })).toBeTruthy()
expect(screen.getByRole('button', { name: /温暖手绘像素/u })).toBeTruthy()
expect(screen.getByRole('button', { name: '像素守夜人' })).toBeTruthy()
expect(screen.getByRole('button', { name: '轻装信使' })).toBeTruthy()

fireEvent.change(screen.getByRole('textbox', { name: '创作指令' }), {
target: { value: '戴银色面具的游侠' },
Expand All @@ -350,21 +348,6 @@ describe('QuickStartPage', () => {
expect(screen.queryByRole('button', { name: /暗黑哥特像素/u })).toBeNull()
})

it('keeps the original Quick Start prompt shortcuts functional', () => {
const view = renderAt('/quick-start', serviceFor(null))
fireEvent.click(screen.getByRole('button', { name: '像素守夜人' }))
expect((screen.getByRole('textbox', { name: '创作指令' }) as HTMLTextAreaElement).value).toBe(
'一位提着风灯、披深色斗篷的像素守夜人',
)

view.unmount()
renderAt('/quick-start', serviceFor(null))
fireEvent.click(screen.getByRole('button', { name: '轻装信使' }))
expect((screen.getByRole('textbox', { name: '创作指令' }) as HTMLTextAreaElement).value).toBe(
'轻装信使,侧视像素风,轮廓清晰,动作轻快',
)
})

it('keeps the entry and run canvases at least viewport height', async () => {
const entry = renderAt('/quick-start', serviceFor(null))
expect(entry.getByLabelText('创作指令').closest('section')?.className).toContain(
Expand Down Expand Up @@ -748,7 +731,7 @@ describe('QuickStartPage', () => {

expect(screen.getByRole('textbox', { name: '创作指令' })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: /16-bit 日式 RPG/u }))
expect((screen.getByRole('textbox') as HTMLTextAreaElement).value).toBe(
expect((screen.getByRole('textbox') as HTMLInputElement).value).toBe(
'16-bit 日式 RPG 像素风,清晰轮廓,明亮配色',
)
expect(screen.queryByRole('button', { name: /暗黑哥特像素/u })).toBeNull()
Expand Down Expand Up @@ -846,7 +829,7 @@ describe('QuickStartPage', () => {
fireEvent.change(screen.getByLabelText('上传角色母版'), { target: { files: [file] } })

const composer = screen.getByLabelText('创作指令').closest('form')
expect(screen.getByLabelText('创作指令').tagName).toBe('TEXTAREA')
expect(screen.getByLabelText('创作指令').tagName).toBe('INPUT')
expect(composer?.textContent).toContain('hero.png')
expect(screen.getByRole('button', { name: '移除图片' }).closest('form')).toBe(composer)
expect(composer?.querySelector('[data-layout="quick-start-attachment-row"]')).toBeNull()
Expand Down
43 changes: 5 additions & 38 deletions frontend/src/pages/quick-start/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
type WorkflowRun,
} from '@/entities'
import { ExportButton, type ExportPackageModel } from '@/features/export-package'
import { KineticCopyCycle, type KineticCopyMessage } from './kinetic-copy-cycle'
import { KineticCopyCycle, type KineticCopyMessage } from '@/shared/ui'
import {
quickStartService,
type QuickStartEntryService,
Expand Down Expand Up @@ -241,17 +241,6 @@ function QuickStartInput({
const hasPrompt = Boolean(prompt.trim())
const showStylePrompts = !hasPrompt && !templateFile

const originalPromptShortcuts = [
{
label: '像素守夜人',
prompt: '一位提着风灯、披深色斗篷的像素守夜人',
},
{
label: '轻装信使',
prompt: '轻装信使,侧视像素风,轮廓清晰,动作轻快',
},
] as const

useEffect(
() => () => {
submitAbortController.current?.abort()
Expand Down Expand Up @@ -361,47 +350,25 @@ function QuickStartInput({
</button>
))}
</div>
<div
data-layout="quick-start-original-shortcuts"
data-presence={showStylePrompts ? 'visible' : 'hidden'}
aria-hidden={!showStylePrompts}
className={`flex flex-wrap justify-center gap-2 transition-[opacity,transform,filter] duration-[460ms] motion-reduce:transition-none ${
showStylePrompts
? 'translate-y-0 opacity-100 blur-0'
: 'pointer-events-none -translate-y-1 opacity-0 blur-[4px]'
}`}
>
{originalPromptShortcuts.map((shortcut) => (
<button
key={shortcut.label}
type="button"
disabled={!showStylePrompts}
onClick={() => setPrompt(shortcut.prompt)}
className="rounded-full border border-app-line px-3 py-1.5 text-xs font-medium text-app-muted transition hover:border-app-line-strong hover:text-app-accent"
>
{shortcut.label}
</button>
))}
</div>
</div>

<div data-layout="quick-start-composer" className="mx-auto w-full max-w-3xl self-end">
<form
onSubmit={(event) => void submit(event)}
className="grid items-center gap-1.5 rounded-xl border border-app-line-strong bg-app-surface-raised p-1.5 shadow-app-panel transition-shadow focus-within:border-app-accent sm:grid-cols-[1fr_auto_auto]"
className="grid items-center gap-1.5 rounded-xl border border-app-line-strong bg-app-surface-raised p-1.5 shadow-app-panel transition-shadow focus-within:border-app-accent focus-within:shadow-[var(--shadow-app-composer-focus)] sm:grid-cols-[1fr_auto_auto]"
>
<label className="min-w-0" htmlFor="quick-start-prompt">
<span className="sr-only">创作指令</span>
<textarea
<input
id="quick-start-prompt"
type="text"
aria-label="创作指令"
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
rows={2}
placeholder={
templateFile ? '描述动作,可留空生成待机动作…' : '描述角色的外形、身份和气质…'
}
className="min-h-10 w-full min-w-0 resize-none border-0 bg-transparent px-3 py-2 text-[15px] leading-6 text-app-ink outline-none placeholder:text-app-faint"
className="h-10 w-full min-w-0 border-0 bg-transparent px-3 text-[15px] text-app-ink outline-none placeholder:text-app-faint"
/>
</label>

Expand Down
9 changes: 9 additions & 0 deletions frontend/src/pages/quick-start/quick-start-motion.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
width: 100%;
}

.quick-start-agent-copy {
place-items: start;
text-align: left;
}

.quick-start-agent-copy .kinetic-copy-line-inner-prefixed {
justify-content: flex-start;
}

/* 逐字进入完成后再开高光,避免父层裁字与子层字符同时绘制同一句。 */
.quick-start-generation-shimmer.kinetic-copy-cycle-resting .kinetic-copy-line-inner {
color: transparent;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export { PageContainer } from './page-container'
export type { PageContainerProps } from './page-container'
export { Pagination } from './pagination'
export type { PaginationProps } from './pagination'
export { KineticCopyCycle } from './kinetic-copy-cycle'
export type { KineticCopyCycleProps, KineticCopyMessage } from './kinetic-copy-cycle'
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,6 @@
place-items: center;
}

.quick-start-agent-copy {
place-items: start;
text-align: left;
}

.quick-start-agent-copy .kinetic-copy-line-inner-prefixed {
justify-content: flex-start;
}

.kinetic-copy-line {
display: block;
overflow: hidden;
Expand Down Expand Up @@ -50,10 +41,7 @@
animation-delay: calc(var(--kinetic-copy-line-reverse-index) * 55ms);
}

.kinetic-copy-prefix {
display: inline-block;
}

.kinetic-copy-prefix,
.kinetic-copy-character {
display: inline-block;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ function messageParts(message: KineticCopyMessage) {
: (message as Exclude<KineticCopyMessage, readonly string[]>)
}

/** Quick Start 的裁切字幕:完整退场后再换文案,避免重叠与跳字。 */
export function KineticCopyCycle({
messages,
active = true,
Expand Down
Loading