init
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
.env
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
8
.vite/deps/_metadata.json
Normal file
8
.vite/deps/_metadata.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"hash": "248dae27",
|
||||
"configHash": "34ef103e",
|
||||
"lockfileHash": "5e3a00f9",
|
||||
"browserHash": "6630477a",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
||||
3
.vite/deps/package.json
Normal file
3
.vite/deps/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ThinkFlow AI - 智能思维发散</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
3002
package-lock.json
generated
Normal file
3002
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "housegpt",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.3",
|
||||
"@vue-flow/core": "^1.48.1",
|
||||
"@vue-flow/node-resizer": "^1.5.0",
|
||||
"axios": "^1.6.7",
|
||||
"lucide-vue-next": "^0.322.0",
|
||||
"vue": "^3.4.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.0",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
416
src/App.vue
Normal file
416
src/App.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, h } from 'vue'
|
||||
import { Sparkles, Search, Folder, Monitor, BookOpen, Code, Sun, Globe, LogIn, Plus, MousePointer2, Zap, ArrowRight, RefreshCw, Image as ImageIcon } from 'lucide-vue-next'
|
||||
import { VueFlow, useVueFlow, Position, MarkerType } from '@vue-flow/core'
|
||||
import { Background, BackgroundVariant } from '@vue-flow/background'
|
||||
import { Controls } from '@vue-flow/controls'
|
||||
|
||||
// 导入 VueFlow 样式
|
||||
import '@vue-flow/core/dist/style.css'
|
||||
import '@vue-flow/core/dist/theme-default.css'
|
||||
|
||||
// API 配置
|
||||
const API_KEY = import.meta.env.VITE_ZHIPU_AI_API_KEY
|
||||
|
||||
// VueFlow 实例
|
||||
const { addNodes, addEdges, onConnect, setNodes, setEdges, nodes: flowNodes, edges: flowEdges, updateNode } = useVueFlow()
|
||||
|
||||
// 状态管理
|
||||
const ideaInput = ref('')
|
||||
const isLoading = ref(false)
|
||||
|
||||
/**
|
||||
* 调用智谱AI生成图片
|
||||
*/
|
||||
const generateNodeImage = async (nodeId: string, prompt: string) => {
|
||||
const node = flowNodes.value.find(n => n.id === nodeId)
|
||||
if (!node || node.data.isImageLoading) return
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: true } })
|
||||
|
||||
try {
|
||||
const response = await fetch('https://open.bigmodel.cn/api/paas/v4/images/generations', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'cogview-3-flash',
|
||||
prompt: `一张精美的、极简插画风格的图片,表现主题:${prompt}。要求:构图简洁,色彩明快,适合作为思维导图的视觉辅助。`
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('Image request failed')
|
||||
const data = await response.json()
|
||||
const imageUrl = data.data[0].url
|
||||
|
||||
updateNode(nodeId, { data: { ...node.data, imageUrl, isImageLoading: false } })
|
||||
} catch (error) {
|
||||
console.error('Image Generation Error:', error)
|
||||
updateNode(nodeId, { data: { ...node.data, isImageLoading: false } })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用智谱AI生成思维发散节点
|
||||
*/
|
||||
const expandIdea = async (param?: any) => {
|
||||
// 判断是点击了节点上的按钮还是主输入框
|
||||
const parentNode = param && param.id ? param : undefined
|
||||
const text = parentNode ? parentNode.data.label : ideaInput.value
|
||||
|
||||
if (!text || isLoading.value) return
|
||||
isLoading.value = true
|
||||
|
||||
// 如果是第一次生成,清空画布
|
||||
if (!parentNode) {
|
||||
setNodes([])
|
||||
setEdges([])
|
||||
}
|
||||
|
||||
const systemPrompt = `你是一个思维发散专家。给定一个想法,请将其发散出 3-5 个更深层或相关维度的子想法。
|
||||
每个子想法包含:简短的名称 (text) 和 极简的描述 (description)。
|
||||
返回格式必须为 JSON: { "nodes": [ { "text": "...", "description": "..." } ] }`
|
||||
|
||||
try {
|
||||
const response = await fetch('https://open.bigmodel.cn/api/paas/v4/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${API_KEY}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'glm-4-flash',
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: text }
|
||||
],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: 0.8
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('AI request failed')
|
||||
const data = await response.json()
|
||||
const result = JSON.parse(data.choices[0].message.content)
|
||||
|
||||
// 起始坐标
|
||||
const startX = parentNode ? parentNode.position.x + 450 : 50
|
||||
const startY = parentNode ? parentNode.position.y : 300
|
||||
|
||||
// 创建根节点 (如果是第一次)
|
||||
if (!parentNode) {
|
||||
const rootId = 'root-' + Date.now()
|
||||
addNodes({
|
||||
id: rootId,
|
||||
type: 'window',
|
||||
position: { x: startX, y: startY },
|
||||
data: { label: text, description: '核心想法', type: 'root' }
|
||||
})
|
||||
|
||||
// 为后续子节点计算位置
|
||||
processSubNodes(result.nodes, rootId, startX, startY)
|
||||
} else {
|
||||
processSubNodes(result.nodes, parentNode.id, startX, startY)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Expansion Error:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const processSubNodes = (subNodes: any[], parentId: string, baseX: number, baseY: number) => {
|
||||
subNodes.forEach((item: any, index: number) => {
|
||||
const childId = `node-${Date.now()}-${index}`
|
||||
const offsetX = 400
|
||||
const offsetY = (index - (subNodes.length - 1) / 2) * 250
|
||||
|
||||
addNodes({
|
||||
id: childId,
|
||||
type: 'window',
|
||||
position: { x: baseX + offsetX, y: baseY + offsetY },
|
||||
data: { label: item.text, description: item.description, type: 'child' }
|
||||
})
|
||||
|
||||
addEdges({
|
||||
id: `e-${parentId}-${childId}`,
|
||||
source: parentId,
|
||||
target: childId,
|
||||
animated: true,
|
||||
style: { stroke: '#fed7aa', strokeWidth: 2 },
|
||||
markerEnd: MarkerType.ArrowClosed
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const startNewSession = () => {
|
||||
ideaInput.value = ''
|
||||
setNodes([])
|
||||
setEdges([])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen w-screen bg-white font-mono text-slate-800 relative overflow-hidden flex flex-col selection:bg-orange-100">
|
||||
<!-- 顶部导航栏 -->
|
||||
<nav class="flex-none bg-white/80 backdrop-blur-md border-b border-slate-200 px-6 py-2 flex items-center justify-between shadow-sm z-50">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2.5 h-2.5 bg-emerald-400 rounded-full animate-pulse"></div>
|
||||
<span class="text-slate-400">ready</span>
|
||||
<span class="text-slate-300 mx-1">~/</span>
|
||||
<span class="font-bold text-slate-900 tracking-tight">ThinkFlow AI</span>
|
||||
<span class="w-1.5 h-5 bg-orange-200 ml-1"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden lg:flex items-center gap-2">
|
||||
<button class="nav-btn group">
|
||||
<span class="text-orange-500 font-bold">$</span>
|
||||
<span class="text-slate-600">ai</span>
|
||||
<span class="text-slate-300 ml-1">--search</span>
|
||||
</button>
|
||||
<button class="nav-btn">
|
||||
<span class="text-emerald-500 font-bold">$</span>
|
||||
<span class="text-slate-600">cd</span>
|
||||
<span class="text-slate-300 ml-1">/categories</span>
|
||||
</button>
|
||||
<button class="nav-btn">
|
||||
<span class="text-blue-500 font-bold">$</span>
|
||||
<span class="text-slate-600">watch</span>
|
||||
<span class="font-bold ml-1">stats</span>
|
||||
</button>
|
||||
<div class="h-4 w-[1px] bg-slate-200 mx-2"></div>
|
||||
<button class="p-2 hover:bg-slate-100 rounded-md transition-colors text-slate-400 font-bold text-xs flex items-center gap-1">
|
||||
<Globe class="w-3.5 h-3.5" /> ZH
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center gap-2 px-4 py-1.5 bg-emerald-50 text-emerald-600 rounded-md text-xs font-bold border border-emerald-100 hover:bg-emerald-100 transition-all"
|
||||
>
|
||||
<span class="text-emerald-500">$</span> 登录 <LogIn class="w-3.5 h-3.5 ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主内容区:VueFlow 画布 -->
|
||||
<div class="flex-grow relative">
|
||||
<VueFlow :default-edge-options="{ type: 'smoothstep' }" :fit-view-on-init="true" class="bg-white">
|
||||
<Background :variant="BackgroundVariant.Lines" pattern-color="#f2f2f2" :gap="24" :size="0.5" />
|
||||
<Controls />
|
||||
|
||||
<!-- 自定义节点插槽 -->
|
||||
<template #node-window="{ id, data }">
|
||||
<div class="window-node group">
|
||||
<!-- Window Header -->
|
||||
<div class="window-header">
|
||||
<div class="flex gap-1.5">
|
||||
<div class="w-2 h-2 rounded-full bg-red-400/80"></div>
|
||||
<div class="w-2 h-2 rounded-full bg-amber-400/80"></div>
|
||||
<div class="w-2 h-2 rounded-full bg-emerald-400/80"></div>
|
||||
</div>
|
||||
<span class="window-title">
|
||||
{{ data.type === 'root' ? 'main.ts' : 'module.tsx' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Window Content -->
|
||||
<div class="window-content">
|
||||
<!-- Image Display -->
|
||||
<div
|
||||
v-if="data.imageUrl || data.isImageLoading"
|
||||
class="mb-4 rounded-lg overflow-hidden bg-slate-50 border border-slate-100 aspect-video flex items-center justify-center relative group/img"
|
||||
>
|
||||
<img v-if="data.imageUrl" :src="data.imageUrl" class="w-full h-full object-cover" />
|
||||
<div v-if="data.isImageLoading" class="absolute inset-0 flex flex-col items-center justify-center bg-white/80 backdrop-blur-sm">
|
||||
<RefreshCw class="w-6 h-6 text-orange-500 animate-spin mb-2" />
|
||||
<span class="text-[8px] font-bold text-slate-400 uppercase">Generating...</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="data.imageUrl"
|
||||
class="absolute inset-0 bg-black/40 opacity-0 group-hover/img:opacity-100 transition-opacity flex items-center justify-center"
|
||||
>
|
||||
<button @click="generateNodeImage(id, data.label)" class="p-2 bg-white/20 hover:bg-white/40 rounded-full backdrop-blur-md transition-all">
|
||||
<RefreshCw class="w-4 h-4 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-orange-500 font-bold">></span>
|
||||
<h3 class="font-black text-slate-900 tracking-tight truncate">{{ data.label }}</h3>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-500 leading-relaxed font-medium line-clamp-3">
|
||||
{{ data.description }}
|
||||
</p>
|
||||
|
||||
<!-- Node Actions -->
|
||||
<div class="pt-3 mt-3 border-t border-slate-50 flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-1.5 shrink-0">
|
||||
<div class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></div>
|
||||
<span class="text-[9px] font-bold text-slate-400 uppercase tracking-tighter">active</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 overflow-hidden">
|
||||
<button
|
||||
v-if="!data.imageUrl && !data.isImageLoading"
|
||||
@click.stop="generateNodeImage(id, data.label)"
|
||||
class="action-btn text-blue-500 hover:bg-blue-50"
|
||||
>
|
||||
<ImageIcon class="w-2.5 h-2.5" />
|
||||
<span>IMG</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click.stop="expandIdea({ id, data, position: flowNodes.find(n => n.id === id)?.position })"
|
||||
class="action-btn text-orange-500 hover:bg-orange-50"
|
||||
>
|
||||
<ArrowRight class="w-2.5 h-2.5" />
|
||||
<span>EXPAND</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VueFlow>
|
||||
|
||||
<!-- 浮动 UI 层 -->
|
||||
<div class="absolute inset-0 pointer-events-none z-10 p-12"></div>
|
||||
|
||||
<!-- 加载状态遮罩 -->
|
||||
<div v-if="isLoading" class="absolute inset-0 z-[100] flex items-center justify-center bg-white/5 backdrop-blur-[1px] pointer-events-none">
|
||||
<div class="bg-white/90 p-4 rounded-full shadow-2xl border border-orange-100 animate-bounce">
|
||||
<Zap class="w-8 h-8 text-orange-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部全局操作栏 -->
|
||||
<div class="fixed bottom-10 left-1/2 -translate-x-1/2 z-50 flex items-center gap-4">
|
||||
<!-- 核心输入框 -->
|
||||
<div
|
||||
class="bg-slate-900 rounded-2xl p-2 pl-6 flex items-center gap-4 shadow-2xl shadow-slate-300 border border-slate-800 w-[600px] group transition-all focus-within:ring-2 focus-within:ring-orange-500/20"
|
||||
>
|
||||
<span class="text-orange-500 font-black text-xl select-none">></span>
|
||||
<input
|
||||
v-model="ideaInput"
|
||||
@keyup.enter="expandIdea"
|
||||
placeholder="Input your idea to expand..."
|
||||
class="bg-transparent border-none outline-none text-white placeholder:text-slate-500 flex-grow font-bold tracking-tight"
|
||||
/>
|
||||
<button
|
||||
@click="expandIdea"
|
||||
:disabled="isLoading"
|
||||
class="p-3 bg-orange-500 hover:bg-orange-600 text-white rounded-xl transition-all active:scale-90 disabled:opacity-50"
|
||||
>
|
||||
<Zap v-if="!isLoading" class="w-5 h-5" />
|
||||
<RefreshCw v-else class="w-5 h-5 animate-spin" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 清空按钮 -->
|
||||
<button
|
||||
@click="startNewSession"
|
||||
class="bg-white text-slate-900 p-4 rounded-2xl shadow-2xl border border-slate-200 hover:bg-slate-50 transition-all active:scale-95 group"
|
||||
title="Clear Canvas"
|
||||
>
|
||||
<Plus class="w-6 h-6 group-hover:rotate-90 transition-transform text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&display=swap');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
@apply flex items-center gap-1 px-3 py-1 bg-white border border-slate-200 rounded-md text-xs font-medium text-slate-600 hover:border-slate-300 hover:shadow-sm transition-all;
|
||||
}
|
||||
|
||||
/* VueFlow Custom Node Styles */
|
||||
.window-node {
|
||||
@apply w-[280px] bg-white rounded-xl border border-slate-200 shadow-xl overflow-hidden transition-all duration-300;
|
||||
}
|
||||
|
||||
.window-node:hover {
|
||||
@apply shadow-2xl shadow-orange-100 -translate-y-1 border-orange-200;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
@apply bg-slate-50/80 px-3 py-1.5 border-b border-slate-100 flex items-center justify-between;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
@apply text-[9px] font-bold text-slate-300 tracking-widest uppercase;
|
||||
}
|
||||
|
||||
.window-content {
|
||||
@apply p-4;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@apply flex items-center gap-1 px-2 py-1 rounded-md text-[9px] font-bold transition-all active:scale-95 uppercase tracking-tighter whitespace-nowrap border border-transparent;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
@apply border-current bg-opacity-10;
|
||||
}
|
||||
|
||||
/* VueFlow Overrides */
|
||||
.vue-flow__node-window {
|
||||
@apply p-0 border-none bg-transparent !important;
|
||||
}
|
||||
|
||||
.vue-flow__edge-path {
|
||||
@apply stroke-orange-200 !important;
|
||||
}
|
||||
|
||||
.vue-flow__controls {
|
||||
@apply !bg-white !border-slate-200 !shadow-xl !rounded-lg !left-6 !bottom-6;
|
||||
}
|
||||
|
||||
.vue-flow__controls-button {
|
||||
@apply !border-slate-100 !fill-slate-400 hover:!bg-slate-50 !transition-colors;
|
||||
}
|
||||
|
||||
.vue-flow__background {
|
||||
@apply !bg-white;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
@keyframes spin-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
@apply opacity-30;
|
||||
}
|
||||
|
||||
input:focus::placeholder {
|
||||
@apply opacity-10;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
5
src/main.ts
Normal file
5
src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
31
src/style.css
Normal file
31
src/style.css
Normal file
@@ -0,0 +1,31 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: Inter, 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;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
9
src/vite-env.d.ts
vendored
Normal file
9
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_ZHIPU_AI_API_KEY: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
15
tailwind.config.js
Normal file
15
tailwind.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'ny-red': '#C41E3A', // New Year Red
|
||||
'ny-gold': '#FFD700', // New Year Gold
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
13
vite.config.ts
Normal file
13
vite.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user