vue-agent-start 0.1.0

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 (169) hide show
  1. package/package.json +30 -0
  2. package/pnpm-workspace.yaml +3 -0
  3. package/src/agent-flow/adapter/backend.ts +99 -0
  4. package/src/agent-flow/components/ChatIframePanel.vue +773 -0
  5. package/src/agent-flow/components/DatasetItemCard.vue +313 -0
  6. package/src/agent-flow/components/MemoryWindow.vue +104 -0
  7. package/src/agent-flow/components/PromptEditor.vue +804 -0
  8. package/src/agent-flow/components/PromptEditorTagPanel.vue +460 -0
  9. package/src/agent-flow/components/SvgIcon.vue +32 -0
  10. package/src/agent-flow/components/ToolItemCard.vue +169 -0
  11. package/src/agent-flow/components/VariableCard.vue +161 -0
  12. package/src/agent-flow/components/VariableSelector.vue +162 -0
  13. package/src/agent-flow/components/chat-iframe-types.ts +35 -0
  14. package/src/agent-flow/components/stubs/DatasetChooser.vue +70 -0
  15. package/src/agent-flow/components/stubs/Embedding.vue +22 -0
  16. package/src/agent-flow/components/stubs/ModelSelect.vue +24 -0
  17. package/src/agent-flow/components/stubs/RestfulSelectCard.vue +24 -0
  18. package/src/agent-flow/components/stubs/ToolChooser.vue +96 -0
  19. package/src/agent-flow/components/stubs/TriggersItemCard.vue +126 -0
  20. package/src/agent-flow/components/stubs/VariableModal.vue +115 -0
  21. package/src/agent-flow/index.ts +73 -0
  22. package/src/agent-flow/stores/workflow.ts +72 -0
  23. package/src/agent-flow/utils/langUtils.ts +173 -0
  24. package/src/agent-flow/workflow/Ability.vue +65 -0
  25. package/src/agent-flow/workflow/AuthorizationItem.vue +101 -0
  26. package/src/agent-flow/workflow/ConversationVar.vue +42 -0
  27. package/src/agent-flow/workflow/CustomEdge.vue +94 -0
  28. package/src/agent-flow/workflow/EnvironmentVar.vue +40 -0
  29. package/src/agent-flow/workflow/FlowDesigner.vue +2802 -0
  30. package/src/agent-flow/workflow/Icon.vue +179 -0
  31. package/src/agent-flow/workflow/NodeConfig.vue +509 -0
  32. package/src/agent-flow/workflow/NodeConfigCard.vue +460 -0
  33. package/src/agent-flow/workflow/NodeHandle.vue +147 -0
  34. package/src/agent-flow/workflow/NodeHeaderToolbar.vue +77 -0
  35. package/src/agent-flow/workflow/NodeHoverToolbar.vue +176 -0
  36. package/src/agent-flow/workflow/OutputItemCard.vue +668 -0
  37. package/src/agent-flow/workflow/ParamTableItem.vue +200 -0
  38. package/src/agent-flow/workflow/StructTable.vue +202 -0
  39. package/src/agent-flow/workflow/VarInsertField.vue +517 -0
  40. package/src/agent-flow/workflow/VarSelectField.vue +223 -0
  41. package/src/agent-flow/workflow/VariableSelectorItem.vue +48 -0
  42. package/src/agent-flow/workflow/WfField.vue +206 -0
  43. package/src/agent-flow/workflow/_config-base.css +470 -0
  44. package/src/agent-flow/workflow/composables/useNode.js +55 -0
  45. package/src/agent-flow/workflow/index.vue +53 -0
  46. package/src/agent-flow/workflow/nodeCard/AgentNodeCard.vue +180 -0
  47. package/src/agent-flow/workflow/nodeCard/AnswerNodeCard.vue +65 -0
  48. package/src/agent-flow/workflow/nodeCard/ClassifierCard.vue +152 -0
  49. package/src/agent-flow/workflow/nodeCard/CodeNodeCard.vue +139 -0
  50. package/src/agent-flow/workflow/nodeCard/ConditionNodeCard.vue +323 -0
  51. package/src/agent-flow/workflow/nodeCard/DocumentExtractorCard.vue +83 -0
  52. package/src/agent-flow/workflow/nodeCard/EndNodeCard.vue +123 -0
  53. package/src/agent-flow/workflow/nodeCard/HttpNodeCard.vue +451 -0
  54. package/src/agent-flow/workflow/nodeCard/HumanInputCard.vue +152 -0
  55. package/src/agent-flow/workflow/nodeCard/IterationCard.vue +66 -0
  56. package/src/agent-flow/workflow/nodeCard/KnowledgeRetrievalCard.vue +48 -0
  57. package/src/agent-flow/workflow/nodeCard/LLMNodeCard.vue +206 -0
  58. package/src/agent-flow/workflow/nodeCard/ListOperatorCard.vue +188 -0
  59. package/src/agent-flow/workflow/nodeCard/ParameterExtractorCard.vue +352 -0
  60. package/src/agent-flow/workflow/nodeCard/ServiceApiNodeCard.vue +188 -0
  61. package/src/agent-flow/workflow/nodeCard/StartNodeCard.vue +216 -0
  62. package/src/agent-flow/workflow/nodeCard/VariableAssignerCard.vue +125 -0
  63. package/src/agent-flow/workflow/nodeCard/VariableNodeCard.vue +121 -0
  64. package/src/agent-flow/workflow/nodes/AgentNode.vue +122 -0
  65. package/src/agent-flow/workflow/nodes/AnswerNode.vue +99 -0
  66. package/src/agent-flow/workflow/nodes/ClassifierNode.vue +199 -0
  67. package/src/agent-flow/workflow/nodes/CodeNode.vue +152 -0
  68. package/src/agent-flow/workflow/nodes/ConditionNode.vue +232 -0
  69. package/src/agent-flow/workflow/nodes/DocumentExtractorNode.vue +96 -0
  70. package/src/agent-flow/workflow/nodes/EndNode.vue +119 -0
  71. package/src/agent-flow/workflow/nodes/FileUploadNode.vue +97 -0
  72. package/src/agent-flow/workflow/nodes/HttpNode.vue +204 -0
  73. package/src/agent-flow/workflow/nodes/HumanInputNode.vue +110 -0
  74. package/src/agent-flow/workflow/nodes/IterationNode.vue +98 -0
  75. package/src/agent-flow/workflow/nodes/KnowledgeRetrievalNode.vue +253 -0
  76. package/src/agent-flow/workflow/nodes/LLMNode.vue +134 -0
  77. package/src/agent-flow/workflow/nodes/ListOperatorNode.vue +111 -0
  78. package/src/agent-flow/workflow/nodes/LoopNode.vue +94 -0
  79. package/src/agent-flow/workflow/nodes/ParameterExtractorNode.vue +147 -0
  80. package/src/agent-flow/workflow/nodes/ServiceApiNode.vue +161 -0
  81. package/src/agent-flow/workflow/nodes/StartNode.vue +199 -0
  82. package/src/agent-flow/workflow/nodes/TemplateNode.vue +103 -0
  83. package/src/agent-flow/workflow/nodes/UserInputNode.vue +92 -0
  84. package/src/agent-flow/workflow/nodes/VariableAssignerNode.vue +145 -0
  85. package/src/agent-flow/workflow/nodes/VariableNode.vue +130 -0
  86. package/src/agent-flow/workflow/nodes/_node-base.css +338 -0
  87. package/src/agent-flow/workflow/utils/node_card_form.ts +335 -0
  88. package/src/agent-flow/workflow/utils/node_config.ts +63 -0
  89. package/src/agent-flow/workflow/utils/workflow_utils.ts +151 -0
  90. package/src/agent-studio/adapters/springAgentStart.ts +390 -0
  91. package/src/agent-studio/api/index.ts +1 -0
  92. package/src/agent-studio/api/types.ts +173 -0
  93. package/src/agent-studio/components/AgentApiDocs.vue +500 -0
  94. package/src/agent-studio/components/AgentAppsPage.vue +1601 -0
  95. package/src/agent-studio/components/AgentCardGrid.vue +259 -0
  96. package/src/agent-studio/components/AgentChatPage.vue +553 -0
  97. package/src/agent-studio/components/AgentDebugPanel.vue +350 -0
  98. package/src/agent-studio/components/AgentLogsPanel.vue +391 -0
  99. package/src/agent-studio/components/AgentMonitorPanel.vue +204 -0
  100. package/src/agent-studio/components/AgentOrchestrate.vue +505 -0
  101. package/src/agent-studio/components/AgentPromptEditor.vue +237 -0
  102. package/src/agent-studio/components/AgentStudioShell.vue +227 -0
  103. package/src/agent-studio/components/AgentToolsPanel.vue +394 -0
  104. package/src/agent-studio/components/AgentVariablesPanel.vue +205 -0
  105. package/src/agent-studio/components/ApiKeyManager.vue +251 -0
  106. package/src/agent-studio/components/AppDesignDrawer.vue +3391 -0
  107. package/src/agent-studio/components/CreateAppModal.vue +737 -0
  108. package/src/agent-studio/components/SparkChart.vue +200 -0
  109. package/src/agent-studio/components/VariableEditorModal.vue +499 -0
  110. package/src/agent-studio/composables/useAgentStudio.ts +347 -0
  111. package/src/agent-studio/index.ts +60 -0
  112. package/src/agent-studio/panels/DrawerFlowDesigner.vue +203 -0
  113. package/src/agent-studio/panels/LogAnnotationPanel.vue +490 -0
  114. package/src/agent-studio/panels/MonitorPanel.vue +535 -0
  115. package/src/agent-studio/types.ts +242 -0
  116. package/src/index.ts +155 -0
  117. package/src/knowledge-hub/adapters/springAgentStart.ts +472 -0
  118. package/src/knowledge-hub/components/AddDocumentsWizard.vue +1452 -0
  119. package/src/knowledge-hub/components/CreateDatasetWizard.vue +1723 -0
  120. package/src/knowledge-hub/components/DatasetCardGrid.vue +467 -0
  121. package/src/knowledge-hub/components/DatasetDetailDrawer.vue +888 -0
  122. package/src/knowledge-hub/components/DatasetPicker.vue +65 -0
  123. package/src/knowledge-hub/components/DatasetPickerModal.vue +526 -0
  124. package/src/knowledge-hub/components/DatasetSettingsPanel.vue +908 -0
  125. package/src/knowledge-hub/components/DatasetSidebar.vue +781 -0
  126. package/src/knowledge-hub/components/DocumentChunksView.vue +802 -0
  127. package/src/knowledge-hub/components/DocumentTable.vue +552 -0
  128. package/src/knowledge-hub/components/HitTestingPanel.vue +74 -0
  129. package/src/knowledge-hub/components/KnowledgeApp.vue +66 -0
  130. package/src/knowledge-hub/components/KnowledgeHubApp.vue +504 -0
  131. package/src/knowledge-hub/components/RecallTestingPanelV2.vue +515 -0
  132. package/src/knowledge-hub/components/RetrievalConfigPopover.vue +270 -0
  133. package/src/knowledge-hub/components/RetrievalMethodPicker.vue +547 -0
  134. package/src/knowledge-hub/components/RetrievedList.vue +24 -0
  135. package/src/knowledge-hub/components/internal/KhDialog.vue +195 -0
  136. package/src/knowledge-hub/composables/useKnowledge.ts +89 -0
  137. package/src/knowledge-hub/i18n/index.ts +2 -0
  138. package/src/knowledge-hub/i18n/messages.ts +422 -0
  139. package/src/knowledge-hub/i18n/useKhI18n.ts +107 -0
  140. package/src/knowledge-hub/index.ts +46 -0
  141. package/src/knowledge-hub/styles/index.css +4 -0
  142. package/src/knowledge-hub/styles/tokens.css +138 -0
  143. package/src/knowledge-hub/types/api.ts +213 -0
  144. package/src/knowledge-hub/types/dataset.ts +88 -0
  145. package/src/knowledge-hub/types/document.ts +45 -0
  146. package/src/knowledge-hub/types/index.ts +9 -0
  147. package/src/knowledge-hub/types/retrieval.ts +46 -0
  148. package/src/provider-hub/components/CredentialForm.vue +103 -0
  149. package/src/provider-hub/components/DefaultModelsPanel.vue +270 -0
  150. package/src/provider-hub/components/GroupedModelSelect.vue +445 -0
  151. package/src/provider-hub/components/ModelCardGrid.vue +332 -0
  152. package/src/provider-hub/components/ModelParameterDrawer.vue +739 -0
  153. package/src/provider-hub/components/ModelPickerPopover.vue +1282 -0
  154. package/src/provider-hub/components/ProviderApp.vue +159 -0
  155. package/src/provider-hub/components/ProviderCredentialModal.vue +321 -0
  156. package/src/provider-hub/components/ProviderGallery.vue +131 -0
  157. package/src/provider-hub/components/ProviderHubShell.vue +1992 -0
  158. package/src/provider-hub/components/ProviderIcon.vue +130 -0
  159. package/src/provider-hub/composables/useProviderHub.ts +358 -0
  160. package/src/provider-hub/index.ts +43 -0
  161. package/src/provider-hub/svg/MiniMax.svg +1 -0
  162. package/src/provider-hub/svg/Volcengine.svg +1 -0
  163. package/src/provider-hub/svg/Zhipu.svg +1 -0
  164. package/src/provider-hub/svg/deepseek.svg +1 -0
  165. package/src/provider-hub/svg/moonshot.svg +1 -0
  166. package/src/provider-hub/svg/openai.svg +1 -0
  167. package/src/provider-hub/svg/qwen.svg +1 -0
  168. package/src/provider-hub/svg/siliconflow.svg +1 -0
  169. package/src/provider-hub/types.ts +256 -0
