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,159 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ProviderApp — 一体化模型供应商配置页。
4
+ *
5
+ * 单一 prop `apiBase` (默认 `/api`):
6
+ * • 内部三段式壳层 `<ProviderHubShell>` 处理 "已装 / 待配置 / 可安装";
7
+ * • 加载状态、错误 toast、删除确认、进阶已装模型面板全在内部;
8
+ * • 宿主只需一句 `<ProviderApp />` 即可完整落地模型配置能力。
9
+ */
10
+ import { onMounted, ref } from 'vue';
11
+
12
+ import { message, Modal, Spin, Switch } from 'ant-design-vue';
13
+
14
+ import {
15
+ setProviderHubApiBase,
16
+ useProviderHub,
17
+ } from '../composables/useProviderHub';
18
+ import type {
19
+ ModelEntity,
20
+ ModelTestResult,
21
+ ProviderView,
22
+ } from '../types';
23
+ import ModelCardGrid from './ModelCardGrid.vue';
24
+ import ProviderHubShell from './ProviderHubShell.vue';
25
+
26
+ interface Props {
27
+ /** Backend base URL, default `/api`. */
28
+ apiBase?: string;
29
+ /**
30
+ * 是否显示进阶面板 (已装模型逐条操作: 设为默认 / 测试 / 删除 / 覆写凭证)。
31
+ * 默认展示; 若不需要传 `:enable-advanced="false"` 隐藏。
32
+ */
33
+ enableAdvanced?: boolean;
34
+ }
35
+ const props = withDefaults(defineProps<Props>(), {
36
+ apiBase: '/api',
37
+ enableAdvanced: true,
38
+ });
39
+
40
+ // Composable is module-scoped — flip the base URL before first fetch.
41
+ setProviderHubApiBase(props.apiBase);
42
+
43
+ const { listProviders, listModels, deleteModel, setDefault } = useProviderHub();
44
+
45
+ const providers = ref<ProviderView[]>([]);
46
+ const models = ref<ModelEntity[]>([]);
47
+ const loading = ref(false);
48
+ const showAdvanced = ref(false);
49
+
50
+ async function refresh() {
51
+ loading.value = true;
52
+ try {
53
+ const [ps, ms] = await Promise.all([listProviders(), listModels()]);
54
+ providers.value = ps;
55
+ models.value = ms;
56
+ } catch (e: any) {
57
+ message.error(e?.message ?? '加载失败');
58
+ } finally {
59
+ loading.value = false;
60
+ }
61
+ }
62
+
63
+ onMounted(refresh);
64
+
65
+ async function onSetDefault(m: ModelEntity) {
66
+ await setDefault(m.id);
67
+ message.success('已设为默认');
68
+ await refresh();
69
+ }
70
+
71
+ async function onTestModel(_m: ModelEntity, r: ModelTestResult) {
72
+ if (r.ok) message.success(`连接成功 · ${r.latencyMs}ms`);
73
+ else message.error(`连接失败: ${r.error ?? 'unknown'}`);
74
+ }
75
+
76
+ function onDelete(m: ModelEntity) {
77
+ Modal.confirm({
78
+ title: '删除模型',
79
+ content: `确认删除「${m.providerName} / ${m.modelName}」?依赖它的知识库/智能体运行时会失败。`,
80
+ okText: '删除',
81
+ okType: 'danger',
82
+ onOk: async () => {
83
+ await deleteModel(m.id);
84
+ message.success('已删除');
85
+ await refresh();
86
+ },
87
+ });
88
+ }
89
+
90
+ function onEditCredentials(m: ModelEntity) {
91
+ Modal.info({
92
+ title: '编辑模型级凭证',
93
+ content:
94
+ `此模型继承供应商级 API Key。如需覆写单条模型的 endpoint/dimensions,` +
95
+ `请通过 API PUT /models/${m.id}/credentials 直接调用。` +
96
+ `\n\n(该操作已从 UI 收起 —— 99% 的场景直接改供应商级凭证更合适)`,
97
+ });
98
+ }
99
+ </script>
100
+
101
+ <template>
102
+ <div class="agent-start-provider-app">
103
+ <div class="agent-start-provider-app__header">
104
+ <div class="agent-start-provider-app__title">模型供应商</div>
105
+ <div class="agent-start-provider-app__desc">
106
+ 填入 API Key 一次导入该供应商全部可用模型 —— 凭证 AES-GCM 加密存储。
107
+ </div>
108
+ </div>
109
+ <Spin :spinning="loading">
110
+ <ProviderHubShell :models="models" @change="refresh" />
111
+
112
+ <!-- 进阶: 已装模型的行级操作,默认收起 -->
113
+ <div v-if="enableAdvanced && models.length > 0" class="mt-6">
114
+ <div class="mb-2 flex items-center gap-2">
115
+ <span class="text-sm font-medium">进阶:已装模型逐条管理</span>
116
+ <Switch v-model:checked="showAdvanced" size="small" />
117
+ <span class="text-xs text-gray-500">
118
+ 设为默认 / 测试 / 删除 / 覆写凭证
119
+ </span>
120
+ </div>
121
+ <div v-if="showAdvanced">
122
+ <ModelCardGrid
123
+ :models="models"
124
+ :providers="providers"
125
+ @set-default="onSetDefault"
126
+ @test="onTestModel"
127
+ @delete="onDelete"
128
+ @edit-credentials="onEditCredentials"
129
+ />
130
+ </div>
131
+ </div>
132
+ </Spin>
133
+ </div>
134
+ </template>
135
+
136
+ <style scoped>
137
+ .agent-start-provider-app {
138
+ padding: 16px;
139
+ }
140
+ .agent-start-provider-app__header {
141
+ margin-bottom: 16px;
142
+ }
143
+ .agent-start-provider-app__title {
144
+ font-size: 18px;
145
+ font-weight: 600;
146
+ color: #111827;
147
+ }
148
+ .agent-start-provider-app__desc {
149
+ margin-top: 4px;
150
+ font-size: 12px;
151
+ color: #6b7280;
152
+ }
153
+ :global(.dark) .agent-start-provider-app__title {
154
+ color: #f3f4f6;
155
+ }
156
+ :global(.dark) .agent-start-provider-app__desc {
157
+ color: #9ca3af;
158
+ }
159
+ </style>
@@ -0,0 +1,321 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ProviderCredentialModal — save-and-validate a provider-level API key.
4
+ *
5
+ * Single step, no auto-import:
6
+ * User fills the schema-driven credential form → clicks "保存". The backend calls
7
+ * the vendor's model listing endpoint as a lightweight validation (bad key → 400,
8
+ * no state persisted), then saves the credential AND seeds the catalog as
9
+ * {@code agent_model} rows with {@code enabled=false}. Individual enable/disable
10
+ * is done in the shell's expanded row via toggle switches — this modal is
11
+ * deliberately dumb.
12
+ *
13
+ * Emits `saved` when the credential is persisted so the parent refreshes.
14
+ */
15
+ import { reactive, ref, watch } from 'vue';
16
+
17
+ import { useProviderHub } from '../composables/useProviderHub';
18
+ import type { ProviderView } from '../types';
19
+
20
+ interface Props {
21
+ open: boolean;
22
+ provider: ProviderView | null;
23
+ tenantId?: string;
24
+ /** In edit mode, secret placeholders read "留空 = 保持不变" and empty submits are skipped. */
25
+ editMode?: boolean;
26
+ }
27
+
28
+ const props = withDefaults(defineProps<Props>(), { editMode: false });
29
+
30
+ const emit = defineEmits<{
31
+ (e: 'update:open', v: boolean): void;
32
+ (e: 'saved'): void;
33
+ }>();
34
+
35
+ const { saveProviderCredential } = useProviderHub();
36
+
37
+ const creds = reactive<Record<string, unknown>>({});
38
+ const saving = ref(false);
39
+ const error = ref<string | null>(null);
40
+
41
+ watch(
42
+ () => [props.open, props.provider?.name],
43
+ ([o]) => {
44
+ if (!o) return;
45
+ Object.keys(creds).forEach((k) => delete creds[k]);
46
+ error.value = null;
47
+ // Preload defaults from the schema so baseUrl-like fields are ready.
48
+ for (const f of props.provider?.credentialSchema ?? []) {
49
+ if (!props.editMode && f.defaultValue) creds[f.name] = f.defaultValue;
50
+ }
51
+ },
52
+ { immediate: true },
53
+ );
54
+
55
+ async function save() {
56
+ if (!props.provider) return;
57
+ error.value = null;
58
+ const payload: Record<string, unknown> = {};
59
+ for (const [k, v] of Object.entries(creds)) {
60
+ if (v !== null && v !== undefined && String(v).length > 0) payload[k] = v;
61
+ }
62
+ if (Object.keys(payload).length === 0) {
63
+ error.value = props.editMode ? '至少填写一个字段' : '请填入 API 密钥';
64
+ return;
65
+ }
66
+ saving.value = true;
67
+ try {
68
+ await saveProviderCredential(props.provider.name, payload, props.tenantId);
69
+ emit('saved');
70
+ emit('update:open', false);
71
+ } catch (e: any) {
72
+ // Backend returns the vendor's error text on 401 — surface it verbatim.
73
+ error.value = e?.message ?? String(e);
74
+ } finally {
75
+ saving.value = false;
76
+ }
77
+ }
78
+ </script>
79
+
80
+ <template>
81
+ <div v-if="open" class="pcm-backdrop" @click.self="emit('update:open', false)">
82
+ <div class="pcm-modal">
83
+ <div class="pcm-header">
84
+ <div class="pcm-title">
85
+ <span class="pcm-title-icon">🔑</span>
86
+ <span v-if="provider">
87
+ {{ editMode ? '编辑凭证' : '添加 API 密钥' }} · {{ provider.label }}
88
+ </span>
89
+ </div>
90
+ <button class="pcm-close" @click="emit('update:open', false)">✕</button>
91
+ </div>
92
+
93
+ <div class="pcm-body">
94
+ <div class="pcm-hint">
95
+ <template v-if="provider?.supportsRemoteModelListing">
96
+ 保存时会向该供应商发起一次列表请求做校验(不会真的注册任何模型);
97
+ 通过后可在下方展开的目录里逐个开关启用。
98
+ </template>
99
+ <template v-else>
100
+ 该供应商没有远程模型列表接口,请保存凭证后手动添加模型名称。
101
+ </template>
102
+ </div>
103
+
104
+ <div class="pcm-form">
105
+ <div
106
+ v-for="f in provider?.credentialSchema ?? []"
107
+ :key="f.name"
108
+ class="pcm-field"
109
+ >
110
+ <label class="pcm-label">
111
+ <span>{{ f.label }}</span>
112
+ <span v-if="f.required" class="pcm-req">*</span>
113
+ <span v-if="f.secret" class="pcm-secret">密文</span>
114
+ </label>
115
+ <input
116
+ v-if="f.secret"
117
+ type="password"
118
+ class="pcm-input"
119
+ autocomplete="off"
120
+ :value="(creds[f.name] as string) ?? ''"
121
+ :placeholder="editMode ? '留空 = 保持不变' : (f.placeholder ?? '')"
122
+ @input="(e) => (creds[f.name] = (e.target as HTMLInputElement).value)"
123
+ />
124
+ <input
125
+ v-else
126
+ type="text"
127
+ class="pcm-input"
128
+ :value="(creds[f.name] as string) ?? ''"
129
+ :placeholder="editMode ? '留空 = 保持不变' : (f.placeholder ?? '')"
130
+ @input="(e) => (creds[f.name] = (e.target as HTMLInputElement).value)"
131
+ />
132
+ </div>
133
+ </div>
134
+
135
+ <div v-if="error" class="pcm-error">
136
+ {{ error }}
137
+ </div>
138
+ </div>
139
+
140
+ <div class="pcm-footer">
141
+ <button
142
+ class="pcm-btn"
143
+ :disabled="saving"
144
+ @click="emit('update:open', false)"
145
+ >
146
+ 取消
147
+ </button>
148
+ <button
149
+ class="pcm-btn pcm-btn-primary"
150
+ :disabled="saving"
151
+ @click="save"
152
+ >
153
+ {{ saving ? '校验中…' : '保存' }}
154
+ </button>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </template>
159
+
160
+ <style scoped>
161
+ .pcm-backdrop {
162
+ position: fixed;
163
+ inset: 0;
164
+ background: rgba(15, 23, 42, 0.35);
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ z-index: 1000;
169
+ }
170
+ .pcm-modal {
171
+ width: 520px;
172
+ max-width: 92vw;
173
+ max-height: 88vh;
174
+ background: #fff;
175
+ border-radius: 12px;
176
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.25);
177
+ display: flex;
178
+ flex-direction: column;
179
+ }
180
+ :global(.dark) .pcm-modal {
181
+ background: #1f1f1f;
182
+ color: #f3f4f6;
183
+ }
184
+ .pcm-header {
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: space-between;
188
+ padding: 14px 20px;
189
+ border-bottom: 1px solid #e5e7eb;
190
+ }
191
+ :global(.dark) .pcm-header {
192
+ border-bottom-color: #2d2d2d;
193
+ }
194
+ .pcm-title {
195
+ font-size: 16px;
196
+ font-weight: 600;
197
+ color: #111827;
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 8px;
201
+ }
202
+ :global(.dark) .pcm-title {
203
+ color: #f3f4f6;
204
+ }
205
+ .pcm-title-icon {
206
+ font-size: 18px;
207
+ }
208
+ .pcm-close {
209
+ background: transparent;
210
+ border: none;
211
+ font-size: 18px;
212
+ color: #6b7280;
213
+ cursor: pointer;
214
+ padding: 4px 8px;
215
+ border-radius: 6px;
216
+ }
217
+ .pcm-close:hover {
218
+ background: #f3f4f6;
219
+ }
220
+ .pcm-body {
221
+ padding: 16px 20px;
222
+ overflow-y: auto;
223
+ flex: 1;
224
+ }
225
+ .pcm-hint {
226
+ margin-bottom: 12px;
227
+ font-size: 12px;
228
+ color: #6b7280;
229
+ line-height: 1.6;
230
+ }
231
+ .pcm-error {
232
+ padding: 8px 12px;
233
+ background: #fef2f2;
234
+ border: 1px solid #fecaca;
235
+ border-radius: 6px;
236
+ color: #b91c1c;
237
+ font-size: 12px;
238
+ margin-top: 8px;
239
+ }
240
+ .pcm-form {
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: 12px;
244
+ }
245
+ .pcm-field {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 4px;
249
+ }
250
+ .pcm-label {
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 6px;
254
+ font-size: 13px;
255
+ font-weight: 500;
256
+ color: #374151;
257
+ }
258
+ :global(.dark) .pcm-label {
259
+ color: #d1d5db;
260
+ }
261
+ .pcm-req {
262
+ color: #dc2626;
263
+ }
264
+ .pcm-secret {
265
+ padding: 0 6px;
266
+ font-size: 10px;
267
+ border-radius: 4px;
268
+ background: #fef3c7;
269
+ color: #b45309;
270
+ }
271
+ .pcm-input {
272
+ padding: 8px 10px;
273
+ font-size: 13px;
274
+ border: 1px solid #d1d5db;
275
+ border-radius: 6px;
276
+ background: #fff;
277
+ color: #111827;
278
+ }
279
+ :global(.dark) .pcm-input {
280
+ background: #2d2d2d;
281
+ border-color: #3d3d3d;
282
+ color: #f3f4f6;
283
+ }
284
+ .pcm-input:focus {
285
+ outline: none;
286
+ border-color: #6366f1;
287
+ }
288
+ .pcm-footer {
289
+ display: flex;
290
+ justify-content: flex-end;
291
+ gap: 8px;
292
+ padding: 12px 20px;
293
+ border-top: 1px solid #e5e7eb;
294
+ }
295
+ :global(.dark) .pcm-footer {
296
+ border-top-color: #2d2d2d;
297
+ }
298
+ .pcm-btn {
299
+ padding: 6px 16px;
300
+ font-size: 13px;
301
+ border-radius: 6px;
302
+ border: 1px solid #d1d5db;
303
+ background: #fff;
304
+ cursor: pointer;
305
+ }
306
+ .pcm-btn:hover {
307
+ background: #f3f4f6;
308
+ }
309
+ .pcm-btn:disabled {
310
+ opacity: 0.5;
311
+ cursor: not-allowed;
312
+ }
313
+ .pcm-btn-primary {
314
+ background: #6366f1;
315
+ color: #fff;
316
+ border-color: #6366f1;
317
+ }
318
+ .pcm-btn-primary:hover {
319
+ background: #4f46e5;
320
+ }
321
+ </style>
@@ -0,0 +1,131 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ProviderGallery — Dify-style "provider catalog" grid: one card per supported
4
+ * provider, click to start configuring a model against it.
5
+ *
6
+ * Emits `select` with the provider name. Consumers wire this to their own
7
+ * add-model dialog.
8
+ */
9
+ import { computed } from 'vue';
10
+
11
+ import type { ProviderView } from '../types';
12
+ import ProviderIcon from './ProviderIcon.vue';
13
+
14
+ interface Props {
15
+ providers: ProviderView[];
16
+ /** Optional title above the grid. */
17
+ title?: string;
18
+ /** Show a badge with predefined model count. */
19
+ showPresetCount?: boolean;
20
+ }
21
+
22
+ const props = withDefaults(defineProps<Props>(), {
23
+ title: '可用供应商',
24
+ showPresetCount: true,
25
+ });
26
+
27
+ const emit = defineEmits<{
28
+ (e: 'select', providerName: string): void;
29
+ }>();
30
+
31
+ const sorted = computed(() =>
32
+ [...props.providers].sort((a, b) => a.label.localeCompare(b.label)),
33
+ );
34
+ </script>
35
+
36
+ <template>
37
+ <div class="ph-gallery">
38
+ <div v-if="title" class="ph-gallery-title">{{ title }}</div>
39
+ <div class="ph-grid">
40
+ <div
41
+ v-for="p in sorted"
42
+ :key="p.name"
43
+ class="ph-card"
44
+ :title="`点击添加 ${p.label} 模型`"
45
+ @click="emit('select', p.name)"
46
+ >
47
+ <ProviderIcon :name="p.name" :size="40" />
48
+ <div class="ph-body">
49
+ <div class="ph-name">{{ p.label }}</div>
50
+ <div class="ph-caps">
51
+ <span
52
+ v-for="t in p.supportedModelTypes"
53
+ :key="t"
54
+ class="ph-cap"
55
+ >
56
+ {{ t }}
57
+ </span>
58
+ </div>
59
+ <div v-if="showPresetCount" class="ph-hint">
60
+ {{ p.predefinedModels.length }} 个预设模型
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <style scoped>
69
+ .ph-gallery-title {
70
+ margin-bottom: 8px;
71
+ font-size: 13px;
72
+ font-weight: 500;
73
+ color: #6b7280;
74
+ }
75
+ .ph-grid {
76
+ display: grid;
77
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
78
+ gap: 12px;
79
+ }
80
+ .ph-card {
81
+ display: flex;
82
+ gap: 12px;
83
+ padding: 12px;
84
+ background: #fff;
85
+ border: 1px solid #e5e7eb;
86
+ border-radius: 10px;
87
+ cursor: pointer;
88
+ transition: box-shadow 0.15s ease;
89
+ }
90
+ .ph-card:hover {
91
+ box-shadow: 0 3px 12px rgba(0, 0, 0, 0.06);
92
+ }
93
+ .ph-icon {
94
+ width: 40px;
95
+ height: 40px;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ border-radius: 8px;
100
+ font-size: 22px;
101
+ flex-shrink: 0;
102
+ }
103
+ .ph-body {
104
+ min-width: 0;
105
+ flex: 1;
106
+ }
107
+ .ph-name {
108
+ font-size: 14px;
109
+ font-weight: 600;
110
+ color: #111827;
111
+ }
112
+ .ph-caps {
113
+ margin-top: 4px;
114
+ display: flex;
115
+ gap: 4px;
116
+ flex-wrap: wrap;
117
+ }
118
+ .ph-cap {
119
+ display: inline-block;
120
+ padding: 1px 6px;
121
+ font-size: 10px;
122
+ border-radius: 4px;
123
+ background: #eef2ff;
124
+ color: #4338ca;
125
+ }
126
+ .ph-hint {
127
+ margin-top: 6px;
128
+ font-size: 12px;
129
+ color: #6b7280;
130
+ }
131
+ </style>