mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-09 22:22:58 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546c82ca40 | ||
|
|
f132dc38f4 | ||
|
|
58c70b8ca6 | ||
|
|
147f55eefe | ||
|
|
229b7b0c12 | ||
|
|
4b7b5ff8a4 | ||
|
|
4906bde746 | ||
|
|
a87a1a8988 | ||
|
|
e05f45e681 | ||
|
|
b4acacea81 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moviepilot",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": "dist/service.js",
|
||||
|
||||
34
src/App.vue
34
src/App.vue
@@ -38,6 +38,9 @@ const backgroundImages = ref<string[]>([])
|
||||
const activeImageIndex = ref(0)
|
||||
const isTransparentTheme = computed(() => globalTheme.name.value === 'transparent')
|
||||
|
||||
// 心跳检测
|
||||
let heartbeatInterval: number | null = null
|
||||
|
||||
// ApexCharts 全局配置
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -45,6 +48,33 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳
|
||||
const startHeartbeat = () => {
|
||||
// 如果已经有心跳,则先停止
|
||||
if (heartbeatInterval) {
|
||||
stopHeartbeat()
|
||||
}
|
||||
|
||||
// 开始心跳任务
|
||||
heartbeatInterval = window.setInterval(async () => {
|
||||
try {
|
||||
if (isLogin.value) {
|
||||
await api.get('dashboard/cpu')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Heartbeat request failed:', error)
|
||||
}
|
||||
}, 5 * 60 * 1000)
|
||||
}
|
||||
|
||||
// 停止心跳
|
||||
const stopHeartbeat = () => {
|
||||
if (heartbeatInterval) {
|
||||
window.clearInterval(heartbeatInterval)
|
||||
heartbeatInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
// 配置 ApexCharts 全局选项
|
||||
function configureApexCharts() {
|
||||
if (typeof window !== 'undefined' && window.Apex) {
|
||||
@@ -234,11 +264,15 @@ onMounted(async () => {
|
||||
ensureRenderComplete(() => {
|
||||
nextTick(removeLoadingWithStateCheck)
|
||||
})
|
||||
// 启动心跳
|
||||
startHeartbeat()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清除背景轮换定时器
|
||||
removeBackgroundTimer('background-rotation')
|
||||
// 停止心跳
|
||||
stopHeartbeat()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,10 +6,20 @@ import type { DownloaderConf, MediaInfo, TorrentInfo, TransferDirectoryConf } fr
|
||||
import { formatFileSize } from '@/@core/utils/formatters'
|
||||
import { VCardTitle, VChip } from 'vuetify/lib/components/index.mjs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
||||
import { numberValidator } from '@/@validators'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 当前识别类型
|
||||
const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
@@ -38,6 +48,18 @@ const directories = ref<TransferDirectoryConf[]>([])
|
||||
// 是否正在加载
|
||||
const loading = ref(false)
|
||||
|
||||
// 是否显示高级选项
|
||||
const showAdvancedOptions = ref(false)
|
||||
|
||||
// TMDB ID
|
||||
const tmdbid = ref<number | undefined>(undefined)
|
||||
|
||||
// 豆瓣ID
|
||||
const doubanId = ref<string | undefined>(undefined)
|
||||
|
||||
// TMDB选择对话框
|
||||
const mediaSelectorDialog = ref(false)
|
||||
|
||||
// 计算按钮图标
|
||||
const icon = computed(() => (loading.value ? 'mdi-progress-download' : 'mdi-download'))
|
||||
|
||||
@@ -96,6 +118,14 @@ async function addDownload() {
|
||||
payload.media_in = props.media
|
||||
}
|
||||
|
||||
// 添加媒体ID辅助识别
|
||||
if (tmdbid.value) {
|
||||
payload.tmdbid = tmdbid.value
|
||||
}
|
||||
if (doubanId.value) {
|
||||
payload.doubanid = doubanId.value
|
||||
}
|
||||
|
||||
const endpoint = props.media ? 'download/' : 'download/add'
|
||||
|
||||
result = await api.post(endpoint, payload)
|
||||
@@ -202,6 +232,56 @@ onMounted(() => {
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow class="px-5 mt-2">
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
variant="text"
|
||||
size="small"
|
||||
:prepend-icon="showAdvancedOptions ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||
@click="showAdvancedOptions = !showAdvancedOptions"
|
||||
>
|
||||
{{
|
||||
showAdvancedOptions
|
||||
? t('dialog.addDownload.hideAdvancedOptions')
|
||||
: t('dialog.addDownload.showAdvancedOptions')
|
||||
}}
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-show="showAdvancedOptions" class="px-5">
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-if="mediaSource === 'themoviedb'"
|
||||
v-model="tmdbid"
|
||||
:label="t('dialog.reorganize.tmdbId')"
|
||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
||||
:rules="[numberValidator]"
|
||||
append-inner-icon="mdi-magnify"
|
||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
size="small"
|
||||
variant="underlined"
|
||||
density="comfortable"
|
||||
@click:append-inner="mediaSelectorDialog = true"
|
||||
/>
|
||||
<VTextField
|
||||
v-else
|
||||
v-model="doubanId"
|
||||
:label="t('dialog.reorganize.doubanId')"
|
||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
||||
:rules="[numberValidator]"
|
||||
append-inner-icon="mdi-magnify"
|
||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
size="small"
|
||||
variant="underlined"
|
||||
density="comfortable"
|
||||
@click:append-inner="mediaSelectorDialog = true"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText class="text-center">
|
||||
<VBtn variant="elevated" :disabled="loading" @click="addDownload" :prepend-icon="icon" class="px-5">
|
||||
@@ -209,5 +289,15 @@ onMounted(() => {
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<!-- 媒体ID选择器 -->
|
||||
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
||||
<MediaIdSelector
|
||||
v-if="mediaSource === 'themoviedb'"
|
||||
v-model="tmdbid"
|
||||
@close="mediaSelectorDialog = false"
|
||||
:type="mediaSource"
|
||||
/>
|
||||
<MediaIdSelector v-else v-model="doubanId" @close="mediaSelectorDialog = false" :type="mediaSource" />
|
||||
</VDialog>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
@@ -1256,6 +1256,8 @@ export default {
|
||||
llmApiKeyPlaceholder: 'Please enter API key',
|
||||
llmBaseUrl: 'LLM Base URL',
|
||||
llmBaseUrlHint: 'Base URL for LLM API, used for custom API endpoints',
|
||||
aiAgentGlobal: 'Global AI Assistant',
|
||||
aiAgentGlobalHint: 'Enable global AI assistant functionality, all message conversations will be answered by the AI agent without using the /ai command',
|
||||
advancedSettings: 'Advanced Settings',
|
||||
advancedSettingsDesc: 'System advanced settings, only need to be adjusted in special cases',
|
||||
downloaders: 'Downloaders',
|
||||
@@ -1914,6 +1916,8 @@ export default {
|
||||
startDownload: 'Start Download',
|
||||
downloadSuccess: '{site} {title} downloaded successfully!',
|
||||
downloadFailed: '{site} {title} download failed: {message}!',
|
||||
showAdvancedOptions: 'Show Advanced Options',
|
||||
hideAdvancedOptions: 'Hide Advanced Options',
|
||||
},
|
||||
subscribeShare: {
|
||||
shareSubscription: 'Share Subscription',
|
||||
|
||||
@@ -1252,6 +1252,8 @@ export default {
|
||||
llmApiKeyPlaceholder: '请输入API密钥',
|
||||
llmBaseUrl: 'LLM基础URL',
|
||||
llmBaseUrlHint: 'LLM API的基础URL地址,用于自定义API端点',
|
||||
aiAgentGlobal: '全局智能助手',
|
||||
aiAgentGlobalHint: '启用全局智能助手功能,所有消息对话均使用智能体回答而不用使用/ai命令',
|
||||
advancedSettings: '高级设置',
|
||||
advancedSettingsDesc: '系统进阶设置,特殊情况下才需要调整',
|
||||
downloaders: '下载器',
|
||||
@@ -1890,6 +1892,8 @@ export default {
|
||||
startDownload: '开始下载',
|
||||
downloadSuccess: '{site} {title} 下载成功!',
|
||||
downloadFailed: '{site} {title} 下载失败:{message}!',
|
||||
showAdvancedOptions: '显示高级选项',
|
||||
hideAdvancedOptions: '隐藏高级选项',
|
||||
},
|
||||
subscribeShare: {
|
||||
shareSubscription: '分享订阅',
|
||||
|
||||
@@ -1240,6 +1240,8 @@ export default {
|
||||
llmApiKeyPlaceholder: '請輸入API密鑰',
|
||||
llmBaseUrl: 'LLM基礎URL',
|
||||
llmBaseUrlHint: 'LLM API的基礎URL地址,用於自定義API端點',
|
||||
aiAgentGlobal: '全局智能助手',
|
||||
aiAgentGlobalHint: '啟用全局智能助手功能,所有消息對話均使用智能體回答而不用使用/ai命令',
|
||||
advancedSettings: '高級設置',
|
||||
advancedSettingsDesc: '系統進階設置,特殊情況下才需要調整',
|
||||
downloaders: '下載器',
|
||||
@@ -1876,6 +1878,8 @@ export default {
|
||||
startDownload: '開始下載',
|
||||
downloadSuccess: '{site} {title} 下載成功!',
|
||||
downloadFailed: '{site} {title} 下載失敗:{message}!',
|
||||
showAdvancedOptions: '顯示高級選項',
|
||||
hideAdvancedOptions: '隱藏高級選項',
|
||||
},
|
||||
subscribeShare: {
|
||||
shareSubscription: '分享訂閱',
|
||||
|
||||
@@ -32,6 +32,7 @@ const SystemSettings = ref<any>({
|
||||
OCR_HOST: null,
|
||||
CUSTOMIZE_WALLPAPER_API_URL: null,
|
||||
AI_AGENT_ENABLE: false,
|
||||
AI_AGENT_GLOBAL: false,
|
||||
LLM_PROVIDER: 'deepseek',
|
||||
LLM_MODEL: 'deepseek-chat',
|
||||
LLM_API_KEY: null,
|
||||
@@ -119,6 +120,10 @@ const progressDialog = ref(false)
|
||||
// 高级设置对话框
|
||||
const advancedDialog = ref(false)
|
||||
|
||||
// LLM 模型列表
|
||||
const llmModels = ref<string[]>([])
|
||||
const loadingModels = ref(false)
|
||||
|
||||
const activeTab = ref('system')
|
||||
|
||||
// 元数据语言
|
||||
@@ -154,6 +159,30 @@ const logLevelItems = [
|
||||
// 安全域名添加变量
|
||||
const newSecurityDomain = ref('')
|
||||
|
||||
// 加载LLM模型列表
|
||||
async function loadLlmModels() {
|
||||
loadingModels.value = true
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/llm-models', {
|
||||
params: {
|
||||
provider: SystemSettings.value.Basic.LLM_PROVIDER,
|
||||
api_key: SystemSettings.value.Basic.LLM_API_KEY,
|
||||
base_url: SystemSettings.value.Basic.LLM_BASE_URL,
|
||||
},
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
llmModels.value = result.data
|
||||
if (llmModels.value.length > 0) SystemSettings.value.Basic.LLM_MODEL = llmModels.value[0]
|
||||
} else {
|
||||
$toast.error(result.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
loadingModels.value = false
|
||||
}
|
||||
|
||||
// 添加安全域名
|
||||
function addSecurityDomain() {
|
||||
if (
|
||||
@@ -638,12 +667,12 @@ onDeactivated(() => {
|
||||
</VCol>
|
||||
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basic.LLM_MODEL"
|
||||
:label="t('setting.system.llmModel')"
|
||||
:hint="t('setting.system.llmModelHint')"
|
||||
placeholder="gpt-3.5-turbo"
|
||||
v-model="SystemSettings.Basic.LLM_BASE_URL"
|
||||
:label="t('setting.system.llmBaseUrl')"
|
||||
:hint="t('setting.system.llmBaseUrlHint')"
|
||||
placeholder="https://api.deepseek.com"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-brain"
|
||||
prepend-inner-icon="mdi-link"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||
@@ -658,13 +687,33 @@ onDeactivated(() => {
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basic.LLM_BASE_URL"
|
||||
:label="t('setting.system.llmBaseUrl')"
|
||||
:hint="t('setting.system.llmBaseUrlHint')"
|
||||
placeholder="https://api.deepseek.com"
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Basic.LLM_MODEL"
|
||||
:label="t('setting.system.llmModel')"
|
||||
:hint="t('setting.system.llmModelHint')"
|
||||
:placeholder="t('setting.system.llmModelHint')"
|
||||
persistent-hint
|
||||
:items="llmModels"
|
||||
:loading="loadingModels"
|
||||
prepend-inner-icon="mdi-brain"
|
||||
>
|
||||
<template #append-inner>
|
||||
<VBtn
|
||||
variant="text"
|
||||
icon="mdi-refresh"
|
||||
size="small"
|
||||
@click="loadLlmModels"
|
||||
:disabled="!SystemSettings.Basic.LLM_API_KEY"
|
||||
/>
|
||||
</template>
|
||||
</VCombobox>
|
||||
</VCol>
|
||||
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basic.AI_AGENT_GLOBAL"
|
||||
:label="t('setting.system.aiAgentGlobal')"
|
||||
:hint="t('setting.system.aiAgentGlobalHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-link"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
Reference in New Issue
Block a user