@@ -0,0 +1,467 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * DatasetCardGrid — Dify-parity knowledge-base list card grid.
4
+ *
5
+ * Emits `create` and `open(dataset)` — the host renders whichever surface it
6
+ * wants for those (KnowledgeHubApp uses the wizard + drawer). Header slot
7
+ * carries the search + filter row so hosts can extend if needed.
8
+ *
9
+ * Delete lives inside a "⋯" popup menu in the card footer (not a top-right X)
10
+ * — destructive actions should require intent, not a mis-hover.
11
+ */
12
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
13
+
14
+ import { useKhI18n } from '../i18n';
15
+ import type { DatasetCardItem } from '../types';
16
+
17
+ interface Props {
18
+ datasets: DatasetCardItem[];
19
+ /** Keyword filter (v-model'd from the parent search input). */
20
+ keyword?: string;
21
+ /** Show the leading "+新建" placeholder card. */
22
+ showCreateCard?: boolean;
23
+ disabledReason?: string;
24
+ }
25
+
26
+ const props = withDefaults(defineProps<Props>(), {
27
+ keyword: '',
28
+ showCreateCard: true,
29
+ });
30
+
31
+ const emit = defineEmits<{
32
+ (e: 'create'): void;
33
+ (e: 'open', dataset: DatasetCardItem): void;
34
+ (e: 'settings', dataset: DatasetCardItem): void;
35
+ (e: 'delete', dataset: DatasetCardItem): void;
36
+ }>();
37
+
38
+ const { t } = useKhI18n();
39
+
40
+ const ICONS = ['📙', '📗', '📘', '📕', '📓', '📔', '📒', '🗂', '📚', '🧭'];
41
+ const BGS = [
42
+ '#FFF4ED',
43
+ '#EEF4FF',
44
+ '#EFFDF4',
45
+ '#FEF3F2',
46
+ '#FFF8E6',
47
+ '#FDF2FA',
48
+ '#F0F9FF',
49
+ '#F0FDF9',
50
+ ];
51
+ function hashCode(s: string): number {
52
+ let h = 0;
53
+ for (let i = 0; i < s.length; i++) h = Math.trunc((h << 5) - h + s.charCodeAt(i));
54
+ return Math.abs(h);
55
+ }
56
+ function iconOf(d: DatasetCardItem) {
57
+ return ICONS[hashCode(d.id || d.name) % ICONS.length];
58
+ }
59
+ function bgOf(d: DatasetCardItem) {
60
+ return BGS[hashCode((d.id || d.name) + '.bg') % BGS.length];
61
+ }
62
+ function fromNow(iso?: string): string {
63
+ if (!iso) return '';
64
+ const t = new Date(iso).getTime();
65
+ if (Number.isNaN(t)) return '';
66
+ const diff = (Date.now() - t) / 1000;
67
+ if (diff < 60) return '刚刚';
68
+ if (diff < 3600) return `${Math.floor(diff / 60)} 分钟前`;
69
+ if (diff < 86_400) return `${Math.floor(diff / 3600)} 小时前`;
70
+ if (diff < 86_400 * 30) return `${Math.floor(diff / 86_400)} 天前`;
71
+ return new Date(iso).toLocaleDateString();
72
+ }
73
+
74
+ const filtered = computed(() => {
75
+ const q = (props.keyword ?? '').trim().toLowerCase();
76
+ if (!q) return props.datasets;
77
+ return props.datasets.filter(
78
+ (d) =>
79
+ d.name.toLowerCase().includes(q) ||
80
+ (d.description ?? '').toLowerCase().includes(q),
81
+ );
82
+ });
83
+
84
+ // -------- popup menu state
85
+ const openMenuId = ref<string | null>(null);
86
+ function toggleMenu(id: string) {
87
+ openMenuId.value = openMenuId.value === id ? null : id;
88
+ }
89
+ function closeMenu() {
90
+ openMenuId.value = null;
91
+ }
92
+ function onMenuOpen(d: DatasetCardItem) {
93
+ closeMenu();
94
+ emit('open', d);
95
+ }
96
+ function onMenuSettings(d: DatasetCardItem) {
97
+ closeMenu();
98
+ emit('settings', d);
99
+ }
100
+ function onMenuDelete(d: DatasetCardItem) {
101
+ closeMenu();
102
+ emit('delete', d);
103
+ }
104
+
105
+ // Close on any outside click / Esc — the menu itself stops propagation.
106
+ function onDocClick() {
107
+ if (openMenuId.value !== null) closeMenu();
108
+ }
109
+ function onKey(e: KeyboardEvent) {
110
+ if (e.key === 'Escape' && openMenuId.value !== null) closeMenu();
111
+ }
112
+ onMounted(() => {
113
+ document.addEventListener('click', onDocClick);
114
+ document.addEventListener('keydown', onKey);
115
+ });
116
+ onBeforeUnmount(() => {
117
+ document.removeEventListener('click', onDocClick);
118
+ document.removeEventListener('keydown', onKey);
119
+ });
120
+ </script>
121
+
122
+ <template>
123
+ <div class="kh-cards">
124
+ <!-- +新建 -->
125
+ <div
126
+ v-if="showCreateCard"
127
+ class="kh-card kh-card-new"
128
+ :class="{ 'kh-card-disabled': !!disabledReason }"
129
+ :title="disabledReason"
130
+ @click="!disabledReason && emit('create')"
131
+ >
132
+ <div class="kh-card-new-inner">
133
+ <div class="kh-card-plus">+</div>
134
+ <div class="kh-card-title-new">新建知识库</div>
135
+ <div class="kh-card-sub-new">导入文档 / 上传文件 / 空知识库</div>
136
+ </div>
137
+ </div>
138
+
139
+ <div
140
+ v-for="d in filtered"
141
+ :key="d.id"
142
+ class="kh-card"
143
+ :class="{ 'kh-card-active': openMenuId === d.id }"
144
+ @click="emit('open', d)"
145
+ >
146
+ <div class="kh-card-head">
147
+ <div class="kh-card-icon" :style="{ background: bgOf(d) }">
148
+ {{ iconOf(d) }}
149
+ </div>
150
+ <div class="kh-card-title-wrap">
151
+ <div class="kh-card-title" :title="d.name">{{ d.name }}</div>
152
+ <div class="kh-card-meta">
153
+ <span
154
+ class="kh-card-tag"
155
+ :data-tone="d.indexingTechnique === 'ECONOMY' ? 'default' : 'blue'"
156
+ >
157
+ {{ d.indexingTechnique === 'ECONOMY' ? '经济' : '高质量' }}
158
+ </span>
159
+ <span>· {{ fromNow(d.updatedAt) || '刚刚' }}</span>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ <div class="kh-card-desc" :title="d.description || ''">
164
+ {{ d.description || '暂无描述' }}
165
+ </div>
166
+ <div class="kh-card-spacer" />
167
+ <div class="kh-card-foot">
168
+ <span class="kh-card-foot-item" :title="`${d.documentCount ?? 0} 个文档`">
169
+ 📄 {{ d.documentCount ?? 0 }}
170
+ </span>
171
+ <span class="kh-card-foot-item" :title="`${d.segmentCount ?? 0} 个片段`">
172
+ 🧩 {{ d.segmentCount ?? 0 }}
173
+ </span>
174
+ <span class="kh-card-foot-fill" />
175
+ <div class="kh-card-menu-wrap" @click.stop>
176
+ <button
177
+ type="button"
178
+ class="kh-card-more"
179
+ :class="{ 'is-open': openMenuId === d.id }"
180
+ :aria-haspopup="'menu'"
181
+ :aria-expanded="openMenuId === d.id"
182
+ :title="t('card.more')"
183
+ @click="toggleMenu(d.id)"
184
+ >
185
+ <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
186
+ <circle cx="3" cy="8" r="1.4" fill="currentColor" />
187
+ <circle cx="8" cy="8" r="1.4" fill="currentColor" />
188
+ <circle cx="13" cy="8" r="1.4" fill="currentColor" />
189
+ </svg>
190
+ </button>
191
+ <div
192
+ v-if="openMenuId === d.id"
193
+ class="kh-card-menu"
194
+ role="menu"
195
+ >
196
+ <button
197
+ type="button"
198
+ class="kh-card-menu-item"
199
+ role="menuitem"
200
+ @click="onMenuOpen(d)"
201
+ >
202
+ <span class="kh-card-menu-ico">📂</span>{{ t('card.open') }}
203
+ </button>
204
+ <button
205
+ type="button"
206
+ class="kh-card-menu-item"
207
+ role="menuitem"
208
+ @click="onMenuSettings(d)"
209
+ >
210
+ <span class="kh-card-menu-ico">⚙️</span>{{ t('card.settings') }}
211
+ </button>
212
+ <div class="kh-card-menu-divider" />
213
+ <button
214
+ type="button"
215
+ class="kh-card-menu-item kh-card-menu-item-danger"
216
+ role="menuitem"
217
+ @click="onMenuDelete(d)"
218
+ >
219
+ <span class="kh-card-menu-ico">🗑</span>{{ t('card.delete') }}
220
+ </button>
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </template>
227
+
228
+ <style scoped>
229
+ .kh-cards {
230
+ display: grid;
231
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
232
+ gap: var(--kh-space-4);
233
+ }
234
+ .kh-card {
235
+ position: relative;
236
+ height: 200px;
237
+ padding: 16px 18px 12px;
238
+ background: var(--kh-card-bg);
239
+ border: 1px solid var(--kh-card-border);
240
+ border-radius: var(--kh-card-radius);
241
+ cursor: pointer;
242
+ transition:
243
+ box-shadow var(--kh-tx-fast),
244
+ transform var(--kh-tx-fast),
245
+ border-color var(--kh-tx-fast);
246
+ display: flex;
247
+ flex-direction: column;
248
+ overflow: hidden;
249
+ box-shadow: var(--kh-shadow-sm);
250
+ }
251
+ .kh-card:hover {
252
+ box-shadow: var(--kh-card-shadow-hover);
253
+ transform: translateY(-2px);
254
+ border-color: var(--kh-color-border-hover);
255
+ }
256
+ .kh-card-active {
257
+ border-color: var(--kh-color-primary-outline);
258
+ box-shadow: var(--kh-card-shadow-hover);
259
+ }
260
+ .kh-card-new {
261
+ border: 1.5px dashed var(--kh-color-primary-outline);
262
+ background: var(--kh-color-primary-soft);
263
+ box-shadow: none;
264
+ }
265
+ .kh-card-new:hover {
266
+ border-color: var(--kh-color-primary);
267
+ transform: translateY(-2px);
268
+ }
269
+ .kh-card-disabled {
270
+ opacity: 0.6;
271
+ cursor: not-allowed;
272
+ }
273
+ .kh-card-new-inner {
274
+ margin: auto 0;
275
+ text-align: center;
276
+ color: var(--kh-color-primary);
277
+ }
278
+ .kh-card-plus {
279
+ font-size: 42px;
280
+ line-height: 1;
281
+ font-weight: 200;
282
+ }
283
+ .kh-card-title-new {
284
+ margin-top: 6px;
285
+ font-size: var(--kh-fs-2xl);
286
+ font-weight: 500;
287
+ }
288
+ .kh-card-sub-new {
289
+ margin-top: 4px;
290
+ font-size: var(--kh-fs-md);
291
+ color: var(--kh-color-text-muted);
292
+ }
293
+
294
+ .kh-card-head {
295
+ display: flex;
296
+ gap: var(--kh-space-3);
297
+ align-items: flex-start;
298
+ }
299
+ .kh-card-icon {
300
+ width: 42px;
301
+ height: 42px;
302
+ flex-shrink: 0;
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ border-radius: var(--kh-radius-md);
307
+ font-size: 22px;
308
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.04);
309
+ }
310
+ .kh-card-title-wrap {
311
+ min-width: 0;
312
+ flex: 1;
313
+ }
314
+ .kh-card-title {
315
+ font-size: var(--kh-fs-2xl);
316
+ font-weight: 600;
317
+ color: var(--kh-color-text-primary);
318
+ white-space: nowrap;
319
+ overflow: hidden;
320
+ text-overflow: ellipsis;
321
+ letter-spacing: 0.1px;
322
+ }
323
+ .kh-card-meta {
324
+ margin-top: 4px;
325
+ font-size: var(--kh-fs-md);
326
+ color: var(--kh-color-text-tertiary);
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 4px;
330
+ }
331
+ .kh-card-tag {
332
+ padding: 1px 6px;
333
+ border-radius: var(--kh-radius-xs);
334
+ background: var(--kh-color-info-soft);
335
+ color: var(--kh-color-info);
336
+ font-size: var(--kh-fs-xs);
337
+ }
338
+ .kh-card-tag[data-tone='default'] {
339
+ background: var(--kh-color-surface-hover);
340
+ color: var(--kh-color-text-secondary);
341
+ }
342
+ .kh-card-desc {
343
+ margin-top: 10px;
344
+ font-size: var(--kh-fs-lg);
345
+ color: var(--kh-color-text-tertiary);
346
+ line-height: 1.5;
347
+ display: -webkit-box;
348
+ -webkit-line-clamp: 2;
349
+ -webkit-box-orient: vertical;
350
+ overflow: hidden;
351
+ }
352
+ .kh-card-spacer {
353
+ flex: 1;
354
+ }
355
+ .kh-card-foot {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: var(--kh-space-3);
359
+ font-size: var(--kh-fs-md);
360
+ color: var(--kh-color-text-tertiary);
361
+ border-top: 1px solid var(--kh-color-divider);
362
+ padding-top: var(--kh-space-2);
363
+ }
364
+ .kh-card-foot-item {
365
+ display: inline-flex;
366
+ align-items: center;
367
+ gap: 4px;
368
+ }
369
+ .kh-card-foot-fill {
370
+ flex: 1;
371
+ }
372
+
373
+ /* "⋯" menu trigger — sits in the footer, right-aligned. */
374
+ .kh-card-menu-wrap {
375
+ position: relative;
376
+ display: inline-flex;
377
+ }
378
+ .kh-card-more {
379
+ display: inline-flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ width: 26px;
383
+ height: 26px;
384
+ border: none;
385
+ border-radius: var(--kh-radius-sm);
386
+ background: transparent;
387
+ color: var(--kh-color-text-tertiary);
388
+ cursor: pointer;
389
+ opacity: 0;
390
+ transition:
391
+ background var(--kh-tx-fast),
392
+ color var(--kh-tx-fast),
393
+ opacity var(--kh-tx-fast);
394
+ }
395
+ .kh-card:hover .kh-card-more,
396
+ .kh-card-more.is-open {
397
+ opacity: 1;
398
+ }
399
+ .kh-card-more:hover,
400
+ .kh-card-more.is-open {
401
+ background: var(--kh-color-surface-hover);
402
+ color: var(--kh-color-text-primary);
403
+ }
404
+
405
+ /* Popup menu — anchored to the ⋯ button, opens upward so it doesn't clip. */
406
+ .kh-card-menu {
407
+ position: absolute;
408
+ right: 0;
409
+ bottom: calc(100% + 6px);
410
+ min-width: 148px;
411
+ padding: 4px;
412
+ background: var(--kh-color-surface-raised);
413
+ border: 1px solid var(--kh-color-border);
414
+ border-radius: var(--kh-radius-md);
415
+ box-shadow: var(--kh-shadow-lg);
416
+ z-index: 5;
417
+ display: flex;
418
+ flex-direction: column;
419
+ gap: 1px;
420
+ animation: kh-menu-in 0.12s ease-out;
421
+ }
422
+ @keyframes kh-menu-in {
423
+ from {
424
+ opacity: 0;
425
+ transform: translateY(4px);
426
+ }
427
+ to {
428
+ opacity: 1;
429
+ transform: translateY(0);
430
+ }
431
+ }
432
+ .kh-card-menu-item {
433
+ display: flex;
434
+ align-items: center;
435
+ gap: 8px;
436
+ padding: 7px 10px;
437
+ background: transparent;
438
+ border: none;
439
+ border-radius: var(--kh-radius-sm);
440
+ font-size: var(--kh-fs-lg);
441
+ color: var(--kh-color-text-primary);
442
+ text-align: left;
443
+ cursor: pointer;
444
+ transition: background var(--kh-tx-fast), color var(--kh-tx-fast);
445
+ }
446
+ .kh-card-menu-item:hover {
447
+ background: var(--kh-color-surface-hover);
448
+ }
449
+ .kh-card-menu-ico {
450
+ font-size: 14px;
451
+ line-height: 1;
452
+ width: 16px;
453
+ display: inline-flex;
454
+ justify-content: center;
455
+ }
456
+ .kh-card-menu-item-danger {
457
+ color: var(--kh-color-danger);
458
+ }
459
+ .kh-card-menu-item-danger:hover {
460
+ background: var(--kh-color-danger-soft);
461
+ }
462
+ .kh-card-menu-divider {
463
+ height: 1px;
464
+ margin: 4px 2px;
465
+ background: var(--kh-color-divider);
466
+ }
467
+ </style>