fix home example prompt presets

This commit is contained in:
qiongyu1999 2026-05-21 16:37:00 +08:00
parent f677563313
commit 48f3404051
7 changed files with 136 additions and 15 deletions

View file

@ -2556,11 +2556,29 @@ function homeHeroExamplePluginsForChip(
plugins: InstalledPluginRecord[],
locale: Locale,
): InstalledPluginRecord[] {
return plugins
const presets = plugins
.filter((plugin) => pluginMatchesExampleChip(plugin, chipId))
.filter((plugin) => Boolean(pluginPresetQuery(plugin, locale)))
.sort((a, b) => pluginPresetRank(b, chipId) - pluginPresetRank(a, chipId))
.slice(0, 18);
if (chipId === 'image') {
return movePluginPresetToEnd(presets, 'example-hatch-pet');
}
return presets;
}
function movePluginPresetToEnd(
records: InstalledPluginRecord[],
pluginId: string,
): InstalledPluginRecord[] {
const index = records.findIndex((record) => record.id === pluginId);
if (index < 0 || index === records.length - 1) return records;
const record = records[index]!;
return [
...records.slice(0, index),
...records.slice(index + 1),
record,
];
}
function pluginMatchesExampleChip(record: InstalledPluginRecord, chipId: string): boolean {
@ -2579,8 +2597,10 @@ function pluginMatchesExampleChip(record: InstalledPluginRecord, chipId: string)
return has('deck', 'slides', 'slide-deck') || hasPart('slide', 'deck');
case 'hyperframes':
return hasPart('hyperframes', 'hyperframe');
case 'live-artifact':
return has('live-artifact') || hasPart('live-artifact');
case 'image':
return (has('image') || hasPart('image-template')) && !hasPart('video', 'audio');
return (has('image') || hasPart('image-template')) && !hasPart('video', 'audio', 'live-artifact');
case 'video':
return (has('video') || hasPart('video-template')) && !hasPart('hyperframes', 'audio');
case 'audio':

View file

@ -860,14 +860,16 @@ export function HomeView({
function useExamplePlugin(record: InstalledPluginRecord, chipId: string, promptText: string) {
const projectKind = projectKindForExamplePlugin(record, chipId);
requestActivePlugin(record, promptText, {
projectKind,
chipId,
inputs: {},
inputFields: [],
queryTemplate: null,
replaceWithoutConfirmation: true,
});
activePluginApplyRequestRef.current += 1;
setActive(null);
setActiveSkill(null);
setFallbackProjectKind(projectKind);
setPendingApplyId(null);
setPendingChipId(null);
setError(null);
setPrompt(promptText);
setPromptEditedByUser(false);
focusPromptAtEnd();
}
function removePluginContext(pluginId: string) {

View file

@ -22,7 +22,12 @@ afterEach(() => {
cleanup();
});
function makePlugin(id: string, mode: string, title = id): InstalledPluginRecord {
function makePlugin(
id: string,
mode: string,
title = id,
extraTags: string[] = [],
): InstalledPluginRecord {
return {
id,
title,
@ -36,7 +41,7 @@ function makePlugin(id: string, mode: string, title = id): InstalledPluginRecord
version: '1.0.0',
title,
description: 'Plugin preset fixture',
tags: [mode],
tags: [mode, ...extraTags],
od: {
mode,
useCase: {
@ -167,6 +172,51 @@ describe('HomeHero intent rail', () => {
);
});
it('keeps Hatch Pet at the end of the image example presets', () => {
const hatchPet = makePlugin('example-hatch-pet', 'image', 'Hatch Pet');
const imagePoster = makePlugin('image-template-poster', 'image', 'Image Poster');
const stoneInfographic = makePlugin('image-template-stone', 'image', 'Stone Infographic');
renderHero({
activeChipId: 'image',
pluginOptions: [hatchPet, imagePoster, stoneInfographic],
});
const presets = screen.getAllByTestId('home-hero-plugin-preset');
expect(presets.map((preset) => preset.textContent)).toEqual([
expect.stringContaining('Image Poster'),
expect.stringContaining('Stone Infographic'),
expect.stringContaining('Hatch Pet'),
]);
});
it('moves live artifact presets out of Image and into Live artifact examples', () => {
const imagePoster = makePlugin('image-template-poster', 'image', 'Image Poster');
const notionDashboard = makePlugin(
'image-template-notion-team-dashboard-live-artifact',
'image',
'Notion-style Team Dashboard (Live Artifact)',
['live-artifact'],
);
renderHero({
activeChipId: 'image',
pluginOptions: [imagePoster, notionDashboard],
});
let presets = screen.getAllByTestId('home-hero-plugin-preset');
expect(presets).toHaveLength(1);
expect(presets[0]?.textContent).toContain('Image Poster');
cleanup();
renderHero({
activeChipId: 'live-artifact',
pluginOptions: [imagePoster, notionDashboard],
});
presets = screen.getAllByTestId('home-hero-plugin-preset');
expect(presets).toHaveLength(1);
expect(presets[0]?.textContent).toContain('Notion-style Team Dashboard (Live Artifact)');
});
it('disables every visible chip while a plugin apply is in flight', () => {
renderHero({ pendingPluginId: 'od-figma-migration', pendingChipId: 'figma' });
for (const chip of HOME_HERO_CHIPS.filter((item) => item.group === 'create')) {

View file

@ -581,6 +581,55 @@ describe('HomeView prompt handoff', () => {
expect(screen.queryByRole('alert')).toBeNull();
});
it('uses example preset cards as plain-text prompt fillers without binding plugin inputs', async () => {
const fetchMock = vi.fn<typeof fetch>(async (url) => {
if (typeof url === 'string' && url === '/api/plugins') {
return new Response(JSON.stringify({ plugins: [WEB_PROTOTYPE_PLUGIN] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
if (typeof url === 'string' && url.includes('/apply')) {
return new Response(JSON.stringify(WEB_PROTOTYPE_APPLY_RESULT), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
throw new Error(`unexpected fetch ${url}`);
});
vi.stubGlobal('fetch', fetchMock);
stubAnimationFrame();
render(
<HomeView
projects={[]}
onSubmit={() => undefined}
onOpenProject={() => undefined}
onViewAllProjects={() => undefined}
/>,
);
await clearActiveTypeChip();
fireEvent.click(await screen.findByTestId('home-hero-rail-prototype'));
fireEvent.click(await screen.findByTestId('home-hero-plugin-preset'));
const input = screen.getByTestId('home-hero-input') as HTMLTextAreaElement;
await waitFor(() => {
expect(input.value).toBe(
'Build a high-fidelity web prototype for product evaluators using the active project design system from the bundled web prototype seed.',
);
});
expect(fetchMock.mock.calls.some(([url]) => (
typeof url === 'string' && url.includes('/api/plugins/example-web-prototype/apply')
))).toBe(false);
expect(screen.queryByTestId('home-hero-active-type-chip')).toBeNull();
expect(screen.queryByTestId('plugin-inputs-form')).toBeNull();
expect(screen.queryByTestId('home-hero-prompt-slot-fidelity')).toBeNull();
expect(screen.queryByTestId('home-hero-prompt-slot-artifactKind')).toBeNull();
expect(screen.queryByTestId('home-hero-prompt-slot-designSystem')).toBeNull();
expect(screen.queryByTestId('home-hero-prompt-slot-template')).toBeNull();
});
it('binds the Home rail Live artifact chip with live-artifact metadata and applies it on submit', async () => {
const fetchMock = vi.fn<typeof fetch>(async (url) => {
if (typeof url === 'string' && url === '/api/plugins') {

View file

@ -27,7 +27,7 @@
"surface": "image",
"preview": {
"type": "image",
"poster": "https://raw.githubusercontent.com/joeylee12629-star/open-design/feat/prompt-template-live-artifact-dashboard/prompt-templates/image/notion-team-dashboard-live-artifact.preview.png"
"poster": "https://raw.githubusercontent.com/nexu-io/open-design/main/prompt-templates/image/notion-team-dashboard-live-artifact.preview.png"
},
"useCase": {
"query": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long