MicroFish/frontend/src/views/Home.vue

891 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="home-container">
<!-- Top Navigation Bar -->
<nav class="navbar">
<div class="nav-brand">MIROFISH</div>
<div class="nav-links">
<a href="https://github.com/666ghj/MiroFish" target="_blank" class="github-link">
Visit our GitHub page <span class="arrow"></span>
</a>
</div>
</nav>
<div class="main-content">
<!-- Upper Section: Hero Area -->
<section class="hero-section">
<div class="hero-left">
<div class="tag-row">
<span class="orange-tag">A concise and general-purpose collective intelligence engine</span>
<span class="version-text">/ v0.1 - Preview Version</span>
</div>
<h1 class="main-title">
Upload any report<br>
<span class="gradient-text">Instantly simulate the future</span>
</h1>
<div class="hero-desc">
<p>
Even with just a piece of text, <span class="highlight-bold">MiroFish</span> can extract real-world signals and automatically generate a parallel world with up to <span class="highlight-orange">millions of agents</span>. By injecting variables from a god-like perspective, it explores <span class="highlight-code">"locally optimal solutions"</span> within complex multi-agent interactions in dynamic environments.
</p>
<p class="slogan-text">
Let the future be rehearsed within agent swarms, and let decisions emerge after countless simulations<span class="blinking-cursor">_</span>
</p>
</div>
<div class="decoration-square"></div>
</div>
<div class="hero-right">
<!-- Logo Area -->
<div class="logo-container">
<img src="../assets/logo/MiroFish_logo_left.jpeg" alt="MiroFish Logo" class="hero-logo" />
</div>
<button class="scroll-down-btn" @click="scrollToBottom">
</button>
</div>
</section>
<!-- Lower Section: Two-column Layout -->
<section class="dashboard-section">
<!-- Left Column: Status and Steps -->
<div class="left-panel">
<div class="panel-header">
<span class="status-dot"></span> System Status
</div>
<h2 class="section-title">Ready</h2>
<p class="section-desc">
The prediction engine is on standby. Multiple unstructured data files can be uploaded to initialize the simulation sequence.
</p>
<!-- Data Metric Cards -->
<div class="metrics-row">
<div class="metric-card">
<div class="metric-value">Low Cost</div>
<div class="metric-label">Average cost: $5 per simulation</div>
</div>
<div class="metric-card">
<div class="metric-value">High Availability</div>
<div class="metric-label">Supports simulations with up to millions of agents</div>
</div>
</div>
<!-- Project Simulation Workflow (Added Section) -->
<div class="steps-container">
<div class="steps-header">
<span class="diamond-icon"></span> Workflow Sequence
</div>
<div class="workflow-list">
<div class="workflow-item">
<span class="step-num">01</span>
<div class="step-info">
<div class="step-title">Graph Construction</div>
<div class="step-desc">Real-world seed extraction & individual and collective memory injection & GraphRAG construction</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">02</span>
<div class="step-info">
<div class="step-title">Environment Setup</div>
<div class="step-desc">Entity relationship extraction & persona generation & environment configuration with agent-injected simulation parameters</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">03</span>
<div class="step-info">
<div class="step-title">Start Simulation</div>
<div class="step-desc">Dual-platform parallel simulation & automatic parsing of prediction requirements & dynamic temporal memory updates</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">04</span>
<div class="step-info">
<div class="step-title">Report Generation</div>
<div class="step-desc">ReportAgent uses a rich toolset to deeply interact with the post-simulation environment</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">05</span>
<div class="step-info">
<div class="step-title">Deep Interaction</div>
<div class="step-desc">Converse with any entity in the simulated world & interact with the ReportAgent</div>
</div>
</div>
</div>
</div>
</div>
<!-- Right Column: Interactive Console -->
<div class="right-panel">
<div class="console-box">
<!-- Upload Area -->
<div class="console-section">
<div class="console-header">
<span class="console-label">01 / Real-world Seeds</span>
<span class="console-meta">Supported formats: PDF, MD, TXT</span>
</div>
<div
class="upload-zone"
:class="{ 'drag-over': isDragOver, 'has-files': files.length > 0 }"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
@click="triggerFileInput"
>
<input
ref="fileInput"
type="file"
multiple
accept=".pdf,.md,.txt"
@change="handleFileSelect"
style="display: none"
:disabled="loading"
/>
<div v-if="files.length === 0" class="upload-placeholder">
<div class="upload-icon">↑</div>
<div class="upload-title">Drag files to upload</div>
<div class="upload-hint">Or click to browse the file system</div>
</div>
<div v-else class="file-list">
<div v-for="(file, index) in files" :key="index" class="file-item">
<span class="file-icon">📄</span>
<span class="file-name">{{ file.name }}</span>
<button @click.stop="removeFile(index)" class="remove-btn">×</button>
</div>
</div>
</div>
</div>
<!-- Divider -->
<div class="console-divider">
<span>Input Parameters</span>
</div>
<!-- Input Area -->
<div class="console-section">
<div class="console-header">
<span class="console-label">>_ 02 / Simulation Prompt</span>
</div>
<div class="input-wrapper">
<textarea
v-model="formData.simulationRequirement"
class="code-input"
placeholder="Describe your simulation or prediction request in natural language (e.g. If Wuhan University announces the revocation of disciplinary action against someone, what public opinion trends would emerge?)"
rows="6"
:disabled="loading"
></textarea>
<div class="model-badge">Engine: MiroFish-V1.0</div>
</div>
</div>
<!-- Start Button -->
<div class="console-section btn-section">
<button
class="start-engine-btn"
@click="startSimulation"
:disabled="!canSubmit || loading"
>
<span v-if="!loading">Start Engine</span>
<span v-else>Initializing...</span>
<span class="btn-arrow"></span>
</button>
</div>
</div>
</div>
</section>
<!-- Historical Project Database -->
<HistoryDatabase />
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import HistoryDatabase from '../components/HistoryDatabase.vue'
const router = useRouter()
// Form data
const formData = ref({
simulationRequirement: ''
})
// File list
const files = ref([])
// State
const loading = ref(false)
const error = ref('')
const isDragOver = ref(false)
// File input reference
const fileInput = ref(null)
// Computed property: whether submission is allowed
const canSubmit = computed(() => {
return formData.value.simulationRequirement.trim() !== '' && files.value.length > 0
})
// Trigger file selection
const triggerFileInput = () => {
if (!loading.value) {
fileInput.value?.click()
}
}
// Handle file selection
const handleFileSelect = (event) => {
const selectedFiles = Array.from(event.target.files)
addFiles(selectedFiles)
}
// Handle drag-related events
const handleDragOver = (e) => {
if (!loading.value) {
isDragOver.value = true
}
}
const handleDragLeave = (e) => {
isDragOver.value = false
}
const handleDrop = (e) => {
isDragOver.value = false
if (loading.value) return
const droppedFiles = Array.from(e.dataTransfer.files)
addFiles(droppedFiles)
}
// Add files
const addFiles = (newFiles) => {
const validFiles = newFiles.filter(file => {
const ext = file.name.split('.').pop().toLowerCase()
return ['pdf', 'md', 'txt'].includes(ext)
})
files.value.push(...validFiles)
}
// Remove file
const removeFile = (index) => {
files.value.splice(index, 1)
}
// Scroll to bottom
const scrollToBottom = () => {
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
})
}
// Start simulation - immediate redirection, API call handled on Process page
const startSimulation = () => {
if (!canSubmit.value || loading.value) return
// Store pending upload data
import('../store/pendingUpload.js').then(({ setPendingUpload }) => {
setPendingUpload(files.value, formData.value.simulationRequirement)
// Immediately redirect to Process page (using special marker for new project)
router.push({
name: 'Process',
params: { projectId: 'new' }
})
})
}
</script>
<style scoped>
/* Global variables and reset */
:root {
--black: #000000;
--white: #FFFFFF;
--orange: #FF4500;
--gray-light: #F5F5F5;
--gray-text: #666666;
--border: #E5E5E5;
/*
Use Space Grotesk as the primary heading font, and JetBrains Mono as the code/tag font.
Make sure these Google Fonts are imported in index.html.
*/
--font-mono: 'JetBrains Mono', monospace;
--font-sans: 'Space Grotesk', 'Noto Sans SC', system-ui, sans-serif;
--font-cn: 'Noto Sans SC', system-ui, sans-serif;
}
.home-container {
min-height: 100vh;
background: var(--white);
font-family: var(--font-sans);
color: var(--black);
}
/* Top navigation */
.navbar {
height: 60px;
background: var(--black);
color: var(--white);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40px;
}
.nav-brand {
font-family: var(--font-mono);
font-weight: 800;
letter-spacing: 1px;
font-size: 1.2rem;
}
.nav-links {
display: flex;
align-items: center;
}
.github-link {
color: var(--white);
text-decoration: none;
font-family: var(--font-mono);
font-size: 0.9rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
transition: opacity 0.2s;
}
.github-link:hover {
opacity: 0.8;
}
.arrow {
font-family: sans-serif;
}
/* Main content area */
.main-content {
max-width: 1400px;
margin: 0 auto;
padding: 60px 40px;
}
/* Hero area */
.hero-section {
display: flex;
justify-content: space-between;
margin-bottom: 80px;
position: relative;
}
.hero-left {
flex: 1;
padding-right: 60px;
}
.tag-row {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 25px;
font-family: var(--font-mono);
font-size: 0.8rem;
}
.orange-tag {
background: var(--orange);
color: var(--white);
padding: 4px 10px;
font-weight: 700;
letter-spacing: 1px;
font-size: 0.75rem;
}
.version-text {
color: #999;
font-weight: 500;
letter-spacing: 0.5px;
}
.main-title {
font-size: 4.5rem;
line-height: 1.2;
font-weight: 500;
margin: 0 0 40px 0;
letter-spacing: -2px;
color: var(--black);
}
.gradient-text {
background: linear-gradient(90deg, #000000 0%, #444444 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: inline-block;
}
.hero-desc {
font-size: 1.05rem;
line-height: 1.8;
color: var(--gray-text);
max-width: 640px;
margin-bottom: 50px;
font-weight: 400;
text-align: justify;
}
.hero-desc p {
margin-bottom: 1.5rem;
}
.highlight-bold {
color: var(--black);
font-weight: 700;
}
.highlight-orange {
color: var(--orange);
font-weight: 700;
font-family: var(--font-mono);
}
.highlight-code {
background: rgba(0, 0, 0, 0.05);
padding: 2px 6px;
border-radius: 2px;
font-family: var(--font-mono);
font-size: 0.9em;
color: var(--black);
font-weight: 600;
}
.slogan-text {
font-size: 1.2rem;
font-weight: 520;
color: var(--black);
letter-spacing: 1px;
border-left: 3px solid var(--orange);
padding-left: 15px;
margin-top: 20px;
}
.blinking-cursor {
color: var(--orange);
animation: blink 1s step-end infinite;
font-weight: 700;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.decoration-square {
width: 16px;
height: 16px;
background: var(--orange);
}
.hero-right {
flex: 0.8;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}
.logo-container {
width: 100%;
display: flex;
justify-content: flex-end;
padding-right: 40px;
}
.hero-logo {
max-width: 500px; /* Adjust logo size */
width: 100%;
}
.scroll-down-btn {
width: 40px;
height: 40px;
border: 1px solid var(--border);
background: transparent;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--orange);
font-size: 1.2rem;
transition: all 0.2s;
}
.scroll-down-btn:hover {
border-color: var(--orange);
}
/* Dashboard two-column layout */
.dashboard-section {
display: flex;
gap: 60px;
border-top: 1px solid var(--border);
padding-top: 60px;
align-items: flex-start;
}
.dashboard-section .left-panel,
.dashboard-section .right-panel {
display: flex;
flex-direction: column;
}
/* Left panel */
.left-panel {
flex: 0.8;
}
.panel-header {
font-family: var(--font-mono);
font-size: 0.8rem;
color: #999;
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.status-dot {
color: var(--orange);
font-size: 0.8rem;
}
.section-title {
font-size: 2rem;
font-weight: 520;
margin: 0 0 15px 0;
}
.section-desc {
color: var(--gray-text);
margin-bottom: 25px;
line-height: 1.6;
}
.metrics-row {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.metric-card {
border: 1px solid var(--border);
padding: 20px 30px;
min-width: 150px;
}
.metric-value {
font-family: var(--font-mono);
font-size: 1.8rem;
font-weight: 520;
margin-bottom: 5px;
}
.metric-label {
font-size: 0.85rem;
color: #999;
}
/* Project simulation steps introduction */
.steps-container {
border: 1px solid var(--border);
padding: 30px;
position: relative;
}
.steps-header {
font-family: var(--font-mono);
font-size: 0.8rem;
color: #999;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 8px;
}
.diamond-icon {
font-size: 1.2rem;
line-height: 1;
}
.workflow-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.workflow-item {
display: flex;
align-items: flex-start;
gap: 20px;
}
.step-num {
font-family: var(--font-mono);
font-weight: 700;
color: var(--black);
opacity: 0.3;
}
.step-info {
flex: 1;
}
.step-title {
font-weight: 520;
font-size: 1rem;
margin-bottom: 4px;
}
.step-desc {
font-size: 0.85rem;
color: var(--gray-text);
}
/* Right interactive console */
.right-panel {
flex: 1.2;
}
.console-box {
border: 1px solid #CCC; /* Outer solid border */
padding: 8px; /* Padding creates a double-border effect */
}
.console-section {
padding: 20px;
}
.console-section.btn-section {
padding-top: 0;
}
.console-header {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
font-family: var(--font-mono);
font-size: 0.75rem;
color: #666;
}
.upload-zone {
border: 1px dashed #CCC;
height: 200px;
overflow-y: auto;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
background: #FAFAFA;
}
.upload-zone.has-files {
align-items: flex-start;
}
.upload-zone:hover {
background: #F0F0F0;
border-color: #999;
}
.upload-placeholder {
text-align: center;
}
.upload-icon {
width: 40px;
height: 40px;
border: 1px solid #DDD;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 15px;
color: #999;
}
.upload-title {
font-weight: 500;
font-size: 0.9rem;
margin-bottom: 5px;
}
.upload-hint {
font-family: var(--font-mono);
font-size: 0.75rem;
color: #999;
}
.file-list {
width: 100%;
padding: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.file-item {
display: flex;
align-items: center;
background: var(--white);
padding: 8px 12px;
border: 1px solid #EEE;
font-family: var(--font-mono);
font-size: 0.85rem;
}
.file-name {
flex: 1;
margin: 0 10px;
}
.remove-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
color: #999;
}
.console-divider {
display: flex;
align-items: center;
margin: 10px 0;
}
.console-divider::before,
.console-divider::after {
content: '';
flex: 1;
height: 1px;
background: #EEE;
}
.console-divider span {
padding: 0 15px;
font-family: var(--font-mono);
font-size: 0.7rem;
color: #BBB;
letter-spacing: 1px;
}
.input-wrapper {
position: relative;
border: 1px solid #DDD;
background: #FAFAFA;
}
.code-input {
width: 100%;
border: none;
background: transparent;
padding: 20px;
font-family: var(--font-mono);
font-size: 0.9rem;
line-height: 1.6;
resize: vertical;
outline: none;
min-height: 150px;
}
.model-badge {
position: absolute;
bottom: 10px;
right: 15px;
font-family: var(--font-mono);
font-size: 0.7rem;
color: #AAA;
}
.start-engine-btn {
width: 100%;
background: var(--black);
color: var(--white);
border: none;
padding: 20px;
font-family: var(--font-mono);
font-weight: 700;
font-size: 1.1rem;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.3s ease;
letter-spacing: 1px;
position: relative;
overflow: hidden;
}
/* Clickable (not disabled) */
.start-engine-btn:not(:disabled) {
background: var(--black);
border: 1px solid var(--black);
animation: pulse-border 2s infinite;
}
.start-engine-btn:hover:not(:disabled) {
background: var(--orange);
border-color: var(--orange);
transform: translateY(-2px);
}
.start-engine-btn:active:not(:disabled) {
transform: translateY(0);
}
.start-engine-btn:disabled {
background: #E5E5E5;
color: #999;
cursor: not-allowed;
transform: none;
border: 1px solid #E5E5E5;
}
/* Guided animation: subtle border pulses */
@keyframes pulse-border {
0% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2); }
70% { box-shadow: 0 0 0 6px rgba(0, 0, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
}
/* Responsive adaptation */
@media (max-width: 1024px) {
.dashboard-section {
flex-direction: column;
}
.hero-section {
flex-direction: column;
}
.hero-left {
padding-right: 0;
margin-bottom: 40px;
}
.hero-logo {
max-width: 200px;
margin-bottom: 20px;
}
}
</style>