create-windy 0.6.7 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +158 -95
  3. package/package.json +1 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +4 -2
  6. package/template/apps/agent-server/README.md +4 -4
  7. package/template/apps/server/src/data-access/drizzle-predicate.ts +16 -6
  8. package/template/apps/server/src/runtime-development.ts +1 -0
  9. package/template/apps/web/src/layout/AppSidebar.vue +1 -0
  10. package/template/apps/web/src/pages/ui/UiComponentPage.vue +7 -0
  11. package/template/apps/web/src/pages/ui/UiComponentPage.webtest.ts +94 -0
  12. package/template/apps/web/src/pages/ui/component-catalog.ts +248 -0
  13. package/template/apps/web/src/pages/ui/components/ComponentPreviewCard.vue +50 -0
  14. package/template/apps/web/src/pages/ui/components/DataComponentShowcases.vue +131 -0
  15. package/template/apps/web/src/pages/ui/components/FeedbackComponentShowcases.vue +158 -0
  16. package/template/apps/web/src/pages/ui/components/FoundationComponentShowcases.vue +129 -0
  17. package/template/apps/web/src/pages/ui/components/NavigationComponentShowcases.vue +155 -0
  18. package/template/apps/web/src/pages/ui/components/ProjectComponentShowcases.vue +31 -0
  19. package/template/apps/web/src/pages/ui/components/UiComponentGallery.vue +238 -0
  20. package/template/apps/web/src/router/index.ts +2 -0
  21. package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -0
  22. package/template/apps/web/src/router/platform-admin-route-bindings.ts +3 -1
  23. package/template/docs/architecture/ai-egress.md +1 -1
  24. package/template/docs/architecture/ai-runtime-readiness.md +2 -2
  25. package/template/docs/architecture/ai-runtime.md +15 -15
  26. package/template/docs/architecture/module-secret-resolution.md +4 -4
  27. package/template/docs/architecture/report-staging.md +1 -1
  28. package/template/docs/development/business-web-routes.md +3 -1
  29. package/template/docs/development/custom-login-page.md +3 -3
  30. package/template/docs/development/ui-component-gallery.md +44 -0
  31. package/template/docs/license/license-offline-activation-code.md +2 -2
  32. package/template/docs/operations/incremental-delivery.md +10 -9
  33. package/template/docs/platform/agent-runtime.md +4 -4
  34. package/template/docs/platform/document-ai-runtime.md +3 -3
  35. package/template/docs/platform/speech-ai-runtime.md +1 -1
  36. package/template/docs/platform/vision-ai-runtime.md +1 -1
  37. package/template/docs/product/built-in-roles.md +9 -1
  38. package/template/packages/modules/package.json +1 -1
  39. package/template/packages/modules/src/system-admin-routes.ts +8 -0
  40. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  41. package/template/packages/modules/src/system-features.ts +5 -0
  42. package/template/packages/modules/src/system-module-catalog.ts +2 -1
  43. package/template/packages/modules/src/system-modules.ts +11 -0
  44. package/template/packages/modules/src/system-permissions.ts +2 -0
  45. package/template/packages/modules/src/system-ui-components.test.ts +36 -0
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { ArrowRightIcon, PlusIcon, Trash2Icon } from "@lucide/vue";
3
+ import { shallowRef } from "vue";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Checkbox } from "@/components/ui/checkbox";
7
+ import { Input } from "@/components/ui/input";
8
+ import { Separator } from "@/components/ui/separator";
9
+ import { Skeleton } from "@/components/ui/skeleton";
10
+ import { Switch } from "@/components/ui/switch";
11
+ import { componentCatalogById, type ComponentId } from "../component-catalog";
12
+ import ComponentPreviewCard from "./ComponentPreviewCard.vue";
13
+
14
+ const props = defineProps<{
15
+ visibleIds: readonly ComponentId[];
16
+ }>();
17
+
18
+ const query = shallowRef("");
19
+ const selected = shallowRef(true);
20
+ const enabled = shallowRef(true);
21
+
22
+ function isVisible(id: ComponentId) {
23
+ return props.visibleIds.includes(id);
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <ComponentPreviewCard
29
+ v-if="isVisible('button')"
30
+ :item="componentCatalogById.button"
31
+ >
32
+ <div class="flex flex-wrap items-center gap-2">
33
+ <Button><PlusIcon />新建记录</Button>
34
+ <Button variant="outline">取消</Button>
35
+ <Button variant="secondary">保存草稿</Button>
36
+ <Button variant="destructive"><Trash2Icon />删除</Button>
37
+ <Button variant="link">查看规范<ArrowRightIcon /></Button>
38
+ </div>
39
+ </ComponentPreviewCard>
40
+
41
+ <ComponentPreviewCard
42
+ v-if="isVisible('badge')"
43
+ :item="componentCatalogById.badge"
44
+ >
45
+ <div class="flex flex-wrap items-center gap-2">
46
+ <Badge>已启用</Badge>
47
+ <Badge variant="secondary">草稿</Badge>
48
+ <Badge variant="outline">待处理</Badge>
49
+ <Badge variant="destructive">异常</Badge>
50
+ </div>
51
+ </ComponentPreviewCard>
52
+
53
+ <ComponentPreviewCard
54
+ v-if="isVisible('input')"
55
+ :item="componentCatalogById.input"
56
+ >
57
+ <label class="grid w-full max-w-sm gap-2 text-sm font-medium">
58
+ 搜索组件
59
+ <Input v-model="query" placeholder="输入名称或用途" />
60
+ <span class="font-normal text-muted-foreground">
61
+ 当前输入:{{ query || "暂无" }}
62
+ </span>
63
+ </label>
64
+ </ComponentPreviewCard>
65
+
66
+ <ComponentPreviewCard
67
+ v-if="isVisible('checkbox')"
68
+ :item="componentCatalogById.checkbox"
69
+ >
70
+ <label class="flex items-center gap-3 text-sm">
71
+ <Checkbox v-model="selected" aria-label="包含已停用组件" />
72
+ <span>
73
+ <span class="block font-medium">包含已停用组件</span>
74
+ <span class="text-muted-foreground">
75
+ {{ selected ? "已包含" : "仅显示可用组件" }}
76
+ </span>
77
+ </span>
78
+ </label>
79
+ </ComponentPreviewCard>
80
+
81
+ <ComponentPreviewCard
82
+ v-if="isVisible('switch')"
83
+ :item="componentCatalogById.switch"
84
+ >
85
+ <div
86
+ class="flex w-full items-center justify-between gap-4 rounded-lg border bg-background p-3"
87
+ >
88
+ <div>
89
+ <p class="text-sm font-medium">组件预览</p>
90
+ <p class="text-xs text-muted-foreground">
91
+ {{ enabled ? "预览已启用" : "预览已暂停" }}
92
+ </p>
93
+ </div>
94
+ <Switch v-model="enabled" aria-label="切换组件预览" />
95
+ </div>
96
+ </ComponentPreviewCard>
97
+
98
+ <ComponentPreviewCard
99
+ v-if="isVisible('separator')"
100
+ :item="componentCatalogById.separator"
101
+ >
102
+ <div class="w-full max-w-sm text-sm">
103
+ <p class="font-medium">组件信息</p>
104
+ <p class="mt-1 text-muted-foreground">由 Windy Web 统一维护。</p>
105
+ <Separator class="my-4" />
106
+ <div class="flex h-5 items-center gap-3">
107
+ <span>稳定</span>
108
+ <Separator orientation="vertical" />
109
+ <span>可访问</span>
110
+ <Separator orientation="vertical" />
111
+ <span>可复用</span>
112
+ </div>
113
+ </div>
114
+ </ComponentPreviewCard>
115
+
116
+ <ComponentPreviewCard
117
+ v-if="isVisible('skeleton')"
118
+ :item="componentCatalogById.skeleton"
119
+ >
120
+ <div class="flex w-full max-w-sm items-center gap-3">
121
+ <Skeleton class="size-10 rounded-full" />
122
+ <div class="grid flex-1 gap-2">
123
+ <Skeleton class="h-3 w-1/3" />
124
+ <Skeleton class="h-3 w-full" />
125
+ <Skeleton class="h-3 w-4/5" />
126
+ </div>
127
+ </div>
128
+ </ComponentPreviewCard>
129
+ </template>
@@ -0,0 +1,155 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ BellIcon,
4
+ ChevronDownIcon,
5
+ LayoutDashboardIcon,
6
+ SettingsIcon,
7
+ } from "@lucide/vue";
8
+ import { shallowRef } from "vue";
9
+ import {
10
+ Breadcrumb,
11
+ BreadcrumbItem,
12
+ BreadcrumbLink,
13
+ BreadcrumbList,
14
+ BreadcrumbPage,
15
+ BreadcrumbSeparator,
16
+ } from "@/components/ui/breadcrumb";
17
+ import { Button } from "@/components/ui/button";
18
+ import {
19
+ DropdownMenu,
20
+ DropdownMenuCheckboxItem,
21
+ DropdownMenuContent,
22
+ DropdownMenuItem,
23
+ DropdownMenuLabel,
24
+ DropdownMenuSeparator,
25
+ DropdownMenuShortcut,
26
+ DropdownMenuTrigger,
27
+ } from "@/components/ui/dropdown-menu";
28
+ import {
29
+ SidebarGroup,
30
+ SidebarGroupContent,
31
+ SidebarGroupLabel,
32
+ SidebarMenu,
33
+ SidebarMenuBadge,
34
+ SidebarMenuItem,
35
+ } from "@/components/ui/sidebar";
36
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
37
+ import { componentCatalogById, type ComponentId } from "../component-catalog";
38
+ import ComponentPreviewCard from "./ComponentPreviewCard.vue";
39
+
40
+ const props = defineProps<{
41
+ visibleIds: readonly ComponentId[];
42
+ }>();
43
+
44
+ const compactMode = shallowRef(false);
45
+
46
+ function isVisible(id: ComponentId) {
47
+ return props.visibleIds.includes(id);
48
+ }
49
+ </script>
50
+
51
+ <template>
52
+ <ComponentPreviewCard
53
+ v-if="isVisible('breadcrumb')"
54
+ :item="componentCatalogById.breadcrumb"
55
+ >
56
+ <Breadcrumb>
57
+ <BreadcrumbList>
58
+ <BreadcrumbItem>
59
+ <BreadcrumbLink href="/admin">工作台</BreadcrumbLink>
60
+ </BreadcrumbItem>
61
+ <BreadcrumbSeparator />
62
+ <BreadcrumbItem>
63
+ <BreadcrumbLink href="/ui/component">UI 组件</BreadcrumbLink>
64
+ </BreadcrumbItem>
65
+ <BreadcrumbSeparator />
66
+ <BreadcrumbItem>
67
+ <BreadcrumbPage>面包屑</BreadcrumbPage>
68
+ </BreadcrumbItem>
69
+ </BreadcrumbList>
70
+ </Breadcrumb>
71
+ </ComponentPreviewCard>
72
+
73
+ <ComponentPreviewCard
74
+ v-if="isVisible('tabs')"
75
+ :item="componentCatalogById.tabs"
76
+ >
77
+ <Tabs default-value="preview" class="w-full">
78
+ <TabsList aria-label="组件页签示例">
79
+ <TabsTrigger value="preview">预览</TabsTrigger>
80
+ <TabsTrigger value="usage">使用说明</TabsTrigger>
81
+ </TabsList>
82
+ <TabsContent
83
+ value="preview"
84
+ class="rounded-lg border bg-background p-3 text-sm"
85
+ >
86
+ 在真实主题和间距系统中检查组件。
87
+ </TabsContent>
88
+ <TabsContent
89
+ value="usage"
90
+ class="rounded-lg border bg-background p-3 text-sm"
91
+ >
92
+ 从组件目录导入,不复制基础组件源码。
93
+ </TabsContent>
94
+ </Tabs>
95
+ </ComponentPreviewCard>
96
+
97
+ <ComponentPreviewCard
98
+ v-if="isVisible('dropdown-menu')"
99
+ :item="componentCatalogById['dropdown-menu']"
100
+ >
101
+ <DropdownMenu>
102
+ <DropdownMenuTrigger as-child>
103
+ <Button variant="outline">页面操作<ChevronDownIcon /></Button>
104
+ </DropdownMenuTrigger>
105
+ <DropdownMenuContent align="start" class="w-52">
106
+ <DropdownMenuLabel>显示设置</DropdownMenuLabel>
107
+ <DropdownMenuCheckboxItem v-model="compactMode">
108
+ 紧凑模式
109
+ </DropdownMenuCheckboxItem>
110
+ <DropdownMenuSeparator />
111
+ <DropdownMenuItem>
112
+ 刷新组件
113
+ <DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
114
+ </DropdownMenuItem>
115
+ </DropdownMenuContent>
116
+ </DropdownMenu>
117
+ </ComponentPreviewCard>
118
+
119
+ <ComponentPreviewCard
120
+ v-if="isVisible('sidebar')"
121
+ :item="componentCatalogById.sidebar"
122
+ >
123
+ <div
124
+ class="w-full max-w-xs overflow-hidden rounded-lg border bg-sidebar text-sidebar-foreground"
125
+ >
126
+ <SidebarGroup>
127
+ <SidebarGroupLabel>组件导航</SidebarGroupLabel>
128
+ <SidebarGroupContent>
129
+ <SidebarMenu>
130
+ <SidebarMenuItem>
131
+ <Button
132
+ variant="ghost"
133
+ class="w-full justify-start bg-sidebar-accent"
134
+ >
135
+ <LayoutDashboardIcon />总览
136
+ </Button>
137
+ </SidebarMenuItem>
138
+ <SidebarMenuItem>
139
+ <Button variant="ghost" class="w-full justify-start">
140
+ <SettingsIcon />表单组件
141
+ </Button>
142
+ <SidebarMenuBadge class="top-1.5">7</SidebarMenuBadge>
143
+ </SidebarMenuItem>
144
+ <SidebarMenuItem>
145
+ <Button variant="ghost" class="w-full justify-start">
146
+ <BellIcon />反馈组件
147
+ </Button>
148
+ <SidebarMenuBadge class="top-1.5">5</SidebarMenuBadge>
149
+ </SidebarMenuItem>
150
+ </SidebarMenu>
151
+ </SidebarGroupContent>
152
+ </SidebarGroup>
153
+ </div>
154
+ </ComponentPreviewCard>
155
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { Badge } from "@/components/ui/badge";
3
+ import type { ComponentCatalogItem } from "../component-catalog";
4
+ import ComponentPreviewCard from "./ComponentPreviewCard.vue";
5
+
6
+ defineProps<{
7
+ items: readonly ComponentCatalogItem[];
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <ComponentPreviewCard v-for="item in items" :key="item.id" :item="item">
13
+ <div class="grid w-full gap-3">
14
+ <p class="text-sm text-muted-foreground">
15
+ 已从当前项目的组件入口自动发现。交互预览可在项目内按需补充。
16
+ </p>
17
+ <div class="flex flex-wrap gap-2">
18
+ <Badge
19
+ v-for="exportName in item.exports"
20
+ :key="exportName"
21
+ variant="outline"
22
+ >
23
+ {{ exportName }}
24
+ </Badge>
25
+ <Badge v-if="item.exports.length === 0" variant="secondary">
26
+ 暂无具名导出
27
+ </Badge>
28
+ </div>
29
+ </div>
30
+ </ComponentPreviewCard>
31
+ </template>
@@ -0,0 +1,238 @@
1
+ <script setup lang="ts">
2
+ import { BlocksIcon, SearchIcon, XIcon } from "@lucide/vue";
3
+ import { computed, shallowRef } from "vue";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+ import {
7
+ componentCatalog,
8
+ componentCategories,
9
+ type ComponentCategoryId,
10
+ } from "../component-catalog";
11
+ import DataComponentShowcases from "./DataComponentShowcases.vue";
12
+ import FeedbackComponentShowcases from "./FeedbackComponentShowcases.vue";
13
+ import FoundationComponentShowcases from "./FoundationComponentShowcases.vue";
14
+ import NavigationComponentShowcases from "./NavigationComponentShowcases.vue";
15
+ import ProjectComponentShowcases from "./ProjectComponentShowcases.vue";
16
+
17
+ type CategoryFilter = "all" | ComponentCategoryId;
18
+
19
+ const searchQuery = shallowRef("");
20
+ const activeCategory = shallowRef<CategoryFilter>("all");
21
+ const normalizedQuery = computed(() =>
22
+ searchQuery.value.trim().toLocaleLowerCase(),
23
+ );
24
+ const visibleComponents = computed(() =>
25
+ componentCatalog.filter((component) => {
26
+ if (
27
+ activeCategory.value !== "all" &&
28
+ component.category !== activeCategory.value
29
+ ) {
30
+ return false;
31
+ }
32
+ if (!normalizedQuery.value) return true;
33
+ return [
34
+ component.name,
35
+ component.label,
36
+ component.description,
37
+ component.exportPath,
38
+ ...component.keywords,
39
+ ].some((value) =>
40
+ value.toLocaleLowerCase().includes(normalizedQuery.value),
41
+ );
42
+ }),
43
+ );
44
+ const visibleIds = computed(() => visibleComponents.value.map(({ id }) => id));
45
+ const visibleProjectComponents = computed(() =>
46
+ visibleComponents.value.filter(({ category }) => category === "project"),
47
+ );
48
+ const visibleCategories = computed(() =>
49
+ componentCategories.filter((category) =>
50
+ visibleComponents.value.some(
51
+ (component) => component.category === category.id,
52
+ ),
53
+ ),
54
+ );
55
+ const categoryCounts = Object.fromEntries(
56
+ componentCategories.map((category) => [
57
+ category.id,
58
+ componentCatalog.filter((component) => component.category === category.id)
59
+ .length,
60
+ ]),
61
+ ) as Readonly<Record<ComponentCategoryId, number>>;
62
+
63
+ function selectCategory(category: CategoryFilter) {
64
+ activeCategory.value = category;
65
+ }
66
+
67
+ function clearFilters() {
68
+ searchQuery.value = "";
69
+ activeCategory.value = "all";
70
+ }
71
+ </script>
72
+
73
+ <template>
74
+ <div class="mx-auto grid w-full max-w-7xl gap-6">
75
+ <header
76
+ class="overflow-hidden rounded-2xl border bg-card text-card-foreground"
77
+ >
78
+ <div
79
+ class="grid gap-6 p-6 lg:grid-cols-[minmax(0,1fr)_minmax(24rem,0.72fr)] lg:p-8"
80
+ >
81
+ <div class="max-w-2xl">
82
+ <div
83
+ class="mb-4 flex size-11 items-center justify-center rounded-xl bg-primary text-primary-foreground"
84
+ >
85
+ <BlocksIcon class="size-5" />
86
+ </div>
87
+ <p
88
+ class="text-xs font-semibold tracking-[0.18em] text-muted-foreground uppercase"
89
+ >
90
+ Windy UI Registry
91
+ </p>
92
+ <h1 class="mt-2 text-3xl font-semibold tracking-tight">组件总览</h1>
93
+ <p class="mt-3 max-w-xl text-sm leading-6 text-muted-foreground">
94
+ 在真实主题、间距和交互上下文中检查 Web 基础组件。每个示例均直接引用
95
+ <code class="rounded bg-muted px-1 py-0.5">components/ui</code>
96
+ 中的封装。
97
+ </p>
98
+ </div>
99
+
100
+ <div class="grid content-start gap-2 rounded-xl border bg-muted/20 p-4">
101
+ <div class="flex items-center justify-between gap-4 border-b pb-3">
102
+ <span class="text-sm font-medium">注册清单</span>
103
+ <code class="text-xs text-muted-foreground">
104
+ {{ componentCatalog.length }} 组件族 ·
105
+ {{ componentCategories.length }} 类
106
+ </code>
107
+ </div>
108
+ <div
109
+ v-for="category in componentCategories"
110
+ :key="category.id"
111
+ class="grid grid-cols-[6rem_1fr_auto] items-center gap-3 py-1 text-xs"
112
+ >
113
+ <code class="text-muted-foreground">{{ category.id }}</code>
114
+ <span>{{ category.label }}</span>
115
+ <span class="tabular-nums text-muted-foreground">
116
+ {{ categoryCounts[category.id] }}
117
+ </span>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </header>
122
+
123
+ <section
124
+ aria-label="组件筛选"
125
+ class="sticky top-0 z-10 rounded-xl border bg-background/95 p-3 shadow-sm backdrop-blur"
126
+ >
127
+ <div
128
+ class="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between"
129
+ >
130
+ <div class="relative w-full xl:max-w-sm">
131
+ <SearchIcon
132
+ class="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
133
+ />
134
+ <Input
135
+ v-model="searchQuery"
136
+ type="search"
137
+ aria-label="搜索组件"
138
+ placeholder="搜索名称、用途或导出路径"
139
+ class="pl-8"
140
+ />
141
+ </div>
142
+ <div class="flex flex-wrap gap-2" aria-label="按分类筛选">
143
+ <Button
144
+ size="sm"
145
+ :variant="activeCategory === 'all' ? 'default' : 'outline'"
146
+ data-category-id="all"
147
+ @click="selectCategory('all')"
148
+ >
149
+ 全部
150
+ <span class="tabular-nums opacity-70">{{
151
+ componentCatalog.length
152
+ }}</span>
153
+ </Button>
154
+ <Button
155
+ v-for="category in componentCategories"
156
+ :key="category.id"
157
+ size="sm"
158
+ :variant="activeCategory === category.id ? 'default' : 'outline'"
159
+ :data-category-id="category.id"
160
+ @click="selectCategory(category.id)"
161
+ >
162
+ {{ category.label }}
163
+ <span class="tabular-nums opacity-70">{{
164
+ categoryCounts[category.id]
165
+ }}</span>
166
+ </Button>
167
+ </div>
168
+ </div>
169
+ <p class="mt-2 text-xs text-muted-foreground" aria-live="polite">
170
+ 当前显示 {{ visibleComponents.length }} /
171
+ {{ componentCatalog.length }} 个组件族
172
+ </p>
173
+ </section>
174
+
175
+ <template v-if="visibleComponents.length">
176
+ <section
177
+ v-for="category in visibleCategories"
178
+ :key="category.id"
179
+ :aria-labelledby="`category-${category.id}`"
180
+ class="grid gap-4"
181
+ >
182
+ <header
183
+ class="flex flex-wrap items-end justify-between gap-2 border-b pb-3"
184
+ >
185
+ <div>
186
+ <p class="text-xs font-medium text-muted-foreground">
187
+ {{ category.id }}
188
+ </p>
189
+ <h2
190
+ :id="`category-${category.id}`"
191
+ class="mt-1 text-xl font-semibold"
192
+ >
193
+ {{ category.label }}
194
+ </h2>
195
+ </div>
196
+ <p class="text-sm text-muted-foreground">
197
+ {{ category.description }}
198
+ </p>
199
+ </header>
200
+
201
+ <div class="grid items-start gap-4 lg:grid-cols-2">
202
+ <FoundationComponentShowcases
203
+ v-if="category.id === 'foundation'"
204
+ :visible-ids="visibleIds"
205
+ />
206
+ <NavigationComponentShowcases
207
+ v-else-if="category.id === 'navigation'"
208
+ :visible-ids="visibleIds"
209
+ />
210
+ <FeedbackComponentShowcases
211
+ v-else-if="category.id === 'feedback'"
212
+ :visible-ids="visibleIds"
213
+ />
214
+ <DataComponentShowcases
215
+ v-else-if="category.id === 'data'"
216
+ :visible-ids="visibleIds"
217
+ />
218
+ <ProjectComponentShowcases v-else :items="visibleProjectComponents" />
219
+ </div>
220
+ </section>
221
+ </template>
222
+
223
+ <section
224
+ v-else
225
+ class="grid min-h-64 place-items-center rounded-xl border border-dashed p-8 text-center"
226
+ >
227
+ <div>
228
+ <p class="font-medium">没有匹配的组件</p>
229
+ <p class="mt-2 text-sm text-muted-foreground">
230
+ 调整关键词或清除分类后重试。
231
+ </p>
232
+ <Button variant="outline" class="mt-4" @click="clearFilters">
233
+ <XIcon />清除筛选
234
+ </Button>
235
+ </div>
236
+ </section>
237
+ </div>
238
+ </template>
@@ -29,6 +29,7 @@ import SystemResourceRoutePage from "@/pages/system/SystemResourceRoutePage.vue"
29
29
  import GlobalSettingsPage from "@/pages/settings/GlobalSettingsPage.vue";
30
30
  // @windy-module system.settings end
31
31
  import PersonalSettingsPage from "@/pages/profile/PersonalSettingsPage.vue";
32
+ import UiComponentPage from "@/pages/ui/UiComponentPage.vue";
32
33
  // @windy-module system.configuration begin
33
34
  import ModuleConfigurationPage from "@/pages/configuration/ModuleConfigurationPage.vue";
34
35
  // @windy-module system.configuration end
@@ -84,6 +85,7 @@ const platformAdminPageComponents: Partial<
84
85
  > = {
85
86
  resource: SystemResourceRoutePage,
86
87
  profile: PersonalSettingsPage,
88
+ "ui-components": UiComponentPage,
87
89
  // @windy-module system.settings begin
88
90
  settings: GlobalSettingsPage,
89
91
  // @windy-module system.settings end
@@ -47,6 +47,7 @@ describe("Manifest 菜单与 Router 契约", () => {
47
47
  expect(definitions.some(({ path }) => path === "/admin/profile")).toBe(
48
48
  true,
49
49
  );
50
+ expect(definitions.some(({ path }) => path === "/ui/component")).toBe(true);
50
51
  expect(definitions.some(({ path }) => path === "/admin/work-orders")).toBe(
51
52
  false,
52
53
  );
@@ -8,7 +8,8 @@ export type PlatformAdminPageKey =
8
8
  | "notification"
9
9
  | "operations"
10
10
  | "ai-runtime"
11
- | "configuration";
11
+ | "configuration"
12
+ | "ui-components";
12
13
 
13
14
  export interface PlatformAdminRouteBinding {
14
15
  readonly routeKey: string;
@@ -37,6 +38,7 @@ export const platformAdminRouteBindings: readonly PlatformAdminRouteBinding[] =
37
38
  resourceKey: "dictionaries",
38
39
  },
39
40
  { routeKey: "system.feature", page: "resource", resourceKey: "features" },
41
+ { routeKey: "system.ui-components", page: "ui-components" },
40
42
  // @windy-module system.license begin
41
43
  {
42
44
  routeKey: "system.license",
@@ -72,7 +72,7 @@ OpenAI-compatible Adapter 只使用 Agent Server 启动配置的一个 Endpoint
72
72
  配置还必须通过 `AI_PROVIDER_HTTPS_HOST_ALLOWLIST` 的精确 Host 匹配;不支持通配符、
73
73
  scheme、port 或重复 Host。内网 HTTP 继续受既有私有地址校验约束。
74
74
 
75
- Southwind AI Server 向 Agent Server 只发送 Operation 的 policy mode 与 Deployment
75
+ Windy Server 向 Agent Server 只发送 Operation 的 policy mode 与 Deployment
76
76
  Key。Agent Server 在发出模型请求前核对:
77
77
 
78
78
  1. policy 必须是 `configured-endpoint`;
@@ -12,7 +12,7 @@ deployment pin 和真实 `/health/ready` snapshot 投影新版 Operation;未
12
12
 
13
13
  ## 目标
14
14
 
15
- AI Runtime Readiness V1 为 Southwind Server、Module Operation Host 和各独立
15
+ AI Runtime Readiness V1 为 Windy Server、Module Operation Host 和各独立
16
16
  Runtime 提供同一套失败关闭契约。它回答的是:
17
17
 
18
18
  > 当前这个 Operation 能否在一个已经过真实探测、身份和 release 均未漂移的
@@ -440,7 +440,7 @@ Readiness 不替代以下权威检查:
440
440
  - Provider egress、DLP 和 Tool network policy;
441
441
  - Run 幂等、取消、审计和输出校验。
442
442
 
443
- 这些 Guard 仍由 Southwind Server 在每次执行的相应阶段重新检查。对未授权调用方,
443
+ 这些 Guard 仍由 Windy Server 在每次执行的相应阶段重新检查。对未授权调用方,
444
444
  Host 不应通过 readiness 暴露跨模块或跨租户的 Deployment 细节。
445
445
 
446
446
  Readiness 也不是 GPU、CPU、显存、队列或并发槽位的资源预留。resource limits 描述该