Explorar el Código

feat: 模板列表/模板页面完成/帮助菜单列表/设置列表/快速入门/文档/关于页面完成/用户中心/日志跟踪/大模型调用日志页面完成/修改main样式

Mickey Mike hace 5 días
padre
commit
a6df223c9b
Se han modificado 33 ficheros con 2592 adiciones y 107 borrados
  1. 2 0
      apps/web/components.d.ts
  2. 1 0
      apps/web/src/assets/icons/book.svg
  3. 4 0
      apps/web/src/assets/icons/check-circle.svg
  4. 1 0
      apps/web/src/assets/icons/chevron-right.svg
  5. 4 0
      apps/web/src/assets/icons/clock.svg
  6. 4 0
      apps/web/src/assets/icons/dollar-sign.svg
  7. 1 0
      apps/web/src/assets/icons/door-open.svg
  8. 1 0
      apps/web/src/assets/icons/goback.svg
  9. 1 0
      apps/web/src/assets/icons/info.svg
  10. 1 0
      apps/web/src/assets/icons/log-in.svg
  11. 3 0
      apps/web/src/assets/icons/play.svg
  12. 1 0
      apps/web/src/assets/icons/user-round.svg
  13. 1 0
      apps/web/src/assets/icons/video.svg
  14. 8 0
      apps/web/src/assets/icons/workflow.svg
  15. 5 0
      apps/web/src/assets/icons/x-circle.svg
  16. 3 0
      apps/web/src/assets/icons/zap.svg
  17. 90 28
      apps/web/src/components/Sidebar/index.vue
  18. 219 0
      apps/web/src/components/TemplateModal/index.vue
  19. 233 6
      apps/web/src/layouts/MainLayout.vue
  20. 42 0
      apps/web/src/router/index.ts
  21. 94 48
      apps/web/src/style.css
  22. 176 0
      apps/web/src/views/About.vue
  23. 1 19
      apps/web/src/views/Chat.vue
  24. 3 3
      apps/web/src/views/Dashboard.vue
  25. 353 0
      apps/web/src/views/Docs.vue
  26. 245 0
      apps/web/src/views/LogStream.vue
  27. 360 0
      apps/web/src/views/ModelLog.vue
  28. 242 0
      apps/web/src/views/QuickStart.vue
  29. 213 0
      apps/web/src/views/TemplateDetail.vue
  30. 274 0
      apps/web/src/views/UserCenter.vue
  31. 3 0
      apps/web/src/vite-env.d.ts
  32. 3 2
      apps/web/tsconfig.app.json
  33. 0 1
      apps/web/tsconfig.node.json

+ 2 - 0
apps/web/components.d.ts

