Compare commits
2 Commits
209a2403ac
...
9336098cd9
| Author | SHA1 | Date | |
|---|---|---|---|
| 9336098cd9 | |||
| 674c45324b |
@ -19,11 +19,12 @@
|
|||||||
},
|
},
|
||||||
"plugins": ["vue"],
|
"plugins": ["vue"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"vue/attributes-order": 0,
|
||||||
"arrow-parens": 0,
|
"arrow-parens": 0,
|
||||||
"generator-star-spacing": 0,
|
"generator-star-spacing": 0,
|
||||||
"no-case-declarations": 0,
|
"no-case-declarations": 0,
|
||||||
"array-callback-return": 0,
|
"array-callback-return": 0,
|
||||||
"no-trailing-spaces": 1,
|
"no-trailing-spaces": 0,
|
||||||
"no-control-regex": 0,
|
"no-control-regex": 0,
|
||||||
"no-useless-constructor": 0,
|
"no-useless-constructor": 0,
|
||||||
"node/no-deprecated-api": 0
|
"node/no-deprecated-api": 0
|
||||||
|
|||||||
@ -101,6 +101,7 @@ baseConfig.files = [
|
|||||||
(Required) The files and folders listed below should not be included in the build.
|
(Required) The files and folders listed below should not be included in the build.
|
||||||
*/
|
*/
|
||||||
'dist/**/*',
|
'dist/**/*',
|
||||||
|
'!node_modules/**/*',
|
||||||
'!dist/main/index.dev.js',
|
'!dist/main/index.dev.js',
|
||||||
'!docs/**/*',
|
'!docs/**/*',
|
||||||
'!tests/**/*',
|
'!tests/**/*',
|
||||||
|
|||||||
3757
lingtropy-client/package-lock.json
generated
3757
lingtropy-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -43,13 +43,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
"lingtropy": "file:",
|
"axios": "^1.8.3",
|
||||||
|
"electron-store": "^10.0.1",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.1",
|
"vue-i18n": "^11.1.1",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vuetify": "^3.7.14",
|
"vuetify": "^3.7.14"
|
||||||
"vutron": "file:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
|
|||||||
@ -1,9 +1,25 @@
|
|||||||
import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
|
import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
|
||||||
import Constants from './utils/Constants'
|
import Constants from './utils/Constants'
|
||||||
|
import Store from 'electron-store'
|
||||||
|
import axios from 'axios'
|
||||||
|
import OpenAI from 'openai'
|
||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent'
|
||||||
|
const store = new Store()
|
||||||
/*
|
/*
|
||||||
* IPC Communications
|
* IPC Communications
|
||||||
* */
|
* */
|
||||||
|
|
||||||
|
// const getBaseUrl = async () => {
|
||||||
|
// try {
|
||||||
|
// const response = await axios.get('https://aqq-jbjsjuxivc.cn-hangzhou.fcapp.run');
|
||||||
|
// console.log('获取baseUrl成功:', response.data);
|
||||||
|
// return response.data.data;
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('获取baseUrl失败:', error);
|
||||||
|
// throw error;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// const baseUrl = ""
|
||||||
export default class IPCs {
|
export default class IPCs {
|
||||||
static initialize(): void {
|
static initialize(): void {
|
||||||
// Get application version
|
// Get application version
|
||||||
@ -11,6 +27,104 @@ export default class IPCs {
|
|||||||
return Constants.APP_VERSION
|
return Constants.APP_VERSION
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('store-get', (event, key) => {
|
||||||
|
return store.get(key) || null
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('store-set', (event, key, value) => {
|
||||||
|
store.set(key, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('fetch-models', async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('https://get-model-list-vcwjgnvcld.cn-hangzhou.fcapp.run') // 从网络获取数据
|
||||||
|
console.log('获取模型数据成功:', response.data)
|
||||||
|
return response.data.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取模型数据失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('getBaseUrl', async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('https://aqq-jbjsjuxivc.cn-hangzhou.fcapp.run')
|
||||||
|
console.log('获取baseUrl成功:', response.data)
|
||||||
|
return response.data.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取baseUrl失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// const requestParams = {
|
||||||
|
// input: '这是一个测试输入', // 输入文本
|
||||||
|
// model: 'gpt-4o', // 设置模型
|
||||||
|
// stream: false, // 是否使用流传输
|
||||||
|
// temperature: 0.8, // 设置温度
|
||||||
|
// // 其他可选参数
|
||||||
|
// max_output_tokens: 100, // 最大输出令牌数
|
||||||
|
// top_p: 0.9, // 核采样参数
|
||||||
|
// };
|
||||||
|
|
||||||
|
ipcMain.handle('call-openai', async (event, baseURL, apiKey, model, count, rawArticle) => {
|
||||||
|
try {
|
||||||
|
const client = new OpenAI({
|
||||||
|
apiKey: apiKey, // 从环境变量中获取API密钥
|
||||||
|
baseURL: baseURL // 设置代理地址
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建 count 个请求
|
||||||
|
const requests = Array.from({ length: count }, (_, index) => {
|
||||||
|
return client.chat.completions
|
||||||
|
.create({
|
||||||
|
model: model,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content:
|
||||||
|
'你是一个小红书文案写手,能够熟练地根据用户的输入,改写成内容相近,但表达方式不同的新文案。你的文案中需要具备吸人眼球的钩子,能够牢牢抓住用户的注意力。请直接输出新的文案,不要输出其他任何提示性词语, 以纯文本的形式输出'
|
||||||
|
},
|
||||||
|
{ role: 'user', content: rawArticle }
|
||||||
|
],
|
||||||
|
stream: true // 启用流式输出
|
||||||
|
})
|
||||||
|
.then(async (stream) => {
|
||||||
|
let fullResponse = ''
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
const content = chunk.choices[0].delta.content || ''
|
||||||
|
fullResponse += content
|
||||||
|
// 实时发送每个请求的部分结果
|
||||||
|
event.sender.send('openai-partial-response', { index, content })
|
||||||
|
}
|
||||||
|
// 返回最终结果
|
||||||
|
return { index, response: fullResponse }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 并发执行所有请求
|
||||||
|
const results = await Promise.all(requests)
|
||||||
|
|
||||||
|
// 返回所有请求的最终结果
|
||||||
|
return results
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error)
|
||||||
|
throw error // 将错误传递给调用者
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('get-api-key', () => {
|
||||||
|
return store.get('api-key') || null
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('set-api-key', (_, apiKey) => {
|
||||||
|
if (!apiKey) {
|
||||||
|
store.delete('api-key')
|
||||||
|
} else {
|
||||||
|
store.set('api-key', apiKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Open url via web browser
|
// Open url via web browser
|
||||||
ipcMain.on('msgOpenExternalLink', async (event: IpcMainEvent, url: string) => {
|
ipcMain.on('msgOpenExternalLink', async (event: IpcMainEvent, url: string) => {
|
||||||
await shell.openExternal(url)
|
await shell.openExternal(url)
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import {
|
|||||||
BrowserWindowConstructorOptions
|
BrowserWindowConstructorOptions
|
||||||
} from 'electron'
|
} from 'electron'
|
||||||
import Constants, { TrayOptions } from './utils/Constants'
|
import Constants, { TrayOptions } from './utils/Constants'
|
||||||
import IPCs from './IPCs'
|
import IPCs from './IPCs.js'
|
||||||
import { createTray, hideWindow, showWindow } from './tray.ts'
|
import { createTray, hideWindow, showWindow } from './tray.ts'
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
width: Constants.IS_DEV_ENV ? 1500 : 1200,
|
width: Constants.IS_DEV_ENV ? 1500 : 1250,
|
||||||
height: 650,
|
height: 850,
|
||||||
tray: {
|
tray: {
|
||||||
// all optional values from DEFAULT_TRAY_OPTIONS can de defined here
|
// all optional values from DEFAULT_TRAY_OPTIONS can de defined here
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@ -1,8 +1,20 @@
|
|||||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'
|
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'
|
||||||
|
|
||||||
// Whitelist of valid channels used for IPC communication (Send message from Renderer to Main)
|
// Whitelist of valid channels used for IPC communication (Send message from Renderer to Main)
|
||||||
const mainAvailChannels: string[] = ['msgRequestGetVersion', 'msgOpenExternalLink', 'msgOpenFile']
|
const mainAvailChannels: string[] = [
|
||||||
const rendererAvailChannels: string[] = []
|
'msgRequestGetVersion',
|
||||||
|
'msgOpenExternalLink',
|
||||||
|
'msgOpenFile',
|
||||||
|
'store-set',
|
||||||
|
'store-get',
|
||||||
|
'fetch-models',
|
||||||
|
'getBaseUrl',
|
||||||
|
'call-openai',
|
||||||
|
'openai-partial-response',
|
||||||
|
'get-api-key',
|
||||||
|
'set-api-key'
|
||||||
|
]
|
||||||
|
const rendererAvailChannels: string[] = ['openai-partial-response', 'get-api-key', 'set-api-key']
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('mainApi', {
|
contextBridge.exposeInMainWorld('mainApi', {
|
||||||
send: (channel: string, ...data: any[]): void => {
|
send: (channel: string, ...data: any[]): void => {
|
||||||
|
|||||||
492
lingtropy-client/src/renderer/components/HeaderComponent.vue
Normal file
492
lingtropy-client/src/renderer/components/HeaderComponent.vue
Normal file
@ -0,0 +1,492 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Token Setting Alert -->
|
||||||
|
<div v-if="showTokenAlert" class="token-alert">
|
||||||
|
<div class="alert-content">
|
||||||
|
<span>请设置用户令牌以继续使用</span>
|
||||||
|
<button @click="toggleSettings" class="set-token-btn">设置令牌</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="logo-container">
|
||||||
|
<div class="logo-icon">AI</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-content">
|
||||||
|
<h1 class="title">文案生成工具</h1>
|
||||||
|
<p class="subtitle">智能改写,提升文案质量</p>
|
||||||
|
</div>
|
||||||
|
<button @click="toggleSettings" class="settings-button">
|
||||||
|
<span class="settings-icon">⚙️</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设置弹窗 -->
|
||||||
|
<div v-if="showSettings" class="modal-overlay">
|
||||||
|
<div class="settings-modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>设置</h2>
|
||||||
|
<button @click="toggleSettings" class="close-button">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>用户令牌</h3>
|
||||||
|
<div class="setting-item token-input-container">
|
||||||
|
<div class="password-input-wrapper">
|
||||||
|
<input
|
||||||
|
v-model="inputToken"
|
||||||
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
placeholder="请输入您的用户令牌"
|
||||||
|
class="token-input"
|
||||||
|
/>
|
||||||
|
<button @click="togglePasswordVisibility" type="button" class="password-toggle-btn">
|
||||||
|
{{ showPassword ? '隐藏' : '显示' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="copyright"> © 2025 沈阳泠启网络科技有限公司版权所有 </div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="toggleSettings" class="cancel-button">取消</button>
|
||||||
|
<button @click="saveSettings" class="save-button">保存设置</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, defineProps, watch } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
tokenvalue: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 用户令牌
|
||||||
|
const inputToken = ref(props.tokenvalue)
|
||||||
|
const userToken = ref(props.tokenvalue)
|
||||||
|
const showTokenAlert = ref(false)
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
// 设置相关
|
||||||
|
const showSettings = ref(false)
|
||||||
|
const emit = defineEmits(['update:tokenvalue'])
|
||||||
|
watch(userToken, (newValue) => {
|
||||||
|
emit('update:tokenvalue', newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化时获取令牌
|
||||||
|
onMounted(async () => {
|
||||||
|
// 从本地存储获取令牌
|
||||||
|
const savedToken = await window.mainApi.invoke('get-api-key')
|
||||||
|
if (savedToken) {
|
||||||
|
userToken.value = savedToken
|
||||||
|
} else {
|
||||||
|
// 如果令牌为空,显示提醒
|
||||||
|
showTokenAlert.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置弹窗相关
|
||||||
|
const toggleSettings = () => {
|
||||||
|
showSettings.value = !showSettings.value
|
||||||
|
inputToken.value = userToken.value
|
||||||
|
showPassword.value = false // 重置密码可见状态
|
||||||
|
|
||||||
|
// 当弹窗打开时,禁止页面滚动
|
||||||
|
if (showSettings.value) {
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换密码可见性
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
showPassword.value = !showPassword.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSettings = () => {
|
||||||
|
// 保存令牌到本地存储
|
||||||
|
userToken.value = inputToken.value
|
||||||
|
window.mainApi.invoke('set-api-key', userToken.value)
|
||||||
|
|
||||||
|
// 如果用户设置了令牌,则隐藏提醒
|
||||||
|
if (userToken.value.trim()) {
|
||||||
|
showTokenAlert.value = false
|
||||||
|
} else {
|
||||||
|
showTokenAlert.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
showSettings.value = false
|
||||||
|
document.body.style.overflow = '' // 恢复页面滚动
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 令牌相关样式 */
|
||||||
|
.token-alert {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ffe5e5;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-token-btn {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-token-btn:hover {
|
||||||
|
background-color: #b71c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-input-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 密码输入框样式 */
|
||||||
|
.password-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
padding-right: 70px; /* 为按钮留出空间 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2ecc71;
|
||||||
|
box-shadow: 0 0 0 2px rgba(46, 204, 113, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-toggle-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-toggle-btn:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 公司版权信息 */
|
||||||
|
.copyright {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 以下保留原有样式 */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
/* 确保模态框不可点击穿透 */
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 95%;
|
||||||
|
max-width: 550px; /* 增加最大宽度 */
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
/* 确保模态框内容可以点击 */
|
||||||
|
pointer-events: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #333;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button,
|
||||||
|
.save-button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
background-color: #2ecc71;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button:hover {
|
||||||
|
background-color: #27ae60;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header 样式 */
|
||||||
|
.header-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: linear-gradient(135deg, #2ecc71, #27ae60);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置按钮 */
|
||||||
|
.settings-button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
transform: rotate(30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-section {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
margin-right: 0;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button,
|
||||||
|
.save-button {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.settings-modal {
|
||||||
|
width: 95%;
|
||||||
|
max-width: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header,
|
||||||
|
.modal-footer {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,342 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="input-section">
|
|
||||||
<div class="header-section">
|
|
||||||
<div class="logo-container">
|
|
||||||
<div class="logo-icon">AI</div>
|
|
||||||
</div>
|
|
||||||
<div class="header-content">
|
|
||||||
<h1 class="title">文案生成工具</h1>
|
|
||||||
<p class="subtitle">智能改写,提升文案质量</p>
|
|
||||||
</div>
|
|
||||||
<button @click="$emit('toggle-settings')" class="settings-button">
|
|
||||||
<span class="settings-icon">⚙️</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ModelSelector
|
|
||||||
:selectedModel="selectedModel"
|
|
||||||
:availableModels="availableModels"
|
|
||||||
@update:selectedModel="$emit('update:selectedModel', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RewriteMode
|
|
||||||
:mode="mode"
|
|
||||||
:generationCount="generationCount"
|
|
||||||
@update:mode="$emit('update:mode', $event)"
|
|
||||||
@update:generationCount="$emit('update:generationCount', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="original-text-section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span class="section-icon">📝</span>
|
|
||||||
原始文案
|
|
||||||
<span v-if="originalText" class="char-count">{{ originalText.length }} 字</span>
|
|
||||||
</h2>
|
|
||||||
<textarea
|
|
||||||
:value="originalText"
|
|
||||||
@input="$emit('update:originalText', $event.target.value)"
|
|
||||||
class="text-input"
|
|
||||||
placeholder="请在此输入需要改写的文本..."
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:disabled="isGenerating"
|
|
||||||
@click="$emit('rewrite')"
|
|
||||||
class="rewrite-button"
|
|
||||||
>
|
|
||||||
<span v-if="isGenerating" class="loading-spinner"></span>
|
|
||||||
<span v-else>{{ mode === 'single' ? '生成改写' : '批量生成' }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import ModelSelector from './ModelSelector.vue'
|
|
||||||
import RewriteMode from './RewriteMode.vue'
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
mode: String,
|
|
||||||
originalText: String,
|
|
||||||
generationCount: Number,
|
|
||||||
selectedModel: String,
|
|
||||||
availableModels: Array,
|
|
||||||
isGenerating: Boolean
|
|
||||||
})
|
|
||||||
|
|
||||||
defineEmits([
|
|
||||||
'update:mode',
|
|
||||||
'update:originalText',
|
|
||||||
'update:generationCount',
|
|
||||||
'update:selectedModel',
|
|
||||||
'rewrite',
|
|
||||||
'toggle-settings'
|
|
||||||
])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.input-section {
|
|
||||||
flex: 0 0 40%;
|
|
||||||
max-width: 600px;
|
|
||||||
min-width: 320px;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 28px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .input-section {
|
|
||||||
background-color: rgba(30, 30, 46, 0.9);
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logo 样式 */
|
|
||||||
.header-section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-icon {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background: linear-gradient(135deg, #2ecc71, #27ae60);
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 20px;
|
|
||||||
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 设置按钮 */
|
|
||||||
.settings-button {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
transform: rotate(30deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .settings-button {
|
|
||||||
background-color: #2d2d3a;
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .settings-button:hover {
|
|
||||||
background-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题样式 */
|
|
||||||
.title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 4px 0;
|
|
||||||
background: linear-gradient(90deg, #2ecc71, #27ae60);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .subtitle {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
color: #333;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .section-title {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 字数统计 */
|
|
||||||
.char-count {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
font-weight: normal;
|
|
||||||
margin-left: auto;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .char-count {
|
|
||||||
background-color: #2d2d3a;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 输入区域样式 */
|
|
||||||
.original-text-section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input {
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
min-height: 150px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
transition: border-color 0.3s, box-shadow 0.3s;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #2ecc71;
|
|
||||||
box-shadow: 0 0 0 3px rgba(46, 204, 113, 0.2);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .text-input {
|
|
||||||
background-color: #2d2d3a;
|
|
||||||
border-color: #3d3d4a;
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .text-input:focus {
|
|
||||||
border-color: #2ecc71;
|
|
||||||
background-color: #252533;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮样式 */
|
|
||||||
.rewrite-button {
|
|
||||||
background: linear-gradient(135deg, #2ecc71, #27ae60);
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rewrite-button:hover:not(:disabled) {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 16px rgba(46, 204, 113, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rewrite-button:active:not(:disabled) {
|
|
||||||
transform: translateY(1px);
|
|
||||||
box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rewrite-button:disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载动画 */
|
|
||||||
.loading-spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top-color: white;
|
|
||||||
animation: spin 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.input-section {
|
|
||||||
flex: none;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.input-section {
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-section {
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.input-section {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="result-section">
|
|
||||||
<div class="result-header-bar">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span class="section-icon">✨</span>
|
|
||||||
改写结果
|
|
||||||
</h2>
|
|
||||||
<div class="result-actions">
|
|
||||||
<div class="display-mode-toggle">
|
|
||||||
<button
|
|
||||||
:class="{ active: displayMode === 'list' }"
|
|
||||||
@click="$emit('change-display-mode', 'list')"
|
|
||||||
class="display-mode-button"
|
|
||||||
title="列表视图"
|
|
||||||
>
|
|
||||||
<span class="mode-icon">📃</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:class="{ active: displayMode === 'grid' }"
|
|
||||||
@click="$emit('change-display-mode', 'grid')"
|
|
||||||
class="display-mode-button"
|
|
||||||
title="网格视图"
|
|
||||||
>
|
|
||||||
<span class="mode-icon">📊</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="result-stats">
|
|
||||||
<span class="model-badge">{{ selectedModel }}</span>
|
|
||||||
<span class="count-badge">{{ rewrittenText.length }} 个结果</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isGenerating && rewrittenText.length === 0" class="generating-placeholder">
|
|
||||||
<div class="generating-animation">
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div class="dot"></div>
|
|
||||||
</div>
|
|
||||||
<p>正在使用 {{ selectedModel }} 生成改写结果...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="results-container">
|
|
||||||
<div :class="['results-wrapper', `display-${displayMode}`]">
|
|
||||||
<ResultItem
|
|
||||||
v-for="(text, index) in rewrittenText"
|
|
||||||
:key="index"
|
|
||||||
:text="text"
|
|
||||||
:index="index"
|
|
||||||
:isExpanded="expandedResults[index]"
|
|
||||||
:isCopied="copiedIndex === index"
|
|
||||||
@toggle-expand="$emit('toggle-expand', index)"
|
|
||||||
@copy-text="$emit('copy-text', text, index)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import ResultItem from './ResultItem.vue'
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
rewrittenText: Array,
|
|
||||||
selectedModel: String,
|
|
||||||
isGenerating: Boolean,
|
|
||||||
expandedResults: Object,
|
|
||||||
copiedIndex: Number,
|
|
||||||
displayMode: String
|
|
||||||
})
|
|
||||||
|
|
||||||
defineEmits(['toggle-expand', 'copy-text', 'change-display-mode'])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.result-section {
|
|
||||||
flex: 1;
|
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 28px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .result-section {
|
|
||||||
background-color: rgba(30, 30, 46, 0.9);
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-header-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .result-header-bar {
|
|
||||||
border-bottom-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .section-title {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-toggle {
|
|
||||||
display: flex;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .display-mode-toggle {
|
|
||||||
background-color: #2d2d3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-button.active {
|
|
||||||
background-color: white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .display-mode-button.active {
|
|
||||||
background-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-stats {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-badge, .count-badge {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-badge {
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
color: #2e7d32;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .model-badge {
|
|
||||||
background-color: #2e7d32;
|
|
||||||
color: #e8f5e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.count-badge {
|
|
||||||
background-color: #e3f2fd;
|
|
||||||
color: #1565c0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .count-badge {
|
|
||||||
background-color: #1565c0;
|
|
||||||
color: #e3f2fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 结果区域样式 */
|
|
||||||
.results-container {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-wrapper {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 生成中占位符 */
|
|
||||||
.generating-placeholder {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .generating-placeholder {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generating-placeholder p {
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generating-animation {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background-color: #2ecc71;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: bounce 1.4s infinite ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot:nth-child(1) {
|
|
||||||
animation-delay: -0.32s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot:nth-child(2) {
|
|
||||||
animation-delay: -0.16s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
0%, 80%, 100% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: scale(1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.result-section {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.result-section {
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-header-bar {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-actions {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.result-section {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-stats {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -0,0 +1,221 @@
|
|||||||
|
<template>
|
||||||
|
<div class="model-selection-section">
|
||||||
|
<h2 class="section-title">
|
||||||
|
<span class="section-icon">🤖</span>
|
||||||
|
AI 模型选择
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="model-input-group">
|
||||||
|
<div ref="modelDropdown" @click="toggleModelList" class="model-dropdown">
|
||||||
|
<input
|
||||||
|
v-model="selectedModel"
|
||||||
|
type="text"
|
||||||
|
class="model-input"
|
||||||
|
placeholder="选择或输入模型名称"
|
||||||
|
@input="onModelInput"
|
||||||
|
/>
|
||||||
|
<span :class="{ 'arrow-up': showModelList }" class="dropdown-arrow">▼</span>
|
||||||
|
|
||||||
|
<!-- 模型列表下拉框 -->
|
||||||
|
<div v-show="showModelList" class="model-list">
|
||||||
|
<div
|
||||||
|
v-for="model in availableModels"
|
||||||
|
:key="model"
|
||||||
|
:class="{ active: model === selectedModel }"
|
||||||
|
@click="selectModel(model)"
|
||||||
|
class="model-item"
|
||||||
|
>
|
||||||
|
<span class="model-name">{{ model }}</span>
|
||||||
|
<!-- <span class="model-description">{{ model.description }}</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelvalue: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// AI 模型列表
|
||||||
|
const availableModels = ref([])
|
||||||
|
|
||||||
|
const loading = ref(true) // 加载状态
|
||||||
|
const error = ref(false)
|
||||||
|
|
||||||
|
// 模型选择相关
|
||||||
|
const selectedModel = ref(props.modelvalue)
|
||||||
|
const showModelList = ref(false)
|
||||||
|
const modelDropdown = ref(null)
|
||||||
|
// const filteredModels = computed(() => {
|
||||||
|
// if (!selectedModel.value) return availableModels
|
||||||
|
// const searchTerm = selectedModel.value.toLowerCase()
|
||||||
|
// return availableModels.value.filter((model) => model.toLowerCase().includes(searchTerm))
|
||||||
|
// })
|
||||||
|
const emit = defineEmits(['update:modelvalue'])
|
||||||
|
// const updateParent = () => {
|
||||||
|
// emit('update:modelvalue', selectModel.value);
|
||||||
|
// };
|
||||||
|
watch(selectedModel, (newValue) => {
|
||||||
|
emit('update:modelvalue', newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换模型列表显示
|
||||||
|
const toggleModelList = () => {
|
||||||
|
showModelList.value = !showModelList.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择模型
|
||||||
|
const selectModel = (model) => {
|
||||||
|
selectedModel.value = model
|
||||||
|
showModelList.value = false
|
||||||
|
const saveSelectedModel = async () =>
|
||||||
|
await window.mainApi.invoke('store-set', 'model', selectedModel.value)
|
||||||
|
saveSelectedModel()
|
||||||
|
// updateParent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模型输入过滤 - 已移至计算属性
|
||||||
|
// const onModelInput = () => {
|
||||||
|
// // 过滤逻辑已移至计算属性
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 监听点击事件,关闭模型列表
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (modelDropdown.value && !modelDropdown.value.contains(event.target)) {
|
||||||
|
showModelList.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
const fetchModels = async () => {
|
||||||
|
try {
|
||||||
|
selectedModel.value = await window.mainApi.invoke('store-get', 'model')
|
||||||
|
|
||||||
|
const res = await window.mainApi.invoke('fetch-models')
|
||||||
|
|
||||||
|
// console.log('获取模型数据成功:', res);
|
||||||
|
availableModels.value = res
|
||||||
|
if (availableModels.value && availableModels.value.length > 0 && !selectedModel.value) {
|
||||||
|
selectedModel.value = availableModels.value[0]
|
||||||
|
} else if (!selectedModel.value) {
|
||||||
|
selectedModel.value = 'gpt-4o'
|
||||||
|
}
|
||||||
|
// console.log('availableModels:', availableModels.value);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取模型数据失败:', err)
|
||||||
|
error.value = true // 设置错误状态
|
||||||
|
} finally {
|
||||||
|
loading.value = false // 结束加载状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchModels()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 模型选择样式 */
|
||||||
|
.model-selection-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-input-group {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-dropdown {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-right: 36px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2ecc71;
|
||||||
|
box-shadow: 0 0 0 3px rgba(46, 204, 113, 0.2);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #666;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up {
|
||||||
|
transform: translateY(-50%) rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item {
|
||||||
|
padding: 14px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item.active {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,339 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div @click="$emit('close')" class="modal-overlay">
|
|
||||||
<div @click.stop class="settings-modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>设置</h2>
|
|
||||||
<button @click="$emit('close')" class="close-button">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="settings-section">
|
|
||||||
<h3>界面设置</h3>
|
|
||||||
<div class="setting-item">
|
|
||||||
<label class="setting-label">
|
|
||||||
<input v-model="localSettings.darkMode" type="checkbox">
|
|
||||||
深色模式
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<label class="setting-label">
|
|
||||||
<input v-model="localSettings.compactMode" type="checkbox">
|
|
||||||
紧凑视图
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-section">
|
|
||||||
<h3>生成设置</h3>
|
|
||||||
<div class="setting-item">
|
|
||||||
<label class="setting-label">温度值</label>
|
|
||||||
<div class="slider-container">
|
|
||||||
<input
|
|
||||||
v-model="localSettings.temperature"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
type="range"
|
|
||||||
class="slider"
|
|
||||||
>
|
|
||||||
<span class="slider-value">{{ localSettings.temperature / 100 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<label class="setting-label">最大长度</label>
|
|
||||||
<select v-model="localSettings.maxLength" class="select-input">
|
|
||||||
<option value="500">500 字</option>
|
|
||||||
<option value="1000">1000 字</option>
|
|
||||||
<option value="2000">2000 字</option>
|
|
||||||
<option value="3000">3000 字</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button @click="$emit('close')" class="cancel-button">取消</button>
|
|
||||||
<button @click="saveSettings" class="save-button">保存设置</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { reactive, onMounted } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
settings: Object
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['close', 'save'])
|
|
||||||
|
|
||||||
const localSettings = reactive({
|
|
||||||
darkMode: false,
|
|
||||||
compactMode: false,
|
|
||||||
temperature: 70,
|
|
||||||
maxLength: '2000'
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 复制设置到本地状态
|
|
||||||
Object.assign(localSettings, props.settings)
|
|
||||||
})
|
|
||||||
|
|
||||||
const saveSettings = () => {
|
|
||||||
emit('save', localSettings)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 设置弹窗 */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
animation: fadeIn 0.3s ease;
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-modal {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 500px;
|
|
||||||
max-height: 90vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
||||||
animation: slideUp 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .settings-modal {
|
|
||||||
background-color: #2d2d3a;
|
|
||||||
color: #f0f0f0;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .modal-header {
|
|
||||||
border-bottom-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .modal-header h2 {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button:hover {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .close-button {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .close-button:hover {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-section h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
color: #333;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .settings-section h3 {
|
|
||||||
color: #f0f0f0;
|
|
||||||
border-bottom-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .setting-label {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-container {
|
|
||||||
display: flex;
|
|
||||||
align- .setting-label {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex: 1;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
flex: 1;
|
|
||||||
height: 6px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background: #e0e0e0;
|
|
||||||
border-radius: 3px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .slider {
|
|
||||||
background: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #2ecc71;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-webkit-slider-thumb:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-value {
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .slider-value {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-input {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: white;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .select-input {
|
|
||||||
background-color: #252533;
|
|
||||||
border-color: #3d3d4a;
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 16px 20px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .modal-footer {
|
|
||||||
border-top-color: #3d3d4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-button, .save-button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-button {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .cancel-button {
|
|
||||||
background-color: #3d3d4a;
|
|
||||||
border-color: #4d4d5a;
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-button:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dark-mode) .cancel-button:hover {
|
|
||||||
background-color: #4d4d5a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-button {
|
|
||||||
background-color: #2ecc71;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-button:hover {
|
|
||||||
background-color: #27ae60;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(40px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
1
lingtropy-client/src/renderer/constants/constants.js
Normal file
1
lingtropy-client/src/renderer/constants/constants.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const ARTICLE_MAX_COUNT = 50 // 生成文章数量的上限
|
||||||
BIN
lingtropy-client/src/renderer/public/placeholder-logo.png
Normal file
BIN
lingtropy-client/src/renderer/public/placeholder-logo.png
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
BIN
lingtropy-client/src/renderer/public/placeholder-user.jpg
Normal file
BIN
lingtropy-client/src/renderer/public/placeholder-user.jpg
Normal file
Binary file not shown.
BIN
lingtropy-client/src/renderer/public/placeholder.jpg
Normal file
BIN
lingtropy-client/src/renderer/public/placeholder.jpg
Normal file
Binary file not shown.
1
lingtropy-client/src/renderer/public/placeholder.svg
Normal file
1
lingtropy-client/src/renderer/public/placeholder.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user