Trae Assistant commited on
Commit
dcce59d
·
1 Parent(s): 184ca85

Initial commit: Venture Bridge Studio v1.1

Browse files
Files changed (5) hide show
  1. Dockerfile +12 -0
  2. README.md +60 -5
  3. app.py +234 -0
  4. requirements.txt +2 -0
  5. templates/index.html +511 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md CHANGED
@@ -1,10 +1,65 @@
1
  ---
2
- title: Venture Bridge Studio
3
- emoji: 🌍
4
- colorFrom: pink
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: 创投桥梁工作室
3
+ emoji: 🌉
4
+ colorFrom: indigo
5
+ colorTo: green
6
  sdk: docker
7
  pinned: false
8
+ short_description: 创业项目路演、投资人匹配与冷启动对接一站式工具
9
  ---
10
 
11
+ # 创投桥梁工作室 (Venture Bridge Studio)
12
+
13
+ 一个专为早期创业者设计的一站式辅助工具,帮助你梳理项目、构建商业计划书(BP)、寻找匹配的投资人并生成对接邮件。
14
+
15
+ ## 核心功能
16
+
17
+ 1. **项目档案管理 (Project Profile)**
18
+ * 结构化录入项目基础信息(赛道、阶段、一句话介绍、亮点)。
19
+ * 本地存储,保护隐私。
20
+
21
+ 2. **智能 BP 大纲构建 (Smart Pitch Deck Builder)**
22
+ * 内置标准红杉/YC 风格的 10 页 BP 结构。
23
+ * 引导式填写,支持导出为文本大纲。
24
+
25
+ 3. **投资人模拟匹配 (Investor Match Simulation)**
26
+ * 基于模拟的投资机构数据库。
27
+ * 根据你的项目赛道和阶段,智能计算匹配度(Compatibility Score)。
28
+ * 直观展示匹配原因(如:赛道匹配、阶段偏好)。
29
+
30
+ 4. **冷启动邮件生成 (Outreach Email Generator)**
31
+ * 一键生成针对特定投资人的冷启动邮件(Cold Email)。
32
+ * 自动填入项目亮点和投资人称呼,提升回复率。
33
+
34
+ ## 技术栈
35
+
36
+ * **Backend:** Python / Flask
37
+ * **Frontend:** Vue.js 3 + Tailwind CSS
38
+ * **Deployment:** Docker
39
+
40
+ ## 如何运行
41
+
42
+ ### 本地运行
43
+
44
+ 1. 安装依赖:
45
+ ```bash
46
+ pip install -r requirements.txt
47
+ ```
48
+
49
+ 2. 运行应用:
50
+ ```bash
51
+ python app.py
52
+ ```
53
+
54
+ 3. 打开浏览器访问 `http://localhost:5000`
55
+
56
+ ### Docker 运行
57
+
58
+ ```bash
59
+ docker build -t venture-bridge .
60
+ docker run -p 5000:5000 venture-bridge
61
+ ```
62
+
63
+ ## 隐私说明
64
+
65
+ 本项目遵循 **Local-First** 原则,你的项目数据主要存储在本地浏览器(localStorage),不会上传至任何第三方服务器(匹配逻辑在后端进行,但仅用于计算,不持久化存储)。
app.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ from flask import Flask, render_template, request, jsonify, send_file
5
+
6
+ app = Flask(__name__)
7
+
8
+ # Configuration
9
+ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev_key_venture_bridge')
10
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload size
11
+ app.config['JSON_AS_ASCII'] = False # Support Chinese characters in JSON
12
+
13
+ # Mock Investor Database (Simulated Market Data - Expanded)
14
+ INVESTORS_DB = [
15
+ {
16
+ "id": 1,
17
+ "name": "红杉中国 (Sequoia China)",
18
+ "type": "VC",
19
+ "stages": ["Seed", "Series A", "Series B", "Growth"],
20
+ "sectors": ["TMT", "Healthcare", "Consumer", "Enterprise", "AI"],
21
+ "ticket_size": "1M-50M",
22
+ "focus": "High growth, market leaders, AI infrastructure",
23
+ "website": "https://www.sequoiacap.com/china/"
24
+ },
25
+ {
26
+ "id": 2,
27
+ "name": "真格基金 (ZhenFund)",
28
+ "type": "Angel/VC",
29
+ "stages": ["Angel", "Seed", "Pre-A"],
30
+ "sectors": ["TMT", "AI", "Consumer", "Education", "SaaS"],
31
+ "ticket_size": "100k-5M",
32
+ "focus": "Great founders, innovative ideas, young teams",
33
+ "website": "http://www.zhenfund.com/"
34
+ },
35
+ {
36
+ "id": 3,
37
+ "name": "高瓴资本 (Hillhouse)",
38
+ "type": "PE/VC",
39
+ "stages": ["Series B", "Series C", "Pre-IPO", "Buyout"],
40
+ "sectors": ["Healthcare", "Consumer", "TMT", "Industrials", "Hard Tech"],
41
+ "ticket_size": "10M-100M+",
42
+ "focus": "Long term value, structural opportunities",
43
+ "website": "https://www.hillhousecap.com/"
44
+ },
45
+ {
46
+ "id": 4,
47
+ "name": "IDG资本",
48
+ "type": "VC",
49
+ "stages": ["Series A", "Series B", "Growth"],
50
+ "sectors": ["TMT", "Advanced Mfg", "Clean Tech", "Consumer"],
51
+ "ticket_size": "5M-30M",
52
+ "focus": "Technology driven, early mover",
53
+ "website": "https://idgcapital.com/"
54
+ },
55
+ {
56
+ "id": 5,
57
+ "name": "经纬创投 (Matrix Partners)",
58
+ "type": "VC",
59
+ "stages": ["Series A", "Series B"],
60
+ "sectors": ["Mobile Internet", "SaaS", "Deep Tech", "Healthcare"],
61
+ "ticket_size": "3M-20M",
62
+ "focus": "Aggressive growth, strong post-investment support",
63
+ "website": "https://matrixpartners.com.cn/"
64
+ },
65
+ {
66
+ "id": 6,
67
+ "name": "奇绩创坛 (MiraclePlus)",
68
+ "type": "Accelerator",
69
+ "stages": ["Seed", "Angel"],
70
+ "sectors": ["AI", "Hard Tech", "BioTech", "Software"],
71
+ "ticket_size": "300k USD",
72
+ "focus": "Y Combinator style, tech-heavy, ambitious founders",
73
+ "website": "https://www.miracleplus.com/"
74
+ },
75
+ {
76
+ "id": 7,
77
+ "name": "源码资本 (Source Code)",
78
+ "type": "VC",
79
+ "stages": ["Series A", "Series B"],
80
+ "sectors": ["Industrial Internet", "New Consumer", "FinTech"],
81
+ "ticket_size": "5M-20M",
82
+ "focus": "Data driven, ecosystem, industrial digitization",
83
+ "website": "https://sourcecodecap.com/"
84
+ },
85
+ {
86
+ "id": 8,
87
+ "name": "五源资本 (5Y Capital)",
88
+ "type": "VC",
89
+ "stages": ["Series A", "Series B"],
90
+ "sectors": ["Cloud", "AI", "Consumer", "Semiconductors"],
91
+ "ticket_size": "5M-25M",
92
+ "focus": "Contrarian bets, disruptive technology",
93
+ "website": "https://5ycap.com/"
94
+ },
95
+ {
96
+ "id": 9,
97
+ "name": "GGV纪源资本",
98
+ "type": "VC",
99
+ "stages": ["Series A", "Series B", "Growth"],
100
+ "sectors": ["Enterprise Tech", "Smart Retail", "Social"],
101
+ "ticket_size": "5M-50M",
102
+ "focus": "Global perspective, cross-border",
103
+ "website": "https://www.ggvc.com/"
104
+ },
105
+ {
106
+ "id": 10,
107
+ "name": "启明创投 (Qiming)",
108
+ "type": "VC",
109
+ "stages": ["Series A", "Series B"],
110
+ "sectors": ["Healthcare", "TMT"],
111
+ "ticket_size": "5M-30M",
112
+ "focus": "Deep industry expertise, healthcare leaders",
113
+ "website": "https://www.qimingvc.com/"
114
+ }
115
+ ]
116
+
117
+ # Pitch Deck Templates (Structure)
118
+ PITCH_DECK_STRUCTURE = [
119
+ {"title": "1. Problem (痛点)", "desc": "描述当前市场存在的具体问题或未被满足的需求。用户现在的痛苦是什么?", "placeholder": "例如:目前的中小企业在招聘时面临..."},
120
+ {"title": "2. Solution (解决方案)", "desc": "你的产品或服务如何解决上述问题。你的价值主张是什么?", "placeholder": "我们提供了一个基于AI的..."},
121
+ {"title": "3. Market Size (市场规模)", "desc": "TAM (潜在市场), SAM (可服务市场), SOM (可获得市场) 分析。", "placeholder": "全球市场规模达到...其中我们可以触达..."},
122
+ {"title": "4. Product (产品介绍)", "desc": "核心功能、用户体验、截图或Demo。", "placeholder": "核心功能包括..."},
123
+ {"title": "5. Business Model (商业模式)", "desc": "你如何赚钱(定价、收费模式)。", "placeholder": "��们采用SaaS订阅模式..."},
124
+ {"title": "6. Traction (运营数据)", "desc": "当前的用户数、收入、增长率等关键指标。", "placeholder": "目前已有1000+注册用户..."},
125
+ {"title": "7. Competition (竞争分析)", "desc": "竞争对手是谁,你的核心优势(护城河)是什么。", "placeholder": "主要竞品有...我们的优势在于..."},
126
+ {"title": "8. Go-to-Market (市场推广)", "desc": "获客渠道、销售策略。", "placeholder": "我们将通过内容营销..."},
127
+ {"title": "9. Team (团队介绍)", "desc": "创始团队背景,为什么你们能做成这件事。", "placeholder": "CEO来自...CTO曾任职..."},
128
+ {"title": "10. Financials & Ask (财务与融资)", "desc": "财务预测,本轮融资金额、出让比例、资金用途。", "placeholder": "我们要融资500万,用于..."}
129
+ ]
130
+
131
+ @app.route('/')
132
+ def index():
133
+ return render_template('index.html')
134
+
135
+ @app.route('/health')
136
+ def health_check():
137
+ return jsonify({"status": "healthy", "version": "1.0.0"})
138
+
139
+ @app.route('/api/investors', methods=['GET'])
140
+ def get_investors():
141
+ return jsonify(INVESTORS_DB)
142
+
143
+ @app.route('/api/match', methods=['POST'])
144
+ def match_investors():
145
+ try:
146
+ data = request.json
147
+ if not data:
148
+ return jsonify([])
149
+
150
+ project_stage = data.get('stage')
151
+ project_sector = data.get('sector')
152
+
153
+ matches = []
154
+ for investor in INVESTORS_DB:
155
+ score = 0
156
+ reasons = []
157
+
158
+ # Stage match
159
+ if project_stage in investor['stages']:
160
+ score += 35
161
+ reasons.append(f"阶段匹配: {project_stage}")
162
+
163
+ # Sector match
164
+ if project_sector in investor['sectors']:
165
+ score += 35
166
+ reasons.append(f"赛道匹配: {project_sector}")
167
+ elif "TMT" in investor['sectors'] and project_sector in ["AI", "SaaS", "Mobile Internet"]: # Broad category fallback
168
+ score += 15
169
+ reasons.append("赛道相关 (TMT)")
170
+
171
+ # Random noise for realism (simulating brand fit, timing, etc.)
172
+ # Seeded random to make it consistent for the same request if needed, but here we want some variety
173
+ luck = random.randint(5, 20)
174
+ score += luck
175
+
176
+ # Bonus for specific strong matches
177
+ if project_sector == "AI" and "AI" in investor['sectors']:
178
+ score += 10
179
+ reasons.append("AI 专项基金")
180
+
181
+ if score > 40:
182
+ matches.append({
183
+ "investor": investor,
184
+ "score": min(score, 99), # Cap at 99
185
+ "reasons": reasons
186
+ })
187
+
188
+ # Sort by score desc
189
+ matches.sort(key=lambda x: x['score'], reverse=True)
190
+ return jsonify(matches)
191
+ except Exception as e:
192
+ app.logger.error(f"Error in match_investors: {str(e)}")
193
+ return jsonify({"error": "Matching failed"}), 500
194
+
195
+ @app.route('/api/deck-structure', methods=['GET'])
196
+ def get_deck_structure():
197
+ return jsonify(PITCH_DECK_STRUCTURE)
198
+
199
+ @app.route('/api/generate-email', methods=['POST'])
200
+ def generate_email():
201
+ try:
202
+ data = request.json
203
+ investor_name = data.get('investor_name', 'Investor')
204
+ founder_name = data.get('founder_name', 'Founder')
205
+ project_name = data.get('project_name', 'MyProject')
206
+ one_liner = data.get('one_liner', 'building the next big thing')
207
+ highlights = data.get('highlights', 'growing 10% MoM')
208
+
209
+ # More sophisticated template
210
+ email_body = f"""Subject: {project_name}: {one_liner}
211
+
212
+ Hi {investor_name} Team,
213
+
214
+ I'm {founder_name}, founder of {project_name}. We are {one_liner}.
215
+
216
+ I've been following {investor_name}'s investments in this space (especially your focus on {data.get('sector', 'our sector')}) and believe we fit your thesis well.
217
+
218
+ Key Highlights:
219
+ - {highlights}
220
+
221
+ I've attached our deck below. Would you be open to a brief 15-min call next week to discuss?
222
+
223
+ Best regards,
224
+ {founder_name}
225
+ Founder, {project_name}
226
+ """
227
+ return jsonify({"email": email_body})
228
+ except Exception as e:
229
+ return jsonify({"error": str(e)}), 500
230
+
231
+ if __name__ == '__main__':
232
+ # Use 7860 for Hugging Face Spaces compatibility
233
+ port = int(os.environ.get('PORT', 7860))
234
+ app.run(host='0.0.0.0', port=port, debug=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ flask
2
+ gunicorn
templates/index.html ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>创投桥梁工作室 Venture Bridge Studio</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <script src="https://unpkg.com/lucide@latest"></script>
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
12
+ body { font-family: 'Inter', sans-serif; }
13
+ .slide-enter-active, .slide-leave-active { transition: all 0.3s ease; }
14
+ .slide-enter-from, .slide-leave-to { opacity: 0; transform: translateY(20px); }
15
+ .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
16
+ .fade-enter-from, .fade-leave-to { opacity: 0; }
17
+ /* Custom scrollbar */
18
+ ::-webkit-scrollbar { width: 8px; }
19
+ ::-webkit-scrollbar-track { background: #f1f1f1; }
20
+ ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
21
+ ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
22
+ [v-cloak] { display: none; }
23
+ </style>
24
+ <script>
25
+ tailwind.config = {
26
+ theme: {
27
+ extend: {
28
+ colors: {
29
+ primary: '#4f46e5',
30
+ secondary: '#10b981',
31
+ dark: '#0f172a'
32
+ }
33
+ }
34
+ }
35
+ }
36
+ </script>
37
+ </head>
38
+ <body class="bg-slate-50 text-slate-800 h-screen flex overflow-hidden">
39
+ <div id="app" v-cloak class="flex w-full h-full">
40
+
41
+ <!-- Mobile Menu Overlay -->
42
+ <div v-if="isMobileMenuOpen" class="fixed inset-0 bg-black bg-opacity-50 z-20 lg:hidden" @click="isMobileMenuOpen = false"></div>
43
+
44
+ <!-- Sidebar -->
45
+ <aside :class="['fixed lg:static inset-y-0 left-0 z-30 w-64 bg-dark text-white flex flex-col transition-transform duration-300 lg:transform-none', isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full']">
46
+ <div class="p-6 border-b border-slate-700 flex justify-between items-center">
47
+ <div>
48
+ <h1 class="text-xl font-bold flex items-center gap-2">
49
+ <i data-lucide="briefcase" class="w-6 h-6 text-primary"></i>
50
+ 创投桥梁
51
+ </h1>
52
+ <p class="text-xs text-slate-400 mt-1">Venture Bridge Studio</p>
53
+ </div>
54
+ <button @click="isMobileMenuOpen = false" class="lg:hidden text-slate-400 hover:text-white">
55
+ <i data-lucide="x" class="w-6 h-6"></i>
56
+ </button>
57
+ </div>
58
+
59
+ <nav class="flex-1 p-4 space-y-2 overflow-y-auto">
60
+ <button @click="switchTab('profile')"
61
+ :class="['w-full text-left px-4 py-3 rounded-lg flex items-center gap-3 transition-colors', currentTab === 'profile' ? 'bg-primary text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white']">
62
+ <i data-lucide="file-user" class="w-5 h-5"></i>
63
+ <span>项目档案</span>
64
+ </button>
65
+ <button @click="switchTab('deck')"
66
+ :class="['w-full text-left px-4 py-3 rounded-lg flex items-center gap-3 transition-colors', currentTab === 'deck' ? 'bg-primary text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white']">
67
+ <i data-lucide="presentation" class="w-5 h-5"></i>
68
+ <span>BP 大纲构建</span>
69
+ </button>
70
+ <button @click="switchTab('match')"
71
+ :class="['w-full text-left px-4 py-3 rounded-lg flex items-center gap-3 transition-colors', currentTab === 'match' ? 'bg-primary text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white']">
72
+ <i data-lucide="search" class="w-5 h-5"></i>
73
+ <span>投资人匹配</span>
74
+ </button>
75
+ <button @click="switchTab('outreach')"
76
+ :class="['w-full text-left px-4 py-3 rounded-lg flex items-center gap-3 transition-colors', currentTab === 'outreach' ? 'bg-primary text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white']">
77
+ <i data-lucide="send" class="w-5 h-5"></i>
78
+ <span>对接邮件生成</span>
79
+ </button>
80
+ </nav>
81
+
82
+ <div class="p-4 border-t border-slate-700 text-xs text-slate-500 text-center">
83
+ v1.1.0 | Local-First Design
84
+ </div>
85
+ </aside>
86
+
87
+ <!-- Main Content -->
88
+ <main class="flex-1 flex flex-col h-full overflow-hidden bg-slate-50">
89
+ <!-- Mobile Header -->
90
+ <header class="lg:hidden bg-white border-b border-slate-200 p-4 flex justify-between items-center">
91
+ <h1 class="font-bold text-slate-800">创投桥梁</h1>
92
+ <button @click="isMobileMenuOpen = true" class="text-slate-600">
93
+ <i data-lucide="menu" class="w-6 h-6"></i>
94
+ </button>
95
+ </header>
96
+
97
+ <div class="flex-1 overflow-y-auto p-4 md:p-8">
98
+ <!-- Tab: Profile -->
99
+ <transition name="fade" mode="out-in">
100
+ <div v-if="currentTab === 'profile'" key="profile" class="max-w-4xl mx-auto space-y-6">
101
+ <div class="bg-white p-6 md:p-8 rounded-xl shadow-sm border border-slate-200">
102
+ <h2 class="text-2xl font-bold mb-6 text-slate-800 border-b pb-4">项目基础档案</h2>
103
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
104
+ <div class="space-y-2">
105
+ <label class="block text-sm font-medium text-slate-700">项目名称</label>
106
+ <input v-model="project.name" type="text" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none" placeholder="例如:未来科技">
107
+ </div>
108
+ <div class="space-y-2">
109
+ <label class="block text-sm font-medium text-slate-700">创始人姓名</label>
110
+ <input v-model="project.founder" type="text" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none" placeholder="例如:张三">
111
+ </div>
112
+ <div class="space-y-2 md:col-span-2">
113
+ <label class="block text-sm font-medium text-slate-700">一句话介绍 (One Liner)</label>
114
+ <input v-model="project.oneLiner" type="text" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none" placeholder="例如:用AI重塑个人生产力工具">
115
+ </div>
116
+ <div class="space-y-2">
117
+ <label class="block text-sm font-medium text-slate-700">所属赛道</label>
118
+ <select v-model="project.sector" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none">
119
+ <option value="TMT">TMT / 互联网</option>
120
+ <option value="AI">人工智能 (AI)</option>
121
+ <option value="SaaS">企业服务 (SaaS)</option>
122
+ <option value="Consumer">新消费 (Consumer)</option>
123
+ <option value="Healthcare">医疗健康 (Healthcare)</option>
124
+ <option value="Hard Tech">硬科技 (Hard Tech)</option>
125
+ <option value="FinTech">金融科技 (FinTech)</option>
126
+ <option value="Education">教育科技 (EdTech)</option>
127
+ </select>
128
+ </div>
129
+ <div class="space-y-2">
130
+ <label class="block text-sm font-medium text-slate-700">融资阶段</label>
131
+ <select v-model="project.stage" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none">
132
+ <option value="Seed">种子轮 (Seed)</option>
133
+ <option value="Angel">天使轮 (Angel)</option>
134
+ <option value="Pre-A">Pre-A轮</option>
135
+ <option value="Series A">A轮 (Series A)</option>
136
+ <option value="Series B">B轮 (Series B)</option>
137
+ <option value="Growth">成长期 (Growth)</option>
138
+ </select>
139
+ </div>
140
+ <div class="space-y-2 md:col-span-2">
141
+ <label class="block text-sm font-medium text-slate-700">核心亮点 (Highlights)</label>
142
+ <textarea v-model="project.highlights" rows="3" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none" placeholder="例如:月增长30%,已有付费客户100家,团队来自BAT..."></textarea>
143
+ </div>
144
+ </div>
145
+ <div class="mt-8 flex justify-end">
146
+ <button @click="saveProfile" class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-indigo-700 transition-colors flex items-center gap-2 shadow-sm active:scale-95 transform">
147
+ <i data-lucide="save" class="w-4 h-4"></i> 保存档案
148
+ </button>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Tab: Deck Builder -->
154
+ <div v-else-if="currentTab === 'deck'" key="deck" class="max-w-5xl mx-auto space-y-6">
155
+ <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 gap-4">
156
+ <h2 class="text-2xl font-bold text-slate-800">BP 商业计划书大纲</h2>
157
+ <div class="flex gap-2">
158
+ <button @click="exportDeck" class="px-4 py-2 bg-white border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 flex items-center gap-2 shadow-sm transition-colors">
159
+ <i data-lucide="download" class="w-4 h-4"></i> 导出文本
160
+ </button>
161
+ <button @click="copyDeckToClipboard" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-indigo-700 flex items-center gap-2 shadow-sm transition-colors">
162
+ <i data-lucide="copy" class="w-4 h-4"></i> 复制全部
163
+ </button>
164
+ </div>
165
+ </div>
166
+
167
+ <div v-if="loadingDeck" class="text-center py-10">
168
+ <div class="animate-spin w-8 h-8 border-4 border-primary border-t-transparent rounded-full mx-auto"></div>
169
+ <p class="text-slate-500 mt-2">加载模板中...</p>
170
+ </div>
171
+
172
+ <div v-else class="grid grid-cols-1 gap-4">
173
+ <div v-for="(slide, index) in deckStructure" :key="index" class="bg-white p-6 rounded-xl shadow-sm border border-slate-200 transition-all hover:shadow-md">
174
+ <div class="flex items-start gap-4">
175
+ <div class="w-10 h-10 rounded-full bg-indigo-50 text-primary flex items-center justify-center font-bold text-lg flex-shrink-0 select-none">
176
+ ${ index + 1 }
177
+ </div>
178
+ <div class="flex-1 space-y-3">
179
+ <div>
180
+ <h3 class="font-bold text-lg text-slate-800">${ slide.title }</h3>
181
+ <p class="text-sm text-slate-500">${ slide.desc }</p>
182
+ </div>
183
+ <textarea v-model="slide.content" rows="3" :placeholder="slide.placeholder" class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-lg focus:bg-white focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"></textarea>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <!-- Tab: Investor Match -->
191
+ <div v-else-if="currentTab === 'match'" key="match" class="max-w-6xl mx-auto space-y-6">
192
+ <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
193
+ <div>
194
+ <h2 class="text-2xl font-bold text-slate-800">投资人智能匹配</h2>
195
+ <p class="text-slate-500 mt-1">基于你的项目赛道 (${ project.sector }) 和阶段 (${ project.stage }) 筛选潜在投资机构</p>
196
+ </div>
197
+ <button @click="runMatching" :disabled="isMatching" class="px-6 py-3 bg-secondary text-white rounded-lg hover:bg-emerald-600 shadow-lg shadow-emerald-200 transition-all flex items-center gap-2 transform active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed">
198
+ <i v-if="isMatching" data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>
199
+ <i v-else data-lucide="zap" class="w-5 h-5"></i>
200
+ <span>${ isMatching ? '匹配中...' : '开始匹配' }</span>
201
+ </button>
202
+ </div>
203
+
204
+ <div v-if="matchedInvestors.length === 0 && !hasRunMatch" class="text-center py-20 bg-white rounded-xl border border-dashed border-slate-300">
205
+ <div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4 text-slate-400">
206
+ <i data-lucide="search" class="w-8 h-8"></i>
207
+ </div>
208
+ <p class="text-slate-500">点击右上角按钮开始寻找匹配的投资人</p>
209
+ </div>
210
+
211
+ <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
212
+ <div v-for="match in matchedInvestors" :key="match.investor.id" class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden hover:shadow-md transition-all group relative flex flex-col h-full">
213
+ <div class="h-2 bg-gradient-to-r from-indigo-500 to-purple-500" :style="{ width: match.score + '%' }"></div>
214
+ <div class="p-6 flex-1 flex flex-col">
215
+ <div class="flex justify-between items-start mb-4">
216
+ <div>
217
+ <h3 class="font-bold text-lg text-slate-800 group-hover:text-primary transition-colors flex items-center gap-2">
218
+ ${ match.investor.name }
219
+ <a :href="match.investor.website" target="_blank" class="text-slate-400 hover:text-primary" title="访问官网">
220
+ <i data-lucide="external-link" class="w-3 h-3"></i>
221
+ </a>
222
+ </h3>
223
+ <span class="inline-block px-2 py-0.5 bg-slate-100 text-slate-600 text-xs rounded mt-1">${ match.investor.type }</span>
224
+ </div>
225
+ <div class="text-right">
226
+ <div class="text-2xl font-bold text-emerald-500">${ match.score }%</div>
227
+ <div class="text-xs text-slate-400">匹配度</div>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="space-y-3 text-sm text-slate-600 mb-6 flex-1">
232
+ <div class="flex items-center gap-2">
233
+ <i data-lucide="target" class="w-4 h-4 text-slate-400 flex-shrink-0"></i>
234
+ <span class="truncate">偏好: ${ match.investor.focus }</span>
235
+ </div>
236
+ <div class="flex items-center gap-2">
237
+ <i data-lucide="wallet" class="w-4 h-4 text-slate-400 flex-shrink-0"></i>
238
+ <span>单笔: ${ match.investor.ticket_size }</span>
239
+ </div>
240
+ <div class="flex flex-wrap gap-1 mt-2">
241
+ <span v-for="tag in match.reasons" class="px-2 py-0.5 bg-indigo-50 text-indigo-600 text-xs rounded border border-indigo-100">${ tag }</span>
242
+ </div>
243
+ </div>
244
+
245
+ <button @click="selectInvestorForOutreach(match.investor)" class="w-full py-2 border border-primary text-primary rounded-lg hover:bg-primary hover:text-white transition-colors flex items-center justify-center gap-2 mt-auto">
246
+ <i data-lucide="mail" class="w-4 h-4"></i> 生成对接邮件
247
+ </button>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+
253
+ <!-- Tab: Outreach -->
254
+ <div v-else-if="currentTab === 'outreach'" key="outreach" class="max-w-4xl mx-auto space-y-6">
255
+ <h2 class="text-2xl font-bold mb-6 text-slate-800">冷启动邮件生成器</h2>
256
+
257
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
258
+ <div class="lg:col-span-1 space-y-4">
259
+ <div class="bg-white p-4 rounded-xl shadow-sm border border-slate-200">
260
+ <label class="block text-sm font-medium text-slate-700 mb-2">选择目标投资人</label>
261
+ <div v-if="selectedInvestor" class="p-3 bg-indigo-50 border border-indigo-100 rounded-lg mb-3">
262
+ <div class="font-bold text-indigo-900">${ selectedInvestor.name }</div>
263
+ <div class="text-xs text-indigo-600">${ selectedInvestor.type }</div>
264
+ </div>
265
+ <button v-else @click="switchTab('match')" class="w-full py-2 border border-dashed border-slate-300 text-slate-500 rounded-lg hover:bg-slate-50 text-sm transition-colors">
266
+ + 从匹配列表选择
267
+ </button>
268
+
269
+ <button @click="generateEmail" :disabled="!selectedInvestor || isGenerating" :class="['w-full py-2 rounded-lg text-white font-medium mt-4 transition-colors flex justify-center items-center gap-2', selectedInvestor ? 'bg-primary hover:bg-indigo-700' : 'bg-slate-300 cursor-not-allowed']">
270
+ <i v-if="isGenerating" data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>
271
+ <span>${ isGenerating ? '生成中...' : '生成邮件内容' }</span>
272
+ </button>
273
+ </div>
274
+ </div>
275
+
276
+ <div class="lg:col-span-2">
277
+ <div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200 h-full flex flex-col">
278
+ <div class="flex justify-between items-center mb-4">
279
+ <h3 class="font-bold text-slate-800">邮件预览</h3>
280
+ <button @click="copyEmail" class="text-sm text-primary hover:text-indigo-700 flex items-center gap-1 transition-colors">
281
+ <i data-lucide="copy" class="w-4 h-4"></i> 复制内容
282
+ </button>
283
+ </div>
284
+ <textarea v-model="generatedEmail" class="flex-1 w-full p-4 bg-slate-50 border border-slate-200 rounded-lg font-mono text-sm leading-relaxed outline-none resize-none focus:ring-2 focus:ring-primary focus:border-primary" placeholder="点击左侧生成按钮..."></textarea>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ </transition>
290
+ </div>
291
+ </main>
292
+
293
+ <!-- Toast Notification -->
294
+ <transition name="slide">
295
+ <div v-if="toast.visible" :class="['fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white flex items-center gap-3 z-50', toast.type === 'error' ? 'bg-red-500' : 'bg-emerald-500']">
296
+ <i :data-lucide="toast.type === 'error' ? 'alert-circle' : 'check-circle'" class="w-5 h-5"></i>
297
+ <span>${ toast.message }</span>
298
+ </div>
299
+ </transition>
300
+ </div>
301
+
302
+ <script>
303
+ const { createApp, ref, onMounted, nextTick } = Vue;
304
+
305
+ createApp({
306
+ delimiters: ['${', '}'],
307
+ setup() {
308
+ const currentTab = ref('profile');
309
+ const hasRunMatch = ref(false);
310
+ const isMobileMenuOpen = ref(false);
311
+ const isMatching = ref(false);
312
+ const isGenerating = ref(false);
313
+ const loadingDeck = ref(false);
314
+
315
+ // Toast
316
+ const toast = ref({ visible: false, message: '', type: 'success' });
317
+ const showToast = (message, type = 'success') => {
318
+ toast.value = { visible: true, message, type };
319
+ setTimeout(() => toast.value.visible = false, 3000);
320
+ nextTick(() => lucide.createIcons());
321
+ };
322
+
323
+ // Project Data
324
+ const project = ref({
325
+ name: '',
326
+ founder: '',
327
+ oneLiner: '',
328
+ sector: 'TMT',
329
+ stage: 'Seed',
330
+ highlights: ''
331
+ });
332
+
333
+ // Deck Structure
334
+ const deckStructure = ref([]);
335
+
336
+ // Matching
337
+ const matchedInvestors = ref([]);
338
+ const selectedInvestor = ref(null);
339
+ const generatedEmail = ref('');
340
+
341
+ // Switch Tab Helper
342
+ const switchTab = (tab) => {
343
+ currentTab.value = tab;
344
+ isMobileMenuOpen.value = false;
345
+ nextTick(() => lucide.createIcons());
346
+ };
347
+
348
+ // Load initial data
349
+ onMounted(async () => {
350
+ // Load from localStorage if available
351
+ const saved = localStorage.getItem('venture_bridge_project');
352
+ if (saved) {
353
+ try {
354
+ const parsed = JSON.parse(saved);
355
+ project.value = { ...project.value, ...parsed };
356
+ } catch(e) {}
357
+ }
358
+
359
+ // Fetch deck structure
360
+ loadingDeck.value = true;
361
+ try {
362
+ const res = await fetch('/api/deck-structure');
363
+ const data = await res.json();
364
+ // Try to load saved deck content
365
+ const savedDeck = localStorage.getItem('venture_bridge_deck');
366
+ if (savedDeck) {
367
+ const parsedDeck = JSON.parse(savedDeck);
368
+ deckStructure.value = data.map((item, idx) => ({
369
+ ...item,
370
+ content: parsedDeck[idx]?.content || ''
371
+ }));
372
+ } else {
373
+ deckStructure.value = data.map(item => ({...item, content: ''}));
374
+ }
375
+ } catch (e) {
376
+ console.error(e);
377
+ showToast('加载模板失败', 'error');
378
+ } finally {
379
+ loadingDeck.value = false;
380
+ nextTick(() => lucide.createIcons());
381
+ }
382
+ });
383
+
384
+ const saveProfile = () => {
385
+ localStorage.setItem('venture_bridge_project', JSON.stringify(project.value));
386
+ showToast('档案已保存!');
387
+ };
388
+
389
+ const runMatching = async () => {
390
+ if (!project.value.name || !project.value.sector) {
391
+ showToast('请先完善项目档案', 'error');
392
+ switchTab('profile');
393
+ return;
394
+ }
395
+ saveProfile(); // Auto save
396
+ isMatching.value = true;
397
+ hasRunMatch.value = true;
398
+ try {
399
+ const res = await fetch('/api/match', {
400
+ method: 'POST',
401
+ headers: {'Content-Type': 'application/json'},
402
+ body: JSON.stringify(project.value)
403
+ });
404
+ if (!res.ok) throw new Error('Network error');
405
+ matchedInvestors.value = await res.json();
406
+ showToast('匹配完成');
407
+ nextTick(() => lucide.createIcons());
408
+ } catch (e) {
409
+ showToast('匹配失败,请重试', 'error');
410
+ } finally {
411
+ isMatching.value = false;
412
+ }
413
+ };
414
+
415
+ const selectInvestorForOutreach = (investor) => {
416
+ selectedInvestor.value = investor;
417
+ switchTab('outreach');
418
+ };
419
+
420
+ const generateEmail = async () => {
421
+ if (!selectedInvestor.value) return;
422
+ isGenerating.value = true;
423
+ try {
424
+ const res = await fetch('/api/generate-email', {
425
+ method: 'POST',
426
+ headers: {'Content-Type': 'application/json'},
427
+ body: JSON.stringify({
428
+ investor_name: selectedInvestor.value.name,
429
+ founder_name: project.value.founder,
430
+ project_name: project.value.name,
431
+ one_liner: project.value.oneLiner,
432
+ highlights: project.value.highlights,
433
+ sector: project.value.sector
434
+ })
435
+ });
436
+ const data = await res.json();
437
+ generatedEmail.value = data.email;
438
+ showToast('邮件内容已生成');
439
+ } catch (e) {
440
+ showToast('生成失败', 'error');
441
+ } finally {
442
+ isGenerating.value = false;
443
+ }
444
+ };
445
+
446
+ const copyEmail = () => {
447
+ if (!generatedEmail.value) return;
448
+ navigator.clipboard.writeText(generatedEmail.value).then(() => {
449
+ showToast('已复制到剪贴板');
450
+ });
451
+ };
452
+
453
+ const exportDeck = () => {
454
+ localStorage.setItem('venture_bridge_deck', JSON.stringify(deckStructure.value));
455
+ let content = `Project: ${project.value.name}\n\n`;
456
+ deckStructure.value.forEach(slide => {
457
+ content += `### ${slide.title}\n${slide.content || '(Empty)'}\n\n`;
458
+ });
459
+
460
+ const blob = new Blob([content], { type: 'text/plain' });
461
+ const url = URL.createObjectURL(blob);
462
+ const a = document.createElement('a');
463
+ a.href = url;
464
+ a.download = `${project.value.name || 'Project'}_Pitch_Deck.txt`;
465
+ a.click();
466
+ showToast('下载已开始');
467
+ };
468
+
469
+ const copyDeckToClipboard = () => {
470
+ localStorage.setItem('venture_bridge_deck', JSON.stringify(deckStructure.value));
471
+ let content = `Project: ${project.value.name}\n\n`;
472
+ deckStructure.value.forEach(slide => {
473
+ content += `### ${slide.title}\n${slide.content || '(Empty)'}\n\n`;
474
+ });
475
+ navigator.clipboard.writeText(content).then(() => {
476
+ showToast('大纲已复制');
477
+ });
478
+ }
479
+
480
+ // Watchers for icons update
481
+ Vue.watch(currentTab, () => {
482
+ nextTick(() => lucide.createIcons());
483
+ });
484
+
485
+ return {
486
+ currentTab,
487
+ project,
488
+ deckStructure,
489
+ matchedInvestors,
490
+ hasRunMatch,
491
+ selectedInvestor,
492
+ generatedEmail,
493
+ isMobileMenuOpen,
494
+ isMatching,
495
+ isGenerating,
496
+ loadingDeck,
497
+ toast,
498
+ switchTab,
499
+ saveProfile,
500
+ runMatching,
501
+ selectInvestorForOutreach,
502
+ generateEmail,
503
+ copyEmail,
504
+ exportDeck,
505
+ copyDeckToClipboard
506
+ };
507
+ }
508
+ }).mount('#app');
509
+ </script>
510
+ </body>
511
+ </html>