@@ -29,6 +29,7 @@ declare module 'vue' {
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDorpdown: typeof import('element-plus/es')['ElDorpdown']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
@@ -78,6 +79,7 @@ declare module 'vue' {
     Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
     TabGroup: typeof import('./src/components/SetterCommon/Code/TabGroup.vue')['default']
+    TemplateModal: typeof import('./src/components/TemplateModal/index.vue')['default']
     TestConfig: typeof import('./src/components/SetterCommon/Code/TestConfig.vue')['default']
     VariablePicker: typeof import('./src/components/SetterCommon/condition/VariablePicker.vue')['default']
   }

+ 1 - 0
apps/web/src/assets/icons/book.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="book"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"></path></svg>

+ 4 - 0
apps/web/src/assets/icons/check-circle.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M22 11.08v.5a10 10 0 1 1-5.93-9.14"></path>
+  <polyline points="22 4 12 14.01 9 11.01"></polyline>
+</svg>

+ 1 - 0
apps/web/src/assets/icons/chevron-right.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="chevron-right" style="color: var(--color--text--tint-1);"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m9 18l6-6l-6-6"></path></svg>

+ 4 - 0
apps/web/src/assets/icons/clock.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <circle cx="12" cy="12" r="10"></circle>
+  <polyline points="12 6 12 12 16 14"></polyline>
+</svg>

+ 4 - 0
apps/web/src/assets/icons/dollar-sign.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <line x1="12" y1="1" x2="12" y2="23"></line>
+  <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+</svg>

+ 1 - 0
apps/web/src/assets/icons/door-open.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="door-open"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 20H2m9-15.438v16.157a1 1 0 0 0 1.242.97L19 20V5.562a2 2 0 0 0-1.515-1.94l-4-1A2 2 0 0 0 11 4.561zM11 4H8a2 2 0 0 0-2 2v14m8-8h.01M22 20h-3"></path></svg>

+ 1 - 0
apps/web/src/assets/icons/goback.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon _icon_1reb8_128" aria-hidden="true" focusable="false" role="img" data-icon="arrow-left"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 19l-7-7l7-7m7 7H5"></path></svg>

+ 1 - 0
apps/web/src/assets/icons/info.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="info"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4m0-4h.01"></path></g></svg>

+ 1 - 0
apps/web/src/assets/icons/log-in.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="log-in"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m10 17l5-5l-5-5m5 5H3m12-9h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path></svg>

+ 3 - 0
apps/web/src/assets/icons/play.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
+  <polygon points="5 3 19 12 5 21 5 3"></polygon>
+</svg>

+ 1 - 0
apps/web/src/assets/icons/user-round.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="user-round"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="8" r="5"></circle><path d="M20 21a8 8 0 0 0-16 0"></path></g></svg>

+ 1 - 0
apps/web/src/assets/icons/video.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="video"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m16 13l5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"></path><rect width="14" height="12" x="2" y="6" rx="2"></rect></g></svg>

+ 8 - 0
apps/web/src/assets/icons/workflow.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <rect x="3" y="3" width="7" height="7"></rect>
+  <rect x="14" y="3" width="7" height="7"></rect>
+  <rect x="14" y="14" width="7" height="7"></rect>
+  <rect x="3" y="14" width="7" height="7"></rect>
+  <line x1="7" y1="10" x2="17" y2="10"></line>
+  <line x1="10" y1="7" x2="10" y2="17"></line>
+</svg>

+ 5 - 0
apps/web/src/assets/icons/x-circle.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <circle cx="12" cy="12" r="10"></circle>
+  <line x1="15" y1="9" x2="9" y2="15"></line>
+  <line x1="9" y1="9" x2="15" y2="15"></line>
+</svg>

+ 3 - 0
apps/web/src/assets/icons/zap.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
+</svg>

+ 90 - 28
apps/web/src/components/Sidebar/index.vue

@@ -71,7 +71,7 @@
 		<div class="spacer"></div>
 
 		<div class="bottom-menu">
-			<div
+			<!-- <div
 				class="bottom-item"
 				:class="{ active: router.currentRoute.value.path === '/management' }"
 				@click="$router.push('/management')"
@@ -81,13 +81,9 @@
 				</el-tooltip>
 				<SvgIcon v-else name="platForm" />
 				<span v-if="!collapsed" class="label">管理面板</span>
-			</div>
+			</div> -->
 
-			<div
-				class="bottom-item"
-				:class="{ active: router.currentRoute.value.path === '/templates' }"
-				@click="$router.push('/templates')"
-			>
+			<div class="bottom-item" :class="{ active: showTemplateModal }" @click="handleTemplateClick">
 				<el-tooltip v-if="collapsed" content="模板" placement="right">
 					<span><SvgIcon name="box" /></span>
 				</el-tooltip>
@@ -107,28 +103,22 @@
 				<span v-if="!collapsed" class="label">统计</span>
 			</div>
 
-			<div
-				class="bottom-item"
-				:class="{ active: router.currentRoute.value.path === '/help' }"
-				@click="$router.push('/help')"
-			>
+			<div class="bottom-item" @click="handleSettingsClick($event, 'help')">
 				<el-tooltip v-if="collapsed" content="帮助" placement="right">
 					<span><SvgIcon name="help" /></span>
 				</el-tooltip>
 				<SvgIcon v-else name="help" />
 				<span v-if="!collapsed" class="label">帮助</span>
+				<SvgIcon v-if="!collapsed" name="chevron-right" />
 			</div>
 
-			<div
-				class="bottom-item"
-				:class="{ active: router.currentRoute.value.path === '/settings' }"
-				@click="$router.push('/settings')"
-			>
+			<div class="bottom-item" @click="handleSettingsClick($event, 'settings')">
 				<el-tooltip v-if="collapsed" content="设置" placement="right">
 					<span><SvgIcon name="setting" /></span>
 				</el-tooltip>
 				<SvgIcon v-else name="setting" />
 				<span v-if="!collapsed" class="label">设置</span>
+				<SvgIcon v-if="!collapsed" name="chevron-right" />
 			</div>
 		</div>
 	</div>
@@ -139,6 +129,13 @@
 		@close="showSearchDialog = false"
 		@select="handleSearchSelect"
 	/>
+
+	<!-- 模板弹窗 -->
+	<TemplateModal
+		:visible="showTemplateModal"
+		@close="showTemplateModal = false"
+		@select="handleTemplateSelect"
+	/>
 </template>
 
 <script setup lang="ts">
@@ -146,10 +143,16 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import SearchDialog from '../SearchDialog/index.vue'
 import { v4 } from 'uuid'
+import TemplateModal from '../TemplateModal/index.vue'
+
+const emit = defineEmits<{
+	'settings-menu-toggle': [position: { top: number; left: number }, name: string]
+}>()
 
 const router = useRouter()
 const collapsed = ref(false)
 const showSearchDialog = ref(false)
+const showTemplateModal = ref(false)
 
 // 计算当前活跃的菜单项
 const activeMenu = computed(() => router.currentRoute.value.path)
@@ -165,10 +168,18 @@ const createWorkflow = () => {
 
 const createCertificate = () => {}
 
-const createVariable = () => {}
-
 const createTable = () => {}
 
+const handleTemplateClick = () => {
+	showTemplateModal.value = true
+}
+
+const handleTemplateSelect = (templateId: string) => {
+	// 导航到模板详情页面
+	router.push(`/templates/${templateId}`)
+	showTemplateModal.value = false
+}
+
 const handleKeyDown = (e: KeyboardEvent) => {
 	if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
 		e.preventDefault()
@@ -188,19 +199,22 @@ onUnmounted(() => {
 	window.removeEventListener('keydown', handleKeyDown)
 })
 
-const dropdownItems = ref([
-	{ id: '1', label: '工作流程', action: createWorkflow },
-	{ id: '2', label: '凭证', action: createCertificate },
-	{ id: '3', label: '变量', action: createVariable },
-	{ id: '4', label: '数据表', action: createTable }
-])
-
 const handleSearchSelect = (item: any) => {
 	console.log('Selected:', item)
 }
 
-const handleDropdownSelect = (item: any) => {
-	console.log('Dropdown selected:', item)
+const handleSettingsClick = (event: MouseEvent, name: string) => {
+	const element = event.currentTarget as HTMLElement
+	const rect = element.getBoundingClientRect()
+
+	emit(
+		'settings-menu-toggle',
+		{
+			top: rect.top,
+			left: rect.right + 8
+		},
+		name
+	)
 }
 </script>
 
@@ -285,6 +299,7 @@ const handleDropdownSelect = (item: any) => {
 }
 .label {
 	font-size: 13px;
+	flex: 1;
 }
 .beta {
 	font-size: 10px;
@@ -304,8 +319,10 @@ const handleDropdownSelect = (item: any) => {
 .bottom-item {
 	display: flex;
 	align-items: center;
+	justify-content: space-between;
 	gap: 10px;
 	padding: 8px 18px;
+	padding-right: 10px;
 	color: #333;
 	cursor: pointer;
 	border-radius: 4px;
@@ -319,6 +336,51 @@ const handleDropdownSelect = (item: any) => {
 	background: #fafafa;
 	color: #ff6b6b;
 }
+
+.settings-menu-panel {
+	position: absolute;
+	left: 100%;
+	width: 240px;
+	background: #fff;
+	border: 1px solid #f0f0f0;
+	border-radius: 4px;
+	box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.12);
+	z-index: 999;
+	margin-left: 8px;
+	animation: slideInRight 0.2s ease;
+
+	.settings-menu-list {
+		padding: 8px 0;
+		max-height: 500px;
+		overflow-y: auto;
+
+		.settings-menu-item {
+			padding: 10px 16px;
+			color: #666;
+			cursor: pointer;
+			transition: all 0.2s;
+			font-size: 13px;
+			white-space: nowrap;
+
+			&:hover {
+				background: #f5f5f5;
+				color: #333;
+			}
+
+			&.logout {
+				color: #ff6b6b;
+				border-top: 1px solid #f0f0f0;
+				margin-top: 4px;
+				padding-top: 12px;
+
+				&:hover {
+					background: #fff5f5;
+				}
+			}
+		}
+	}
+}
+
 .sidebar.collapsed .label {
 	display: none;
 }

+ 219 - 0
apps/web/src/components/TemplateModal/index.vue

@@ -0,0 +1,219 @@
+<template>
+	<el-dialog v-model="dialogVisible" title="特色模板" width="1000px" @close="handleClose">
+		<div class="template-modal">
+			<el-row :gutter="24" justify="start">
+				<el-col :span="8" v-for="template in templates" :key="template.id">
+					<div class="template-card">
+						<div class="template-icon">{{ template.icon }}</div>
+						<div class="template-title">{{ template.title }}</div>
+						<div class="template-description">{{ template.description }}</div>
+						<el-button
+							type="primary"
+							size="small"
+							class="use-btn"
+							@click="selectTemplate(template)"
+						>
+							使用模板
+						</el-button>
+					</div>
+				</el-col>
+			</el-row>
+		</div>
+	</el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+
+interface Template {
+	id: string
+	title: string
+	description: string
+	icon: string
+	data?: any
+}
+
+const props = defineProps({
+	visible: {
+		type: Boolean,
+		default: false
+	}
+})
+
+const emit = defineEmits<{
+	close: []
+	select: [templateId: string]
+}>()
+
+const dialogVisible = ref(false)
+
+// 模板数据
+const templates = ref<Template[]>([
+	{
+		id: '1',
+		title: 'Gmail + GPT-4o-mini',
+		description: '使用 Gmail、GPT-4o-mini 和 Notion 自动处理电子邮件分类和摘要',
+		icon: '✉️',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'gmail', type: 'http', config: { service: 'gmail' } },
+				{ id: 'gpt', type: 'code', config: { model: 'gpt-4o-mini' } },
+				{ id: 'notion', type: 'database', config: { service: 'notion' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	},
+	{
+		id: '2',
+		title: 'Telegram + OpenAI',
+		description: '使用 Telegram、OpenAI 和 Google Drive 将聊天内容分类为搜索引擎,并生成 PDF 文件',
+		icon: '💬',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'telegram', type: 'http', config: { service: 'telegram' } },
+				{ id: 'openai', type: 'code', config: { model: 'gpt-4' } },
+				{ id: 'drive', type: 'http', config: { service: 'google-drive' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	},
+	{
+		id: '3',
+		title: 'Gmail + Memo RAG',
+		description: '由 Gmail 和 Memo 提供持有的 RAG 代码的电子邮件分析',
+		icon: '📧',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'gmail', type: 'http', config: { service: 'gmail' } },
+				{ id: 'memo', type: 'database', config: { service: 'memo' } },
+				{ id: 'rag', type: 'code', config: { type: 'rag' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	},
+	{
+		id: '4',
+		title: 'Mistral + OpenRouter',
+		description: '使用 Mistral 通过 OpenRouter 与 AI 模型处理工作流',
+		icon: '🤖',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'mistral', type: 'code', config: { model: 'mistral' } },
+				{ id: 'router', type: 'http', config: { service: 'openrouter' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	},
+	{
+		id: '5',
+		title: 'RAG 架构',
+		description: '基于 Supabase、TogetherAI 和 OpenRouter 的 RAG 架构',
+		icon: '🔍',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'supabase', type: 'database', config: { service: 'supabase' } },
+				{ id: 'together', type: 'code', config: { service: 'together' } },
+				{ id: 'router', type: 'http', config: { service: 'openrouter' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	},
+	{
+		id: '6',
+		title: 'LINE + Supabase',
+		description: '结合 LINE Messaging、Supabase Vector DB 和 Gmail 的 AI 综合应用本表记功能',
+		icon: '💌',
+		data: {
+			nodes: [
+				{ id: 'start', type: 'start' },
+				{ id: 'line', type: 'http', config: { service: 'line' } },
+				{ id: 'supabase', type: 'database', config: { service: 'supabase' } },
+				{ id: 'gmail', type: 'http', config: { service: 'gmail' } },
+				{ id: 'end', type: 'end' }
+			]
+		}
+	}
+])
+
+watch(
+	() => props.visible,
+	(val) => {
+		dialogVisible.value = val
+	}
+)
+
+const handleClose = () => {
+	emit('close')
+}
+
+const selectTemplate = (template: Template) => {
+	// 发送选中的模板 ID
+	emit('select', template.id)
+}
+</script>
+
+<style lang="less" scoped>
+.template-modal {
+	:deep(.el-col:nth-child(n + 4)) {
+		.template-card {
+			margin-bottom: 0;
+		}
+	}
+
+	.template-card {
+		padding: 20px;
+		border: 1px solid #e5e5e5;
+		border-radius: 8px;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		text-align: center;
+		transition: all 0.3s ease;
+		cursor: pointer;
+		margin-bottom: 24px;
+
+		&:hover {
+			border-color: #0076ff;
+			box-shadow: 0 4px 12px rgba(0, 118, 255, 0.15);
+			transform: translateY(-2px);
+
+			.template-title {
+				color: #0076ff;
+			}
+		}
+
+		.template-icon {
+			font-size: 48px;
+			margin-bottom: 12px;
+		}
+
+		.template-title {
+			font-size: 14px;
+			font-weight: 600;
+			color: #2c2c2c;
+			margin-bottom: 8px;
+			transition: color 0.2s;
+		}
+
+		.template-description {
+			font-size: 12px;
+			color: #666;
+			margin-bottom: 12px;
+			min-height: 36px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			line-height: 1.4;
+		}
+
+		.use-btn {
+			width: 100%;
+		}
+	}
+}
+</style>

+ 233 - 6
apps/web/src/layouts/MainLayout.vue

@@ -1,27 +1,254 @@
 <template>
-	<el-container style="height: 100vh">
+	<el-container style="height: 100vh; position: relative">
 		<el-aside width="auto">
-			<Sidebar />
+			<Sidebar
+				@settings-menu-toggle="(position, name) => handleSettingsMenuToggle(position, name)"
+			/>
 		</el-aside>
-
 		<el-container>
-			<el-main style="padding: 16px; overflow: auto" :style="mainStyle">
+			<el-main style="padding: 0; overflow: auto" :style="mainStyle">
 				<router-view />
 			</el-main>
 		</el-container>
+
+		<!-- 帮助菜单面板 -->
+		<div
+			v-if="showSettingsMenu && menuType === 'help'"
+			class="settings-menu-panel"
+			:style="{ transform: `translate(${menuLeft}px, ${menuTop}px)` }"
+		>
+			<div class="settings-menu-list">
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('quick-start')">
+					<div class="icon-wrapper">
+						<SvgIcon name="video" />
+					</div>
+					<span>快速入门</span>
+				</div>
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('docs')">
+					<div class="icon-wrapper">
+						<SvgIcon name="book" />
+					</div>
+					<span>文档</span>
+				</div>
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('about')">
+					<div class="icon-wrapper">
+						<SvgIcon name="info" />
+					</div>
+					<span>关于AI Agent</span>
+				</div>
+			</div>
+		</div>
+
+		<!-- 设置菜单面板 -->
+		<div
+			v-if="showSettingsMenu && menuType === 'settings'"
+			class="settings-menu-panel"
+			:style="{ transform: `translate(${menuLeft}px, ${menuTop}px)` }"
+		>
+			<div class="settings-menu-list">
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('user')">
+					<div class="icon-wrapper">
+						<SvgIcon name="user-round" />
+					</div>
+					<span>用户中心</span>
+				</div>
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('log-stream')">
+					<div class="icon-wrapper">
+						<SvgIcon name="log-in" />
+					</div>
+					<span>Agent日志跟踪</span>
+				</div>
+				<div class="settings-menu-item" @click="handleSettingsMenuClick('model-log')">
+					<div class="icon-wrapper">
+						<SvgIcon name="log-in" />
+					</div>
+					<span>Agent大模型调用日志</span>
+				</div>
+				<div class="settings-menu-item logout" @click="handleSettingsMenuClick('logout')">
+					<div class="icon-wrapper">
+						<SvgIcon name="door-open" />
+					</div>
+					<span>退出登录</span>
+				</div>
+			</div>
+		</div>
 	</el-container>
 </template>
 <script setup lang="ts">
-import { ref, provide, type CSSProperties } from 'vue'
+import { ref, provide, type CSSProperties, onMounted, onUnmounted } from 'vue'
+import { useRouter } from 'vue-router'
 import Sidebar from '@/components/Sidebar/index.vue'
 
+const router = useRouter()
 const mainStyle = ref<CSSProperties>()
+const showSettingsMenu = ref(false)
+const menuType = ref<'help' | 'settings'>('settings')
+const menuTop = ref(0)
+const menuLeft = ref(0)
+const settingsButtonPos = ref({ top: 0, left: 0 })
 
 provide('layout', {
 	setMainStyle(style: CSSProperties) {
 		mainStyle.value = style
 	}
 })
+
+const handleSettingsMenuToggle = (position: { top: number; left: number }, name: string) => {
+	// 如果点击同一个菜单,则切换显示/隐藏
+	// 如果点击不同菜单,则切换菜单类型
+	if (showSettingsMenu.value && menuType.value === name) {
+		showSettingsMenu.value = false
+	} else {
+		menuType.value = name as 'help' | 'settings'
+		showSettingsMenu.value = true
+		settingsButtonPos.value = { top: position.top, left: position.left }
+		updateMenuPosition()
+	}
+}
+
+const updateMenuPosition = () => {
+	// 菜单出现在点击位置的右下方,偏移量较小
+	let top = settingsButtonPos.value.top + 40 // 向下偏移40px
+	let left = settingsButtonPos.value.left
+
+	// 获取实际可用的视口大小(多层故障转移)
+	const viewportHeight =
+		document.documentElement.clientHeight || window.innerHeight || screen.height
+
+	const viewportWidth = document.documentElement.clientWidth || window.innerWidth || screen.width
+
+	// 防止菜单超出屏幕下方
+	const menuHeight = 165 // 菜单预估高度
+	const maxTop = viewportHeight - menuHeight
+	if (maxTop > 0) {
+		top = Math.max(0, maxTop - 10)
+	}
+
+	// 防止菜单超出屏幕右侧
+	const menuWidth = 240
+	const maxLeft = viewportWidth - menuWidth
+	if (left > maxLeft && maxLeft > 0) {
+		left = Math.max(0, maxLeft - 10)
+	}
+
+	menuTop.value = Math.max(0, top)
+	menuLeft.value = Math.max(0, left)
+}
+
+const handleWindowResize = () => {
+	if (showSettingsMenu.value) {
+		updateMenuPosition()
+	}
+}
+
+const handleSettingsMenuClick = (name: string) => {
+	showSettingsMenu.value = false
+
+	// 路由映射
+	const routeMap: Record<string, string> = {
+		'quick-start': '/quick-start',
+		docs: '/docs',
+		about: '/about',
+		user: '/user-center',
+		'log-stream': '/log-stream',
+		'model-log': '/model-log',
+		logout: ''
+	}
+
+	const route = routeMap[name]
+	if (route) {
+		router.push(route)
+	} else if (name === 'logout') {
+		// 处理退出登录
+		console.log('Logout')
+	}
+}
+
+const handleClickOutside = (event: MouseEvent) => {
+	const target = event.target as HTMLElement
+	// 检查点击是否在菜单面板外部
+	if (
+		showSettingsMenu.value &&
+		!target.closest('.settings-menu-panel') &&
+		!target.closest('.bottom-item')
+	) {
+		showSettingsMenu.value = false
+	}
+}
+
+onMounted(() => {
+	window.addEventListener('resize', handleWindowResize)
+	document.addEventListener('click', handleClickOutside)
+})
+
+onUnmounted(() => {
+	window.removeEventListener('resize', handleWindowResize)
+	document.removeEventListener('click', handleClickOutside)
+})
 </script>
 
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.settings-menu-panel {
+	position: fixed;
+	left: 0;
+	top: 0;
+	width: 240px;
+	background: #fff;
+	border: 1px solid #f0f0f0;
+	border-radius: 4px;
+	box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.12);
+	z-index: 999;
+	max-height: calc(100vh - 60px);
+	overflow-y: auto;
+
+	.settings-menu-list {
+		.settings-menu-item {
+			padding: 10px 8px;
+			color: #666;
+			cursor: pointer;
+			transition: all 0.2s;
+			font-size: 13px;
+			white-space: nowrap;
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			.icon-wrapper {
+				width: 25px;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+			}
+			span {
+				flex: 1;
+			}
+
+			&:hover {
+				background: #f5f5f5;
+				color: #333;
+			}
+
+			&.logout {
+				color: #ff6b6b;
+				border-top: 1px solid #f0f0f0;
+				margin-top: 4px;
+				padding-top: 12px;
+
+				&:hover {
+					background: #fff5f5;
+				}
+			}
+		}
+	}
+}
+
+@keyframes slideInRight {
+	from {
+		opacity: 0;
+		transform: translateX(-8px);
+	}
+	to {
+		opacity: 1;
+		transform: translateX(0);
+	}
+}
+</style>

+ 42 - 0
apps/web/src/router/index.ts

@@ -5,6 +5,13 @@ const Dashboard = () => import('@/views/Dashboard.vue')
 const Editor = () => import('@/views/Editor.vue')
 const Statistics = () => import('@/views/Statistics.vue')
 const Chat = () => import('@/views/Chat.vue')
+const Templates = () => import('@/views/TemplateDetail.vue')
+const QuickStart = () => import('@/views/QuickStart.vue')
+const Docs = () => import('@/views/Docs.vue')
+const About = () => import('@/views/About.vue')
+const UserCenter = () => import('@/views/UserCenter.vue')
+const LogStream = () => import('@/views/LogStream.vue')
+const ModelLog = () => import('@/views/ModelLog.vue')
 
 const routes = [
 	{
@@ -30,6 +37,41 @@ const routes = [
 				path: 'workflow/:id',
 				name: 'Editor',
 				component: Editor
+			},
+			{
+				path: 'templates/:id',
+				name: 'TemplateDetail',
+				component: Templates
+			},
+			{
+				path: 'quick-start',
+				name: 'QuickStart',
+				component: QuickStart
+			},
+			{
+				path: 'docs',
+				name: 'Docs',
+				component: Docs
+			},
+			{
+				path: 'about',
+				name: 'About',
+				component: About
+			},
+			{
+				path: 'user-center',
+				name: 'UserCenter',
+				component: UserCenter
+			},
+			{
+				path: 'log-stream',
+				name: 'LogStream',
+				component: LogStream
+			},
+			{
+				path: 'model-log',
+				name: 'ModelLog',
+				component: ModelLog
 			}
 		]
 	}

+ 94 - 48
apps/web/src/style.css

@@ -1,81 +1,127 @@
 :root {
-  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
-  line-height: 1.5;
-  font-weight: 400;
+	font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+	line-height: 1.5;
+	font-weight: 400;
 
-  color-scheme: light dark;
-  color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
+	color-scheme: light dark;
+	color: rgba(255, 255, 255, 0.87);
+	background-color: #242424;
 
-  font-synthesis: none;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+	font-synthesis: none;
+	text-rendering: optimizeLegibility;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
 
-  /* Element Plus theme color */
-  --el-color-primary: #ff6b6b;
+	/* Element Plus theme color */
+	--el-color-primary: #ff6b6b;
 }
 
 a {
-  font-weight: 500;
-  color: #646cff;
-  text-decoration: inherit;
+	font-weight: 500;
+	color: #646cff;
+	text-decoration: inherit;
 }
 a:hover {
-  color: #535bf2;
+	color: #535bf2;
 }
 
 body {
-  margin: 0;
-  padding: 0;
-  min-width: 320px;
-  height: 100%;
+	margin: 0;
+	padding: 0;
+	min-width: 320px;
+	height: 100%;
 }
 
 h1 {
-  font-size: 3.2em;
-  line-height: 1.1;
+	font-size: 3.2em;
+	line-height: 1.1;
 }
 
 button {
-  border-radius: 8px;
-  border: 1px solid transparent;
-  padding: 0.6em 1.2em;
-  font-size: 1em;
-  font-weight: 500;
-  font-family: inherit;
-  background-color: #1a1a1a;
-  cursor: pointer;
-  transition: border-color 0.25s;
+	border-radius: 8px;
+	border: 1px solid transparent;
+	padding: 0.6em 1.2em;
+	font-size: 1em;
+	font-weight: 500;
+	font-family: inherit;
+	background-color: #1a1a1a;
+	cursor: pointer;
+	transition: border-color 0.25s;
 }
 button:hover {
-  border-color: #646cff;
+	border-color: #646cff;
 }
 button:focus,
 button:focus-visible {
-  outline: 4px auto -webkit-focus-ring-color;
+	outline: 4px auto -webkit-focus-ring-color;
 }
 
 .card {
-  padding: 2em;
+	padding: 2em;
 }
 
 #app {
-  height: 100%;
-  width: 100%;
-  margin: 0;
-  padding: 0;
+	height: 100%;
+	width: 100%;
+	margin: 0;
+	padding: 0;
 }
 
 @media (prefers-color-scheme: light) {
-  :root {
-    color: #213547;
-    background-color: #ffffff;
-  }
-  a:hover {
-    color: #747bff;
-  }
-  button {
-    background-color: #f9f9f9;
-  }
+	:root {
+		color: #213547;
+		background-color: #ffffff;
+	}
+	a:hover {
+		color: #747bff;
+	}
+	button {
+		background-color: #f9f9f9;
+	}
+}
+
+/* Dialog 全局样式 */
+.el-dialog {
+	overflow: hidden;
+	border-top-left-radius: 4px !important;
+	border-top-right-radius: 4px !important;
+	padding: 0 !important;
+}
+.el-dialog__body {
+	padding: calc(var(--el-dialog-padding-primary)) var(--el-dialog-padding-primary);
+}
+
+.el-dialog__header {
+	border-bottom: 1px solid #e2e2e2 !important;
+	margin-right: 0 !important;
+	border-top-left-radius: 4px;
+	border-top-right-radius: 4px;
+	padding: 0px 13px !important;
+	line-height: 53px;
+	height: 50px;
+	color: var(--el-button-text-color) !important;
+	font-weight: bold;
+	font-size: 14px;
+	background-color: #fff !important;
+}
+
+.el-dialog__headerbtn {
+	height: 50px !important;
+	width: 50px !important;
+	top: 0 !important;
+}
+
+.el-dialog__footer {
+	padding: var(--el-dialog-padding-primary) !important;
+	padding-top: 0 !important;
+	text-align: right;
+	box-sizing: border-box;
+}
+
+.el-dialog__headerbtn .el-dialog__close {
+	color: var(--el-button-text-color) !important;
+}
+
+.el-dialog__title {
+	color: var(--el-button-text-color);
 }

+ 176 - 0
apps/web/src/views/About.vue

@@ -0,0 +1,176 @@
+<template>
+	<div class="about-container">
+		<div class="hero-section">
+			<div class="logo-area">
+				<div class="logo-circle">
+					<span class="logo-text">AI</span>
+				</div>
+				<h1>AI Agent</h1>
+			</div>
+			<p class="version">版本 1.0.0</p>
+		</div>
+
+		<div class="content-section">
+			<div class="info-card">
+				<h2>关于我们</h2>
+				<p>
+					AI Agent
+					是一个强大且灵活的自动化工作流平台,帮助团队和个人构建智能化的业务流程。我们致力于让自动化变得简单、高效且易于维护。
+				</p>
+			</div>
+
+			<div class="features-grid">
+				<div class="feature-card">
+					<SvgIcon name="zap" class="feature-icon" />
+					<h3>高性能</h3>
+					<p>优化的执行引擎,确保工作流快速可靠运行</p>
+				</div>
+				<div class="feature-card">
+					<SvgIcon name="shield" class="feature-icon" />
+					<h3>安全可靠</h3>
+					<p>企业级安全标准,保护你的数据和凭证</p>
+				</div>
+				<div class="feature-card">
+					<SvgIcon name="users" class="feature-icon" />
+					<h3>团队协作</h3>
+					<p>支持多人协作,共同构建和维护工作流</p>
+				</div>
+				<div class="feature-card">
+					<SvgIcon name="code" class="feature-icon" />
+					<h3>开放扩展</h3>
+					<p>支持自定义节点和集成,满足个性化需求</p>
+				</div>
+			</div>
+
+			<div class="footer-text">
+				<p>&copy; 2026 AI Agent. All rights reserved.</p>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped>
+.about-container {
+	max-width: 1000px;
+	margin: 0 auto;
+	padding: 40px 20px;
+
+	.hero-section {
+		text-align: center;
+		padding: 60px 0;
+		background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+		border-radius: 20px;
+		margin-bottom: 40px;
+		color: #fff;
+
+		.logo-area {
+			margin-bottom: 16px;
+
+			.logo-circle {
+				width: 120px;
+				height: 120px;
+				background: rgba(255, 255, 255, 0.2);
+				backdrop-filter: blur(10px);
+				border-radius: 50%;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin: 0 auto 24px;
+				border: 3px solid rgba(255, 255, 255, 0.3);
+
+				.logo-text {
+					font-size: 48px;
+					font-weight: 900;
+					letter-spacing: -2px;
+				}
+			}
+
+			h1 {
+				font-size: 42px;
+				font-weight: 700;
+				margin: 0;
+			}
+		}
+
+		.version {
+			font-size: 16px;
+			opacity: 0.9;
+		}
+	}
+
+	.content-section {
+		.info-card {
+			background: #fff;
+			border-radius: 12px;
+			padding: 32px;
+			margin-bottom: 24px;
+			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+			h2 {
+				font-size: 24px;
+				font-weight: 600;
+				color: #333;
+				margin-bottom: 16px;
+			}
+
+			p {
+				font-size: 15px;
+				color: #666;
+				line-height: 1.8;
+			}
+		}
+
+		.features-grid {
+			display: grid;
+			grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+			gap: 20px;
+			margin-bottom: 24px;
+
+			.feature-card {
+				background: #fff;
+				border-radius: 12px;
+				padding: 28px;
+				text-align: center;
+				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+				transition: all 0.3s;
+
+				&:hover {
+					transform: translateY(-4px);
+					box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
+				}
+
+				.feature-icon {
+					font-size: 40px;
+					color: #667eea;
+					margin-bottom: 16px;
+				}
+
+				h3 {
+					font-size: 18px;
+					font-weight: 600;
+					color: #333;
+					margin-bottom: 8px;
+				}
+
+				p {
+					font-size: 14px;
+					color: #666;
+					line-height: 1.6;
+				}
+			}
+		}
+
+		.footer-text {
+			text-align: center;
+			padding: 20px 0;
+
+			p {
+				font-size: 14px;
+				color: #999;
+			}
+		}
+	}
+}
+</style>

+ 1 - 19
apps/web/src/views/Chat.vue

@@ -96,15 +96,7 @@
 </template>
 
 <script setup lang="ts">
-import {
-	ref,
-	computed,
-	onMounted,
-	nextTick,
-	onBeforeUnmount,
-	inject,
-	type CSSProperties
-} from 'vue'
+import { ref, computed, onMounted, nextTick, onBeforeUnmount } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { BubbleList, Sender } from 'vue-element-plus-x'
 import { useChatStore } from '@/store/modules/chat.store'
@@ -157,16 +149,6 @@ onMounted(() => {
 	}
 })
 
-const layout = inject<{ setMainStyle: (style: CSSProperties) => void }>('layout')
-
-layout?.setMainStyle({
-	padding: '0px'
-})
-
-onBeforeUnmount(() => {
-	layout?.setMainStyle({})
-})
-
 // 创建新对话
 const handleNewChat = () => {
 	chatStore.createConversation()

+ 3 - 3
apps/web/src/views/Dashboard.vue

@@ -114,7 +114,7 @@
 				</div>
 
 				<div>
-					<el-button type="text">筛选</el-button>
+					<el-button text>筛选</el-button>
 				</div>
 			</div>
 
@@ -137,7 +137,7 @@
 				</div>
 
 				<div style="display: flex; gap: 8px">
-					<el-button type="text">筛选</el-button>
+					<el-button text>筛选</el-button>
 				</div>
 			</div>
 
@@ -189,7 +189,7 @@
 						<el-table-column type="selection" width="50" />
 						<el-table-column prop="workflow" label="工作流程">
 							<template #default="scope">
-								<el-button type="text" @click="$router.push(`/workflow/${scope.row.id}`)">{{
+								<el-button text @click="$router.push(`/workflow/${scope.row.id}`)">{{
 									scope.row.workflow
 								}}</el-button>
 							</template>

+ 353 - 0
apps/web/src/views/Docs.vue

@@ -0,0 +1,353 @@
+<template>
+	<div class="docs-container">
+		<aside class="sidebar">
+			<div class="sidebar-header">
+				<h2>文档导航</h2>
+			</div>
+			<nav class="nav-menu">
+				<div class="nav-section">
+					<h3>快速开始</h3>
+					<ul>
+						<li :class="{ active: activeDoc === 'intro' }" @click="activeDoc = 'intro'">
+							介绍
+						</li>
+						<li
+							:class="{ active: activeDoc === 'installation' }"
+							@click="activeDoc = 'installation'"
+						>
+							安装部署
+						</li>
+					</ul>
+				</div>
+				<div class="nav-section">
+					<h3>核心概念</h3>
+					<ul>
+						<li :class="{ active: activeDoc === 'workflow' }" @click="activeDoc = 'workflow'">
+							工作流
+						</li>
+						<li :class="{ active: activeDoc === 'nodes' }" @click="activeDoc = 'nodes'">节点</li>
+						<li
+							:class="{ active: activeDoc === 'credentials' }"
+							@click="activeDoc = 'credentials'"
+						>
+							凭证管理
+						</li>
+					</ul>
+				</div>
+				<div class="nav-section">
+					<h3>节点文档</h3>
+					<ul>
+						<li :class="{ active: activeDoc === 'http' }" @click="activeDoc = 'http'">
+							HTTP 节点
+						</li>
+						<li :class="{ active: activeDoc === 'code' }" @click="activeDoc = 'code'">
+							代码节点
+						</li>
+						<li :class="{ active: activeDoc === 'database' }" @click="activeDoc = 'database'">
+							数据库节点
+						</li>
+					</ul>
+				</div>
+			</nav>
+		</aside>
+
+		<main class="docs-content">
+			<article class="doc-article">
+				<h1>{{ docTitle }}</h1>
+				<div class="doc-body">
+					<p class="lead">{{ docDescription }}</p>
+
+					<section class="doc-section">
+						<h2>概述</h2>
+						<p>
+							AI Agent 是一个强大的自动化工作流平台,允许你创建复杂的自动化流程。通过可视化的工作流编辑器,你可以轻松添加各种节点类型,连接各种服务和应用,实现数据的自动化处理和传输。
+						</p>
+					</section>
+
+					<section class="doc-section">
+						<h2>主要特性</h2>
+						<ul class="feature-list">
+							<li>
+								<strong>可视化编辑器</strong> - 直观的工作流设计器,支持开始、结束、HTTP请求、条件分支、代码执行、数据查询等多种节点
+							</li>
+							<li>
+								<strong>丰富的节点库</strong> - 支持 HTTP、数据库、代码执行等多种节点类型
+							</li>
+							<li>
+								<strong>实时调试</strong> - 支持单步执行和断点调试
+							</li>
+							<li>
+								<strong>数据转换</strong> - 强大的数据映射和转换能力
+							</li>
+						</ul>
+					</section>
+
+					<section class="doc-section">
+						<h2>快速示例</h2>
+						<div class="code-block">
+							<pre><code>// 在代码节点中处理数据
+const data = $input.all();
+
+const result = data.map(item => ({
+  id: item.json.id,
+  name: item.json.name.toUpperCase(),
+  timestamp: new Date().toISOString()
+}));
+
+return result;</code></pre>
+						</div>
+					</section>
+
+					<section class="doc-section">
+						<h2>下一步</h2>
+						<div class="next-steps">
+							<a href="#" class="step-link">
+								<SvgIcon name="video" />
+								<span>观看教程视频</span>
+							</a>
+							<a href="#" class="step-link">
+								<SvgIcon name="box" />
+								<span>浏览示例模板</span>
+							</a>
+						</div>
+					</section>
+				</div>
+			</article>
+		</main>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+
+const activeDoc = ref('intro')
+
+const docTitle = computed(() => {
+	const titles: Record<string, string> = {
+		intro: 'AI Agent 介绍',
+		installation: '安装与部署',
+		workflow: '工作流概念',
+		nodes: '节点系统',
+		credentials: '凭证管理',
+		http: 'HTTP 节点',
+		code: '代码节点',
+		database: '数据库节点'
+	}
+	return titles[activeDoc.value] || 'AI Agent 文档'
+})
+
+const docDescription = computed(() => {
+	const descriptions: Record<string, string> = {
+		intro: '了解 AI Agent 的核心概念和基本使用方法',
+		installation: '学习如何在你的环境中安装和配置 AI Agent',
+		workflow: '深入理解工作流的设计模式和最佳实践',
+		nodes: '掌握节点系统的工作原理和使用技巧',
+		credentials: '安全地管理和使用第三方服务凭证',
+		http: '使用 HTTP 节点与 REST API 进行交互',
+		code: '编写自定义代码处理复杂的业务逻辑',
+		database: '连接和操作各种数据库系统'
+	}
+	return descriptions[activeDoc.value] || ''
+})
+</script>
+
+<style lang="less" scoped>
+.docs-container {
+	display: flex;
+	height: calc(100vh - 32px);
+	background: #f8f9fa;
+
+	.sidebar {
+		width: 280px;
+		background: #fff;
+		border-right: 1px solid #e5e7eb;
+		overflow-y: auto;
+		flex-shrink: 0;
+
+		.sidebar-header {
+			padding: 0px 20px;
+			border-bottom: 1px solid #e5e7eb;
+
+			h2 {
+				font-size: 18px;
+				font-weight: 600;
+				color: #333;
+			}
+		}
+
+		.nav-menu {
+			padding: 16px 0;
+
+			.nav-section {
+				margin-bottom: 24px;
+
+				h3 {
+					padding: 8px 20px;
+					font-size: 12px;
+					font-weight: 600;
+					color: #999;
+					text-transform: uppercase;
+					letter-spacing: 0.5px;
+				}
+
+				ul {
+					list-style: none;
+					padding: 0;
+					margin: 0;
+
+					li {
+						padding: 8px 20px 8px 32px;
+						font-size: 14px;
+						color: #666;
+						cursor: pointer;
+						transition: all 0.2s;
+
+						&:hover {
+							background: #f8f9fa;
+							color: #333;
+						}
+
+						&.active {
+							color: #667eea;
+							background: #f0f3ff;
+							border-right: 3px solid #667eea;
+							font-weight: 500;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.docs-content {
+		flex: 1;
+		overflow-y: auto;
+		padding: 40px;
+
+		.doc-article {
+			max-width: 900px;
+			margin: 0 auto;
+			background: #fff;
+			border-radius: 12px;
+			padding: 48px;
+			box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+
+			h1 {
+				font-size: 36px;
+				font-weight: 700;
+				color: #333;
+				margin-bottom: 16px;
+			}
+
+			.doc-body {
+				.lead {
+					font-size: 18px;
+					color: #666;
+					margin-bottom: 32px;
+					line-height: 1.6;
+				}
+
+				.doc-section {
+					margin-bottom: 40px;
+
+					h2 {
+						font-size: 24px;
+						font-weight: 600;
+						color: #333;
+						margin-bottom: 16px;
+						padding-bottom: 8px;
+						border-bottom: 2px solid #f0f0f0;
+					}
+
+					p {
+						font-size: 15px;
+						color: #666;
+						line-height: 1.8;
+						margin-bottom: 16px;
+					}
+
+					.feature-list {
+						list-style: none;
+						padding: 0;
+
+						li {
+							padding: 12px 0;
+							padding-left: 28px;
+							position: relative;
+							color: #666;
+							line-height: 1.6;
+
+							&:before {
+								content: '✓';
+								position: absolute;
+								left: 0;
+								color: #667eea;
+								font-weight: 700;
+							}
+
+							strong {
+								color: #333;
+							}
+						}
+					}
+
+					.code-block {
+						background: #282c34;
+						border-radius: 8px;
+						padding: 20px;
+						overflow-x: auto;
+						margin: 16px 0;
+
+						pre {
+							margin: 0;
+
+							code {
+								color: #abb2bf;
+								font-family: 'Courier New', monospace;
+								font-size: 14px;
+								line-height: 1.6;
+							}
+						}
+					}
+
+					.next-steps {
+						display: flex;
+						gap: 16px;
+						margin-top: 24px;
+
+						.step-link {
+							flex: 1;
+							display: flex;
+							align-items: center;
+							gap: 12px;
+							padding: 16px 20px;
+							background: #f8f9fa;
+							border: 1px solid #e5e7eb;
+							border-radius: 8px;
+							text-decoration: none;
+							color: #333;
+							transition: all 0.3s;
+
+							&:hover {
+								background: #fff;
+								border-color: #667eea;
+								box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
+							}
+
+							svg {
+								font-size: 24px;
+								color: #667eea;
+							}
+
+							span {
+								font-size: 14px;
+								font-weight: 500;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 245 - 0
apps/web/src/views/LogStream.vue

@@ -0,0 +1,245 @@
+<template>
+	<div class="log-stream-container">
+		<div class="header">
+			<h1>Agent 日志跟踪</h1>
+			<div class="header-actions">
+				<el-input
+					v-model="searchKeyword"
+					placeholder="搜索日志..."
+					:prefix-icon="Search"
+					style="width: 300px"
+				/>
+				<el-button type="primary" @click="refreshLogs">
+					<el-icon>
+						<RefreshRight />
+					</el-icon>
+					刷新
+				</el-button>
+			</div>
+		</div>
+
+		<div class="filters">
+			<el-select v-model="selectedLevel" placeholder="日志级别" style="width: 150px">
+				<el-option label="全部" value="" />
+				<el-option label="信息" value="info" />
+				<el-option label="警告" value="warning" />
+				<el-option label="错误" value="error" />
+			</el-select>
+			<el-date-picker
+				v-model="dateRange"
+				type="daterange"
+				range-separator="至"
+				start-placeholder="开始日期"
+				end-placeholder="结束日期"
+			/>
+		</div>
+
+		<div class="logs-container">
+			<div v-for="log in filteredLogs" :key="log.id" :class="['log-item', `log-${log.level}`]">
+				<div class="log-header">
+					<span :class="['log-level', `level-${log.level}`]">{{ log.level.toUpperCase() }}</span>
+					<span class="log-time">{{ log.timestamp }}</span>
+					<span class="log-workflow">{{ log.workflowName }}</span>
+				</div>
+				<div class="log-message">{{ log.message }}</div>
+				<div v-if="log.details" class="log-details">
+					<pre>{{ JSON.stringify(log.details, null, 2) }}</pre>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { Search, RefreshRight } from '@element-plus/icons-vue'
+import { ref, computed } from 'vue'
+
+const searchKeyword = ref('')
+const selectedLevel = ref('')
+const dateRange = ref([])
+
+const logs = ref([
+	{
+		id: 1,
+		level: 'info',
+		timestamp: '2026-01-28 14:23:45',
+		workflowName: '数据同步工作流',
+		message: '工作流执行成功',
+		details: { executionTime: '2.5s', nodesExecuted: 5 }
+	},
+	{
+		id: 2,
+		level: 'warning',
+		timestamp: '2026-01-28 14:20:12',
+		workflowName: 'API 调用工作流',
+		message: '请求响应时间超过阈值',
+		details: { responseTime: '3200ms', threshold: '3000ms' }
+	},
+	{
+		id: 3,
+		level: 'error',
+		timestamp: '2026-01-28 14:15:33',
+		workflowName: '邮件发送工作流',
+		message: '邮件发送失败: SMTP 连接超时',
+		details: { error: 'Connection timeout', host: 'xxxxx.com' }
+	},
+	{
+		id: 4,
+		level: 'info',
+		timestamp: '2026-01-28 14:10:08',
+		workflowName: '数据处理工作流',
+		message: '处理了 1000 条数据记录',
+		details: { processed: 1000, failed: 0 }
+	},
+	{
+		id: 5,
+		level: 'info',
+		timestamp: '2026-01-28 14:05:22',
+		workflowName: '定时任务工作流',
+		message: '定时任务触发执行',
+		details: { schedule: '*/5 * * * *', triggeredBy: 'cron' }
+	}
+])
+
+const filteredLogs = computed(() => {
+	return logs.value.filter((log) => {
+		const matchesKeyword =
+			!searchKeyword.value ||
+			log.message.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
+			log.workflowName.toLowerCase().includes(searchKeyword.value.toLowerCase())
+		const matchesLevel = !selectedLevel.value || log.level === selectedLevel.value
+		return matchesKeyword && matchesLevel
+	})
+})
+
+const refreshLogs = () => {
+	console.log('Refreshing logs...')
+}
+</script>
+
+<style lang="less" scoped>
+.log-stream-container {
+	max-width: 1400px;
+	margin: 0 auto;
+	padding: 20px;
+
+	.header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 24px;
+
+		h1 {
+			font-size: 28px;
+			font-weight: 600;
+			color: #333;
+		}
+
+		.header-actions {
+			display: flex;
+			gap: 12px;
+			align-items: center;
+		}
+	}
+
+	.filters {
+		display: flex;
+		gap: 16px;
+		margin-bottom: 24px;
+	}
+
+	.logs-container {
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+
+		.log-item {
+			background: #fff;
+			border-radius: 8px;
+			padding: 16px;
+			border-left: 4px solid #e5e7eb;
+			box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+			transition: all 0.3s;
+
+			&:hover {
+				box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+			}
+
+			&.log-info {
+				border-left-color: #3b82f6;
+			}
+
+			&.log-warning {
+				border-left-color: #f59e0b;
+			}
+
+			&.log-error {
+				border-left-color: #ef4444;
+			}
+
+			.log-header {
+				display: flex;
+				align-items: center;
+				gap: 12px;
+				margin-bottom: 8px;
+
+				.log-level {
+					font-size: 11px;
+					font-weight: 700;
+					padding: 4px 8px;
+					border-radius: 4px;
+					text-transform: uppercase;
+
+					&.level-info {
+						background: #dbeafe;
+						color: #1e40af;
+					}
+
+					&.level-warning {
+						background: #fef3c7;
+						color: #92400e;
+					}
+
+					&.level-error {
+						background: #fee2e2;
+						color: #991b1b;
+					}
+				}
+
+				.log-time {
+					font-size: 13px;
+					color: #666;
+				}
+
+				.log-workflow {
+					font-size: 13px;
+					color: #667eea;
+					font-weight: 500;
+				}
+			}
+
+			.log-message {
+				font-size: 14px;
+				color: #333;
+				line-height: 1.6;
+				margin-bottom: 8px;
+			}
+
+			.log-details {
+				background: #f8f9fa;
+				border-radius: 4px;
+				padding: 12px;
+				margin-top: 12px;
+
+				pre {
+					margin: 0;
+					font-size: 12px;
+					color: #666;
+					line-height: 1.6;
+					overflow-x: auto;
+				}
+			}
+		}
+	}
+}
+</style>

+ 360 - 0
apps/web/src/views/ModelLog.vue

@@ -0,0 +1,360 @@
+<template>
+	<div class="model-log-container">
+		<div class="header">
+			<h1>Agent 大模型调用日志</h1>
+			<div class="header-actions">
+				<el-button type="primary" @click="refreshLogs">
+					<el-icon>
+						<RefreshRight />
+					</el-icon>
+					刷新
+				</el-button>
+			</div>
+		</div>
+
+		<div class="stats-cards">
+			<div class="stat-card">
+				<div class="stat-icon success">
+					<SvgIcon name="check-circle" size="30" />
+				</div>
+				<div class="stat-info">
+					<div class="stat-value">2,345</div>
+					<div class="stat-label">成功调用</div>
+				</div>
+			</div>
+			<div class="stat-card">
+				<div class="stat-icon error">
+					<SvgIcon name="x-circle" size="30" />
+				</div>
+				<div class="stat-info">
+					<div class="stat-value">12</div>
+					<div class="stat-label">失败调用</div>
+				</div>
+			</div>
+			<div class="stat-card">
+				<div class="stat-icon">
+					<SvgIcon name="zap" size="30" />
+				</div>
+				<div class="stat-info">
+					<div class="stat-value">1.2s</div>
+					<div class="stat-label">平均响应时间</div>
+				</div>
+			</div>
+			<div class="stat-card">
+				<div class="stat-icon">
+					<SvgIcon name="dollar-sign" size="30" />
+				</div>
+				<div class="stat-info">
+					<div class="stat-value">$45.67</div>
+					<div class="stat-label">今日消费</div>
+				</div>
+			</div>
+		</div>
+
+		<div class="logs-table">
+			<el-table :data="modelLogs" stripe style="width: 100%">
+				<el-table-column prop="timestamp" label="时间" />
+				<el-table-column prop="model" label="模型" />
+				<el-table-column prop="workflowName" label="工作流" />
+				<el-table-column label="状态">
+					<template #default="scope">
+						<el-tag :type="scope.row.status === 'success' ? 'success' : 'danger'">
+							{{ scope.row.status === 'success' ? '成功' : '失败' }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column prop="tokens" label="Token 数" />
+				<el-table-column prop="cost" label="费用" />
+				<el-table-column prop="responseTime" label="响应时间" />
+				<el-table-column label="操作">
+					<template #default="scope">
+						<el-button size="small" @click="viewDetails(scope.row)">查看</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+		</div>
+
+		<!-- 详情对话框 -->
+		<el-dialog v-model="dialogVisible" title="调用详情" width="800px">
+			<div v-if="selectedLog" class="log-detail">
+				<div class="detail-section">
+					<h3>基本信息</h3>
+					<div class="detail-grid">
+						<div class="detail-item">
+							<span class="label">时间:</span>
+							<span class="value">{{ selectedLog.timestamp }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">模型:</span>
+							<span class="value">{{ selectedLog.model }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">工作流:</span>
+							<span class="value">{{ selectedLog.workflowName }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">状态:</span>
+							<el-tag :type="selectedLog.status === 'success' ? 'success' : 'danger'">
+								{{ selectedLog.status === 'success' ? '成功' : '失败' }}
+							</el-tag>
+						</div>
+					</div>
+				</div>
+
+				<div class="detail-section">
+					<h3>请求内容</h3>
+					<div class="code-block">
+						<pre>{{ selectedLog.prompt }}</pre>
+					</div>
+				</div>
+
+				<div class="detail-section">
+					<h3>响应内容</h3>
+					<div class="code-block">
+						<pre>{{ selectedLog.response }}</pre>
+					</div>
+				</div>
+
+				<div class="detail-section">
+					<h3>使用统计</h3>
+					<div class="detail-grid">
+						<div class="detail-item">
+							<span class="label">输入 Token:</span>
+							<span class="value">{{ selectedLog.inputTokens }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">输出 Token:</span>
+							<span class="value">{{ selectedLog.outputTokens }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">总计:</span>
+							<span class="value">{{ selectedLog.tokens }}</span>
+						</div>
+						<div class="detail-item">
+							<span class="label">费用:</span>
+							<span class="value">{{ selectedLog.cost }}</span>
+						</div>
+					</div>
+				</div>
+			</div>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { RefreshRight } from '@element-plus/icons-vue'
+import { ref } from 'vue'
+
+const dialogVisible = ref(false)
+const selectedLog = ref<any>(null)
+
+const modelLogs = ref([
+	{
+		id: 1,
+		timestamp: '2026-01-28 14:25:33',
+		model: 'GPT-4',
+		workflowName: 'AI 文章生成',
+		status: 'success',
+		tokens: '1,250',
+		cost: '$0.025',
+		responseTime: '1.8s',
+		inputTokens: 450,
+		outputTokens: 800,
+		prompt: '请帮我生成一篇关于人工智能发展的文章...',
+		response: '人工智能的发展历程可以追溯到...'
+	},
+	{
+		id: 2,
+		timestamp: '2026-01-28 14:23:15',
+		model: 'Claude-3',
+		workflowName: '智能客服',
+		status: 'success',
+		tokens: '680',
+		cost: '$0.014',
+		responseTime: '0.9s',
+		inputTokens: 280,
+		outputTokens: 400,
+		prompt: '用户询问: 如何退货?',
+		response: '您好,退货流程如下...'
+	},
+	{
+		id: 3,
+		timestamp: '2026-01-28 14:20:42',
+		model: 'GPT-4',
+		workflowName: '代码审查助手',
+		status: 'success',
+		tokens: '2,100',
+		cost: '$0.042',
+		responseTime: '2.3s',
+		inputTokens: 1200,
+		outputTokens: 900,
+		prompt: '请审查以下代码...',
+		response: '代码审查结果: 整体结构良好...'
+	},
+	{
+		id: 4,
+		timestamp: '2026-01-28 14:18:05',
+		model: 'GPT-3.5',
+		workflowName: '数据分析',
+		status: 'failed',
+		tokens: '0',
+		cost: '$0.000',
+		responseTime: '5.0s',
+		inputTokens: 0,
+		outputTokens: 0,
+		prompt: '分析销售数据...',
+		response: 'Error: API rate limit exceeded'
+	}
+])
+
+const refreshLogs = () => {
+	console.log('Refreshing model logs...')
+}
+
+const viewDetails = (log: any) => {
+	selectedLog.value = log
+	dialogVisible.value = true
+}
+</script>
+
+<style lang="less" scoped>
+.model-log-container {
+	max-width: 1400px;
+	margin: 0 auto;
+	padding: 20px;
+
+	.header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 24px;
+
+		h1 {
+			font-size: 28px;
+			font-weight: 600;
+			color: #333;
+		}
+
+		.header-actions {
+			display: flex;
+			gap: 12px;
+		}
+	}
+
+	.stats-cards {
+		display: grid;
+		grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+		gap: 20px;
+		margin-bottom: 24px;
+
+		.stat-card {
+			background: #fff;
+			border-radius: 12px;
+			padding: 24px;
+			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+			display: flex;
+			align-items: center;
+			gap: 20px;
+
+			.stat-icon {
+				width: 56px;
+				height: 56px;
+				border-radius: 12px;
+				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				color: #fff;
+				font-size: 28px;
+
+				&.success {
+					background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+				}
+
+				&.error {
+					background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+				}
+			}
+
+			.stat-info {
+				.stat-value {
+					font-size: 28px;
+					font-weight: 700;
+					color: #333;
+					line-height: 1;
+					margin-bottom: 8px;
+				}
+
+				.stat-label {
+					font-size: 13px;
+					color: #666;
+				}
+			}
+		}
+	}
+
+	.logs-table {
+		background: #fff;
+		border-radius: 12px;
+		padding: 20px;
+		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+	}
+
+	.log-detail {
+		.detail-section {
+			margin-bottom: 24px;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			h3 {
+				font-size: 16px;
+				font-weight: 600;
+				color: #333;
+				margin-bottom: 16px;
+			}
+
+			.detail-grid {
+				display: grid;
+				grid-template-columns: repeat(2, 1fr);
+				gap: 16px;
+
+				.detail-item {
+					display: flex;
+					align-items: center;
+					gap: 8px;
+
+					.label {
+						font-size: 14px;
+						color: #666;
+						font-weight: 500;
+					}
+
+					.value {
+						font-size: 14px;
+						color: #333;
+					}
+				}
+			}
+
+			.code-block {
+				background: #f8f9fa;
+				border-radius: 8px;
+				padding: 16px;
+				max-height: 300px;
+				overflow-y: auto;
+
+				pre {
+					margin: 0;
+					font-size: 13px;
+					color: #333;
+					line-height: 1.6;
+					white-space: pre-wrap;
+					word-wrap: break-word;
+				}
+			}
+		}
+	}
+}
+</style>

+ 242 - 0
apps/web/src/views/QuickStart.vue

@@ -0,0 +1,242 @@
+<template>
+	<div class="quick-start-container">
+		<div class="header">
+			<h1>快速入门</h1>
+			<p class="subtitle">欢迎使用 AI Agent,让我们开始你的第一个工作流</p>
+		</div>
+
+		<div class="content">
+			<div class="video-section">
+				<div class="video-placeholder">
+					<SvgIcon name="video" class="video-icon" size="60" />
+					<p>教学视频</p>
+				</div>
+			</div>
+
+			<div class="steps-section">
+				<h2>开始步骤</h2>
+				<div class="steps">
+					<div class="step-card">
+						<div class="step-number">1</div>
+						<div class="step-content">
+							<h3>创建工作流</h3>
+							<p>点击左侧菜单的 "+" 按钮,选择"工作流程"创建你的第一个工作流</p>
+						</div>
+					</div>
+					<div class="step-card">
+						<div class="step-number">2</div>
+						<div class="step-content">
+							<h3>添加节点</h3>
+							<p>
+								支持多种节点类型 -
+								包括开始、结束、HTTP请求、条件分支、代码执行、数据查询等,快速搭建自动化业务逻辑
+							</p>
+						</div>
+					</div>
+					<div class="step-card">
+						<div class="step-number">3</div>
+						<div class="step-content">
+							<h3>配置与测试</h3>
+							<p>配置节点参数,测试运行,确保工作流按预期执行</p>
+						</div>
+					</div>
+					<div class="step-card">
+						<div class="step-number">4</div>
+						<div class="step-content">
+							<h3>部署上线</h3>
+							<p>保存并激活工作流,让它自动为你工作</p>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<div class="resources-section">
+				<h2>更多资源</h2>
+				<div class="resource-cards">
+					<div class="resource-card" @click="$router.push('/docs')">
+						<SvgIcon name="book" size="60" />
+						<h3>查看文档</h3>
+						<p>深入了解所有功能特性</p>
+					</div>
+					<div class="resource-card" @click="$router.push('/templates/1')">
+						<SvgIcon name="box" size="60" />
+						<h3>浏览模板</h3>
+						<p>使用预设模板快速开始</p>
+					</div>
+					<div class="resource-card">
+						<SvgIcon name="help" size="60" />
+						<h3>获取帮助</h3>
+						<p>遇到问题?联系我们</p>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped>
+.quick-start-container {
+	max-width: 1200px;
+	margin: 0 auto;
+	padding: 40px 20px;
+
+	.header {
+		text-align: center;
+		margin-bottom: 60px;
+
+		h1 {
+			font-size: 36px;
+			font-weight: 700;
+			color: #333;
+			margin-bottom: 12px;
+		}
+
+		.subtitle {
+			font-size: 16px;
+			color: #666;
+		}
+	}
+
+	.content {
+		.video-section {
+			margin-bottom: 60px;
+
+			.video-placeholder {
+				width: 100%;
+				max-width: 800px;
+				margin: 0 auto;
+				aspect-ratio: 16/9;
+				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+				border-radius: 12px;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				color: #fff;
+
+				.video-icon {
+					font-size: 64px;
+					margin-bottom: 16px;
+				}
+
+				p {
+					font-size: 18px;
+					font-weight: 500;
+				}
+			}
+		}
+
+		.steps-section {
+			margin-bottom: 60px;
+
+			h2 {
+				font-size: 28px;
+				font-weight: 600;
+				color: #333;
+				margin-bottom: 32px;
+			}
+
+			.steps {
+				display: grid;
+				grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+				gap: 24px;
+
+				.step-card {
+					background: #fff;
+					border: 1px solid #f0f0f0;
+					border-radius: 12px;
+					padding: 24px;
+					display: flex;
+					gap: 16px;
+					transition: all 0.3s ease;
+
+					&:hover {
+						box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+						transform: translateY(-2px);
+					}
+
+					.step-number {
+						width: 40px;
+						height: 40px;
+						background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+						color: #fff;
+						border-radius: 50%;
+						display: flex;
+						align-items: center;
+						justify-content: center;
+						font-size: 20px;
+						font-weight: 700;
+						flex-shrink: 0;
+					}
+
+					.step-content {
+						h3 {
+							font-size: 18px;
+							font-weight: 600;
+							color: #333;
+							margin-bottom: 8px;
+						}
+
+						p {
+							font-size: 14px;
+							color: #666;
+							line-height: 1.6;
+						}
+					}
+				}
+			}
+		}
+
+		.resources-section {
+			h2 {
+				font-size: 28px;
+				font-weight: 600;
+				color: #333;
+				margin-bottom: 32px;
+			}
+
+			.resource-cards {
+				display: grid;
+				grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+				gap: 24px;
+
+				.resource-card {
+					background: #fff;
+					border: 1px solid #f0f0f0;
+					border-radius: 12px;
+					padding: 32px;
+					text-align: center;
+					cursor: pointer;
+					transition: all 0.3s ease;
+
+					&:hover {
+						border-color: #667eea;
+						box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
+						transform: translateY(-4px);
+					}
+
+					svg {
+						font-size: 48px;
+						color: #667eea;
+						margin-bottom: 16px;
+					}
+
+					h3 {
+						font-size: 18px;
+						font-weight: 600;
+						color: #333;
+						margin-bottom: 8px;
+					}
+
+					p {
+						font-size: 14px;
+						color: #666;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 213 - 0
apps/web/src/views/TemplateDetail.vue

@@ -0,0 +1,213 @@
+<template>
+	<div class="template-detail-page">
+		<!-- 顶部栏 -->
+		<div class="detail-header">
+			<div class="header-left">
+				<el-button text @click="goBack" class="back-btn">
+					<span style="font-size: 18px; margin-right: 8px"> <SvgIcon name="goback" /> </span>返回
+				</el-button>
+				<el-divider direction="vertical" />
+				<span class="breadcrumb">模板</span>
+			</div>
+			<div class="template-title">{{ currentTemplate?.title }}</div>
+			<el-button type="primary" @click="useTemplate"> 使用此工作流程 </el-button>
+		</div>
+
+		<!-- 详情内容 -->
+		<div class="detail-content">
+			<!-- 流程图占位 -->
+			<div class="workflow-diagram">
+				<img
+					src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 400'%3E%3Crect width='800' height='400' fill='%23f5f5f5'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-size='16' fill='%23999'%3E流程图占位%3C/text%3E%3C/svg%3E"
+					alt="workflow-diagram"
+					class="diagram-image"
+				/>
+			</div>
+
+			<!-- 下方说明文本 -->
+			<div class="detail-description">
+				<h3>{{ currentTemplate?.title }}</h3>
+				<p>{{ currentTemplate?.description }}</p>
+				<p class="detail-text">
+					此工作流程通过自动化手段连接电子邮件服务、AI 模型和数据库,帮助您高效管理和处理大量信息。
+				</p>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+
+interface Template {
+	id: string
+	title: string
+	description: string
+	icon: string
+}
+
+const router = useRouter()
+const route = useRoute()
+
+const currentTemplate = ref<Template | null>(null)
+
+const templates: Record<string, Template> = {
+	'1': {
+		id: '1',
+		title: 'Gmail + GPT-4o-mini',
+		description: '使用 Gmail、GPT-4o-mini 和 Notion 自动处理电子邮件分类和摘要',
+		icon: '✉️'
+	},
+	'2': {
+		id: '2',
+		title: 'Telegram + OpenAI',
+		description: '使用 Telegram、OpenAI 和 Google Drive 将聊天内容分类为搜索引擎,并生成 PDF 文件',
+		icon: '💬'
+	},
+	'3': {
+		id: '3',
+		title: 'Gmail + Memo RAG',
+		description: '由 Gmail 和 Memo 提供持有的 RAG 代码的电子邮件分析',
+		icon: '📧'
+	},
+	'4': {
+		id: '4',
+		title: 'Mistral + OpenRouter',
+		description: '使用 Mistral 通过 OpenRouter 与 AI 模型处理工作流',
+		icon: '🤖'
+	},
+	'5': {
+		id: '5',
+		title: 'RAG 架构',
+		description: '基于 Supabase、TogetherAI 和 OpenRouter 的 RAG 架构',
+		icon: '🔍'
+	},
+	'6': {
+		id: '6',
+		title: 'LINE + Supabase',
+		description: '结合 LINE Messaging、Supabase Vector DB 和 Gmail 的 AI 综合应用本表记功能',
+		icon: '💌'
+	}
+}
+
+onMounted(() => {
+	const templateId = route.params.id as string
+	currentTemplate.value = templates[templateId] || null
+})
+
+// 返回上一个路由,保留所有参数
+const goBack = () => {
+	router.back()
+}
+
+// 使用此工作流程,跳转到编辑器
+const useTemplate = () => {
+	router.push('/workflow/0')
+}
+</script>
+
+<style lang="less" scoped>
+.template-detail-page {
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	background: #fff;
+
+	.detail-header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 0 24px;
+		border-bottom: 1px solid #f0f0f0;
+		height: 50px;
+
+		.header-left {
+			display: flex;
+			align-items: center;
+			font-size: 14px;
+			color: #666;
+
+			.back-btn {
+				padding: 0;
+				color: #0076ff;
+				font-size: 14px;
+
+				&:hover {
+					color: #0056cc;
+				}
+			}
+
+			:deep(.el-divider--vertical) {
+				margin: 0 12px;
+				height: 20px;
+				background-color: #e5e5e5;
+			}
+
+			.breadcrumb {
+				color: #666;
+			}
+		}
+
+		.template-title {
+			flex: 1;
+			text-align: center;
+			font-size: 18px;
+			font-weight: 600;
+			color: #2c2c2c;
+			margin: 0 24px;
+		}
+	}
+
+	.detail-content {
+		flex: 1;
+		overflow-y: auto;
+		padding: 32px 24px;
+		display: flex;
+		flex-direction: column;
+		gap: 32px;
+
+		.workflow-diagram {
+			flex: 1;
+			min-height: 400px;
+			background: #f9f9f9;
+			border: 1px solid #e5e5e5;
+			border-radius: 8px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			overflow: hidden;
+
+			.diagram-image {
+				width: 100%;
+				height: 100%;
+				object-fit: contain;
+			}
+		}
+
+		.detail-description {
+			padding: 24px;
+			background: #f5f7fa;
+			border-radius: 8px;
+
+			h3 {
+				font-size: 16px;
+				font-weight: 600;
+				color: #2c2c2c;
+				margin: 0 0 12px 0;
+			}
+
+			p {
+				font-size: 14px;
+				color: #666;
+				margin: 0 0 8px 0;
+				line-height: 1.6;
+
+				&.detail-text {
+					color: #555;
+				}
+			}
+		}
+	}
+}
+</style>

+ 274 - 0
apps/web/src/views/UserCenter.vue

@@ -0,0 +1,274 @@
+<template>
+	<div class="user-center-container">
+		<div class="header">
+			<h1>用户中心</h1>
+		</div>
+
+		<div class="content">
+			<div class="profile-card">
+				<div class="avatar-section">
+					<div class="avatar">
+						<SvgIcon name="user-round" size="60"/>
+					</div>
+					<el-button type="primary" size="small">更换头像</el-button>
+				</div>
+				<div class="info-section">
+					<h2>个人信息</h2>
+					<el-form label-width="100px">
+						<el-form-item label="用户名">
+							<el-input v-model="userInfo.username" placeholder="请输入用户名" />
+						</el-form-item>
+						<el-form-item label="邮箱">
+							<el-input v-model="userInfo.email" placeholder="请输入邮箱" />
+						</el-form-item>
+						<el-form-item label="手机号">
+							<el-input v-model="userInfo.phone" placeholder="请输入手机号" />
+						</el-form-item>
+						<el-form-item label="公司">
+							<el-input v-model="userInfo.company" placeholder="请输入公司名称" />
+						</el-form-item>
+						<el-form-item>
+							<el-button type="primary">保存修改</el-button>
+							<el-button>取消</el-button>
+						</el-form-item>
+					</el-form>
+				</div>
+			</div>
+
+			<div class="stats-grid">
+				<div class="stat-card">
+					<div class="stat-icon">
+						<SvgIcon name="workflow" size="30" />
+					</div>
+					<div class="stat-info">
+						<div class="stat-value">12</div>
+						<div class="stat-label">工作流数量</div>
+					</div>
+				</div>
+				<div class="stat-card">
+					<div class="stat-icon">
+						<SvgIcon name="play" size="30" />
+					</div>
+					<div class="stat-info">
+						<div class="stat-value">1,234</div>
+						<div class="stat-label">执行次数</div>
+					</div>
+				</div>
+				<div class="stat-card">
+					<div class="stat-icon">
+						<SvgIcon name="clock" size="30" />
+					</div>
+					<div class="stat-info">
+						<div class="stat-value">45</div>
+						<div class="stat-label">节省小时</div>
+					</div>
+				</div>
+			</div>
+
+			<div class="security-card">
+				<h2>账号安全</h2>
+				<div class="security-items">
+					<div class="security-item">
+						<div class="security-left">
+							<SvgIcon name="lock" />
+							<div class="security-text">
+								<div class="security-title">登录密码</div>
+								<div class="security-desc">定期更换密码可以提高账号安全性</div>
+							</div>
+						</div>
+						<el-button size="small">修改</el-button>
+					</div>
+					<div class="security-item">
+						<div class="security-left">
+							<SvgIcon name="shield" />
+							<div class="security-text">
+								<div class="security-title">双因素认证</div>
+								<div class="security-desc">未开启</div>
+							</div>
+						</div>
+						<el-button size="small">开启</el-button>
+					</div>
+					<div class="security-item">
+						<div class="security-left">
+							<SvgIcon name="smartphone" />
+							<div class="security-text">
+								<div class="security-title">绑定手机</div>
+								<div class="security-desc">131****1111</div>
+							</div>
+						</div>
+						<el-button size="small">更换</el-button>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const userInfo = ref({
+	username: 'admin',
+	email: 'admin@qq.com',
+	phone: '13333333333333',
+	company: 'AI Agent'
+})
+</script>
+
+<style lang="less" scoped>
+.user-center-container {
+	max-width: 1200px;
+	margin: 0 auto;
+	padding: 20px;
+
+	.header {
+		margin-bottom: 32px;
+
+		h1 {
+			font-size: 28px;
+			font-weight: 600;
+			color: #333;
+		}
+	}
+
+	.content {
+		.profile-card {
+			background: #fff;
+			border-radius: 12px;
+			padding: 32px;
+			margin-bottom: 24px;
+			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+			display: flex;
+			gap: 48px;
+
+			.avatar-section {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				gap: 16px;
+
+				.avatar {
+					width: 120px;
+					height: 120px;
+					border-radius: 50%;
+					background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					color: #fff;
+					font-size: 60px;
+				}
+			}
+
+			.info-section {
+				flex: 1;
+
+				h2 {
+					font-size: 20px;
+					font-weight: 600;
+					color: #333;
+					margin-bottom: 24px;
+				}
+			}
+		}
+
+		.stats-grid {
+			display: grid;
+			grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+			gap: 20px;
+			margin-bottom: 24px;
+
+			.stat-card {
+				background: #fff;
+				border-radius: 12px;
+				padding: 24px;
+				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+				display: flex;
+				align-items: center;
+				gap: 20px;
+
+				.stat-icon {
+					width: 60px;
+					height: 60px;
+					border-radius: 12px;
+					background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					color: #fff;
+					font-size: 28px;
+				}
+
+				.stat-info {
+					.stat-value {
+						font-size: 32px;
+						font-weight: 700;
+						color: #333;
+						line-height: 1;
+						margin-bottom: 8px;
+					}
+
+					.stat-label {
+						font-size: 14px;
+						color: #666;
+					}
+				}
+			}
+		}
+
+		.security-card {
+			background: #fff;
+			border-radius: 12px;
+			padding: 32px;
+			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+			h2 {
+				font-size: 20px;
+				font-weight: 600;
+				color: #333;
+				margin-bottom: 24px;
+			}
+
+			.security-items {
+				display: flex;
+				flex-direction: column;
+				gap: 20px;
+
+				.security-item {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					padding: 20px;
+					background: #f8f9fa;
+					border-radius: 8px;
+
+					.security-left {
+						display: flex;
+						align-items: center;
+						gap: 16px;
+
+						svg {
+							font-size: 32px;
+							color: #667eea;
+						}
+
+						.security-text {
+							.security-title {
+								font-size: 15px;
+								font-weight: 500;
+								color: #333;
+								margin-bottom: 4px;
+							}
+
+							.security-desc {
+								font-size: 13px;
+								color: #666;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 3 - 0
apps/web/src/vite-env.d.ts

@@ -0,0 +1,3 @@
+/// <reference types="vite/client" />
+
+declare module 'virtual:svg-icons-register'

+ 3 - 2
apps/web/tsconfig.app.json

@@ -24,6 +24,7 @@
         "src/**/*.tsx",
         "src/**/*.vue",
         "src/**/**/*.vue",
-        "src/**/**/**/*.vue"
+        "src/**/**/**/*.vue",
+        "src/vite-env.d.ts"
     ]
-}
+}

+ 0 - 1
apps/web/tsconfig.node.json

@@ -4,7 +4,6 @@
     "target": "ES2023",
     "lib": ["ES2023"],
     "module": "ESNext",
-    "types": ["node"],
     "skipLibCheck": true,
 
     /* Bundler mode */