mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-12 23:01:05 +00:00
feat(suite): enrich overview with entry skill guidance
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
b92d8da70f
commit
ff0a1cd4cb
5 changed files with 73 additions and 11 deletions
|
|
@ -1778,6 +1778,9 @@
|
|||
"viewMember": "View skill: {{name}}",
|
||||
"pinnedVersion": "Pinned version v{{version}}",
|
||||
"entrySkill": "Entry skill",
|
||||
"startWithEntry": "Start with the entry skill",
|
||||
"startWithEntryDescription": "Installing this suite gives you {{count}} version-pinned skills. Open the entry skill first to understand the primary way to use them together.",
|
||||
"viewEntrySkill": "View entry skill",
|
||||
"versionHistory": "Version history",
|
||||
"validationRequired": "Complete the required fields and select at least one skill",
|
||||
"draftUpdated": "Suite draft updated",
|
||||
|
|
|
|||
|
|
@ -1775,6 +1775,9 @@
|
|||
"viewMember": "Открыть навык: {{name}}",
|
||||
"pinnedVersion": "Зафиксированная версия v{{version}}",
|
||||
"entrySkill": "Входной навык",
|
||||
"startWithEntry": "Начните с входного навыка",
|
||||
"startWithEntryDescription": "После установки набора вы получите {{count}} навыков с зафиксированными версиями. Сначала откройте входной навык, чтобы понять основной сценарий их совместного использования.",
|
||||
"viewEntrySkill": "Открыть входной навык",
|
||||
"versionHistory": "История версий",
|
||||
"validationRequired": "Заполните обязательные поля и выберите хотя бы один навык",
|
||||
"draftUpdated": "Черновик набора обновлён",
|
||||
|
|
|
|||
|
|
@ -1777,6 +1777,9 @@
|
|||
"viewMember": "查看技能:{{name}}",
|
||||
"pinnedVersion": "固定版本 v{{version}}",
|
||||
"entrySkill": "入口技能",
|
||||
"startWithEntry": "从入口技能开始",
|
||||
"startWithEntryDescription": "安装套件后将获得 {{count}} 个固定版本的技能。先打开入口技能,了解这组技能的主要使用方式。",
|
||||
"viewEntrySkill": "查看入口技能",
|
||||
"versionHistory": "版本历史",
|
||||
"validationRequired": "请填写必填项并至少选择一个技能",
|
||||
"draftUpdated": "套件草稿已更新",
|
||||
|
|
|
|||
|
|
@ -93,13 +93,19 @@ describe('SuiteDetailPage', () => {
|
|||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('separates the Markdown overview from browsable member skills', () => {
|
||||
it('adds entry skill guidance to the overview and keeps the full member grid separate', () => {
|
||||
mocks.detail = { data: suite(), isLoading: false, error: null }
|
||||
|
||||
render(<SuiteDetailPage />)
|
||||
|
||||
expect(screen.getByTestId('suite-overview').textContent).toContain('Run the entry skill first.')
|
||||
expect(screen.queryByText('Medical Records')).toBeNull()
|
||||
expect(screen.getByText('suite.startWithEntry')).not.toBeNull()
|
||||
expect(screen.getByText('suite.startWithEntryDescription')).not.toBeNull()
|
||||
expect(screen.getByText('Medical Records')).not.toBeNull()
|
||||
expect(screen.getByText('@global/medical-records@1.0.0')).not.toBeNull()
|
||||
expect(screen.getByRole('link', { name: 'suite.viewEntrySkill' }).getAttribute('href'))
|
||||
.toContain('/space/global/medical-records')
|
||||
expect(screen.queryByText('@global/deleted-helper')).toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'suite.membersTab' }))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { suiteBlockingReasonLabel, suiteStatusLabel, suiteVisibilityLabel } from
|
|||
import { SuiteManagementActions } from '@/features/suite/suite-management-actions'
|
||||
import { MarkdownRenderer } from '@/features/skill/markdown-renderer'
|
||||
import { Card } from '@/shared/ui/card'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Button, buttonVariants } from '@/shared/ui/button'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs'
|
||||
import { SkeletonList } from '@/shared/components/skeleton-loader'
|
||||
import { NamespaceBadge } from '@/shared/components/namespace-badge'
|
||||
|
|
@ -53,6 +53,7 @@ export function SuiteDetailPage() {
|
|||
const hasPrimaryActions = suite.allowedActions.includes('EDIT')
|
||||
|| suite.allowedActions.includes('SUBMIT')
|
||||
|| suite.allowedActions.includes('PUBLISH_PRIVATE')
|
||||
const entryMember = suite.members.find(member => member.entry)
|
||||
|
||||
return (
|
||||
<div className={cn(APP_SHELL_PAGE_CLASS_NAME, 'mx-auto max-w-6xl')}>
|
||||
|
|
@ -94,14 +95,60 @@ export function SuiteDetailPage() {
|
|||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="mt-6">
|
||||
<Card className="p-8">
|
||||
{suite.overview ? (
|
||||
<MarkdownRenderer content={suite.overview} />
|
||||
) : (
|
||||
<p className="text-sm leading-7 text-muted-foreground">
|
||||
{suite.summary || t('suite.noOverview')}
|
||||
</p>
|
||||
)}
|
||||
<Card className="overflow-hidden">
|
||||
<div className="p-6 sm:p-8">
|
||||
{suite.overview ? (
|
||||
<MarkdownRenderer content={suite.overview} />
|
||||
) : (
|
||||
<p className="text-sm leading-7 text-muted-foreground">
|
||||
{suite.summary || t('suite.noOverview')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{entryMember ? (
|
||||
<section className="border-t border-border/60 bg-secondary/30 p-6 sm:p-8">
|
||||
<div className="flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<h2 className="font-heading text-lg font-semibold text-foreground">
|
||||
{t('suite.startWithEntry')}
|
||||
</h2>
|
||||
<p className="mt-1 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
{t('suite.startWithEntryDescription', { count: suite.members.length })}
|
||||
</p>
|
||||
</div>
|
||||
{entryMember.browsable && !entryMember.blockingReason
|
||||
&& entryMember.skillId && entryMember.skillVersionId ? (
|
||||
<Link
|
||||
to="/space/$namespace/$slug"
|
||||
params={{ namespace: entryMember.namespace, slug: entryMember.slug }}
|
||||
search={{ returnTo }}
|
||||
className={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'shrink-0 gap-1.5')}
|
||||
>
|
||||
{t('suite.viewEntrySkill')}
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<p className="flex min-w-0 items-center gap-2 font-semibold text-foreground">
|
||||
<Wrench className="h-4 w-4 shrink-0 text-primary" aria-hidden="true" />
|
||||
<span className="truncate">
|
||||
{entryMember.displayName || `@${entryMember.namespace}/${entryMember.slug}`}
|
||||
</span>
|
||||
</p>
|
||||
<p className="break-all font-mono text-xs text-muted-foreground">
|
||||
@{entryMember.namespace}/{entryMember.slug}@{entryMember.version}
|
||||
</p>
|
||||
{entryMember.blockingReason ? (
|
||||
<span className="text-xs font-medium text-destructive">
|
||||
{suiteBlockingReasonLabel(t, entryMember.blockingReason)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue