feat: initial commit of AI Comic Generator
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<el-config-provider :locale="locale">
|
||||
<el-container>
|
||||
<el-header class="main-header">
|
||||
<div class="logo">AI Comic Generator</div>
|
||||
<el-menu mode="horizontal" router :default-active="$route.path" class="nav-menu">
|
||||
<el-menu-item index="/">Projects</el-menu-item>
|
||||
<el-menu-item index="/config">Models</el-menu-item>
|
||||
</el-menu>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
|
||||
const locale = ref(zhCn)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #333;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
}
|
||||
.nav-menu {
|
||||
border-bottom: none;
|
||||
background-color: transparent;
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './style.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import ConfigView from '../views/ConfigView.vue'
|
||||
import ProjectView from '../views/ProjectView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/config',
|
||||
name: 'config',
|
||||
component: ConfigView
|
||||
},
|
||||
{
|
||||
path: '/project/:id',
|
||||
name: 'project',
|
||||
component: ProjectView
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,33 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Scrollbar styling for tech look */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="config-view">
|
||||
<div class="header">
|
||||
<h2>Model Configurations</h2>
|
||||
<el-button type="primary" @click="openDialog()">Add Config</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="configs" style="width: 100%">
|
||||
<el-table-column prop="provider" label="Provider" />
|
||||
<el-table-column prop="model_name" label="Model Name" />
|
||||
<el-table-column prop="model_type" label="Type" />
|
||||
<el-table-column prop="is_active" label="Active">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.is_active ? 'success' : 'info'">{{ scope.row.is_active ? 'Yes' : 'No' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Actions">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openDialog(scope.row)">Edit</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteConfig(scope.row.id)">Delete</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? 'Edit Config' : 'Add Config'">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="Provider">
|
||||
<el-select v-model="form.provider" placeholder="Select provider">
|
||||
<el-option label="Google" value="google" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Model Name">
|
||||
<el-input v-model="form.model_name" placeholder="e.g. gemini-1.5-flash" />
|
||||
</el-form-item>
|
||||
<el-form-item label="API Key">
|
||||
<el-input v-model="form.api_key" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="Type">
|
||||
<el-select v-model="form.model_type">
|
||||
<el-option label="Text" value="text" />
|
||||
<el-option label="Image" value="image" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Base URL">
|
||||
<el-input v-model="form.base_url" placeholder="Optional" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Active">
|
||||
<el-switch v-model="form.is_active" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="saveConfig">Save</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const configs = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const form = ref({
|
||||
provider: 'google',
|
||||
model_name: '',
|
||||
api_key: '',
|
||||
model_type: 'text',
|
||||
base_url: '',
|
||||
is_active: true
|
||||
})
|
||||
|
||||
const fetchConfigs = async () => {
|
||||
try {
|
||||
const res = await axios.get('/api/v1/configs/')
|
||||
configs.value = res.data
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to fetch configs')
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (row = null) => {
|
||||
if (row) {
|
||||
isEdit.value = true
|
||||
form.value = { ...row }
|
||||
} else {
|
||||
isEdit.value = false
|
||||
form.value = {
|
||||
provider: 'google',
|
||||
model_name: '',
|
||||
api_key: '',
|
||||
model_type: 'text',
|
||||
base_url: '',
|
||||
is_active: true
|
||||
}
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await axios.put(`/api/v1/configs/${form.value.id}`, form.value)
|
||||
} else {
|
||||
await axios.post('/api/v1/configs/', form.value)
|
||||
}
|
||||
ElMessage.success('Saved successfully')
|
||||
dialogVisible.value = false
|
||||
fetchConfigs()
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to save config')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteConfig = async (id) => {
|
||||
try {
|
||||
await axios.delete(`/api/v1/configs/${id}`)
|
||||
ElMessage.success('Deleted successfully')
|
||||
fetchConfigs()
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to delete config')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchConfigs)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-view {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="home-view">
|
||||
<div class="header">
|
||||
<h2>My Projects</h2>
|
||||
<el-button type="primary" @click="dialogVisible = true">New Project</el-button>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6" v-for="project in projects" :key="project.id">
|
||||
<el-card shadow="hover" class="project-card" @click="goToProject(project.id)">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ project.title }}</span>
|
||||
<el-button type="text" icon="Delete" @click.stop="deleteProject(project.id)"></el-button>
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ project.description || 'No description' }}</p>
|
||||
<div class="footer">
|
||||
<span>{{ new Date(project.updated_at).toLocaleDateString() }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="Create New Project">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="Title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Description">
|
||||
<el-input type="textarea" v-model="form.description" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="createProject">Create</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const projects = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const form = ref({ title: '', description: '' })
|
||||
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
const res = await axios.get('/api/v1/projects/')
|
||||
projects.value = res.data
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to fetch projects')
|
||||
}
|
||||
}
|
||||
|
||||
const createProject = async () => {
|
||||
try {
|
||||
const res = await axios.post('/api/v1/projects/', form.value)
|
||||
ElMessage.success('Created successfully')
|
||||
dialogVisible.value = false
|
||||
goToProject(res.data.id)
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to create project')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteProject = async (id) => {
|
||||
try {
|
||||
await axios.delete(`/api/v1/projects/${id}`)
|
||||
ElMessage.success('Deleted successfully')
|
||||
fetchProjects()
|
||||
} catch (error) {
|
||||
ElMessage.error('Failed to delete project')
|
||||
}
|
||||
}
|
||||
|
||||
const goToProject = (id) => {
|
||||
router.push(`/project/${id}`)
|
||||
}
|
||||
|
||||
onMounted(fetchProjects)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-view {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.project-card {
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user