mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
fix home example prompt presets
This commit is contained in:
parent
f677563313
commit
48f3404051
7 changed files with 136 additions and 15 deletions
|
|
@ -2556,11 +2556,29 @@ function homeHeroExamplePluginsForChip(
|
||||||
plugins: InstalledPluginRecord[],
|
plugins: InstalledPluginRecord[],
|
||||||
locale: Locale,
|
locale: Locale,
|
||||||
): InstalledPluginRecord[] {
|
): InstalledPluginRecord[] {
|
||||||
return plugins
|
const presets = plugins
|
||||||
.filter((plugin) => pluginMatchesExampleChip(plugin, chipId))
|
.filter((plugin) => pluginMatchesExampleChip(plugin, chipId))
|
||||||
.filter((plugin) => Boolean(pluginPresetQuery(plugin, locale)))
|
.filter((plugin) => Boolean(pluginPresetQuery(plugin, locale)))
|
||||||
.sort((a, b) => pluginPresetRank(b, chipId) - pluginPresetRank(a, chipId))
|
.sort((a, b) => pluginPresetRank(b, chipId) - pluginPresetRank(a, chipId))
|
||||||
.slice(0, 18);
|
.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 {
|
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');
|
return has('deck', 'slides', 'slide-deck') || hasPart('slide', 'deck');
|
||||||
case 'hyperframes':
|
case 'hyperframes':
|
||||||
return hasPart('hyperframes', 'hyperframe');
|
return hasPart('hyperframes', 'hyperframe');
|
||||||
|
case 'live-artifact':
|
||||||
|
return has('live-artifact') || hasPart('live-artifact');
|
||||||
case 'image':
|
case 'image':
|
||||||
return (has('image') || hasPart('image-template')) && !hasPart('video', 'audio');
|
return (has('image') || hasPart('image-template')) && !hasPart('video', 'audio', 'live-artifact');
|
||||||
case 'video':
|
case 'video':
|
||||||
return (has('video') || hasPart('video-template')) && !hasPart('hyperframes', 'audio');
|
return (has('video') || hasPart('video-template')) && !hasPart('hyperframes', 'audio');
|
||||||
case 'audio':
|
case 'audio':
|
||||||
|
|
|
||||||
|
|
@ -860,14 +860,16 @@ export function HomeView({
|
||||||
|
|
||||||
function useExamplePlugin(record: InstalledPluginRecord, chipId: string, promptText: string) {
|
function useExamplePlugin(record: InstalledPluginRecord, chipId: string, promptText: string) {
|
||||||
const projectKind = projectKindForExamplePlugin(record, chipId);
|
const projectKind = projectKindForExamplePlugin(record, chipId);
|
||||||
requestActivePlugin(record, promptText, {
|
activePluginApplyRequestRef.current += 1;
|
||||||
projectKind,
|
setActive(null);
|
||||||
chipId,
|
setActiveSkill(null);
|
||||||
inputs: {},
|
setFallbackProjectKind(projectKind);
|
||||||
inputFields: [],
|
setPendingApplyId(null);
|
||||||
queryTemplate: null,
|
setPendingChipId(null);
|
||||||
replaceWithoutConfirmation: true,
|
setError(null);
|
||||||
});
|
setPrompt(promptText);
|
||||||
|
setPromptEditedByUser(false);
|
||||||
|
focusPromptAtEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePluginContext(pluginId: string) {
|
function removePluginContext(pluginId: string) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@ afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
function makePlugin(id: string, mode: string, title = id): InstalledPluginRecord {
|
function makePlugin(
|
||||||
|
id: string,
|
||||||
|
mode: string,
|
||||||
|
title = id,
|
||||||
|
extraTags: string[] = [],
|
||||||
|
): InstalledPluginRecord {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
|
|
@ -36,7 +41,7 @@ function makePlugin(id: string, mode: string, title = id): InstalledPluginRecord
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
title,
|
title,
|
||||||
description: 'Plugin preset fixture',
|
description: 'Plugin preset fixture',
|
||||||
tags: [mode],
|
tags: [mode, ...extraTags],
|
||||||
od: {
|
od: {
|
||||||
mode,
|
mode,
|
||||||
useCase: {
|
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', () => {
|
it('disables every visible chip while a plugin apply is in flight', () => {
|
||||||
renderHero({ pendingPluginId: 'od-figma-migration', pendingChipId: 'figma' });
|
renderHero({ pendingPluginId: 'od-figma-migration', pendingChipId: 'figma' });
|
||||||
for (const chip of HOME_HERO_CHIPS.filter((item) => item.group === 'create')) {
|
for (const chip of HOME_HERO_CHIPS.filter((item) => item.group === 'create')) {
|
||||||
|
|
|
||||||
|
|
@ -581,6 +581,55 @@ describe('HomeView prompt handoff', () => {
|
||||||
expect(screen.queryByRole('alert')).toBeNull();
|
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 () => {
|
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) => {
|
const fetchMock = vi.fn<typeof fetch>(async (url) => {
|
||||||
if (typeof url === 'string' && url === '/api/plugins') {
|
if (typeof url === 'string' && url === '/api/plugins') {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"surface": "image",
|
"surface": "image",
|
||||||
"preview": {
|
"preview": {
|
||||||
"type": "image",
|
"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": {
|
"useCase": {
|
||||||
"query": {
|
"query": {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue