mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-12 02:21:06 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
229b7b0c12 | ||
|
|
4b7b5ff8a4 | ||
|
|
4906bde746 | ||
|
|
a87a1a8988 | ||
|
|
e05f45e681 | ||
|
|
b4acacea81 | ||
|
|
fa9645b05b | ||
|
|
1ed4052814 | ||
|
|
7dc814461f | ||
|
|
9154ec0e8c | ||
|
|
3a2ea60583 | ||
|
|
b36bff3a1e | ||
|
|
b3d8cbf280 | ||
|
|
38fb02d112 | ||
|
|
2597f893cd | ||
|
|
ebdd036654 | ||
|
|
5032f0e6a9 | ||
|
|
ad963d718d | ||
|
|
69d314bce3 | ||
|
|
4a7425a947 |
@@ -245,13 +245,21 @@ const props = defineProps({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard-widget">
|
<div class="dashboard-widget">
|
||||||
<!-- 仪表板内容 -->
|
<v-hover>
|
||||||
<v-card>
|
<!-- 仪表板内容 -->
|
||||||
<v-card-title>{{ config.title || '仪表板组件' }}</v-card-title>
|
<template #default="{ isHovering, props: hoverProps }">
|
||||||
<v-card-text>
|
<v-card v-bind="hoverProps">
|
||||||
<!-- 组件内容 -->
|
<v-card-title>{{ config.title || '仪表板组件' }}</v-card-title>
|
||||||
</v-card-text>
|
<v-card-text>
|
||||||
</v-card>
|
<!-- 组件内容 -->
|
||||||
|
</v-card-text>
|
||||||
|
<!-- 只在悬停时显示拖拽图标 -->
|
||||||
|
<div v-show="isHovering" class="absolute right-5 top-5">
|
||||||
|
<v-icon class="cursor-move">mdi-drag</v-icon>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
</v-hover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "2.8.1",
|
"version": "2.8.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ export function kFormatter(num: number) {
|
|||||||
: Math.abs(num).toFixed(0).replace(regex, ',')
|
: Math.abs(num).toFixed(0).replace(regex, ',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化下载量显示,超过1000显示为x.xk格式
|
||||||
|
export function formatDownloadCount(num: number): string {
|
||||||
|
if (!num || num < 1000) return num?.toLocaleString() || '0'
|
||||||
|
|
||||||
|
return `${(num / 1000).toFixed(1)}k`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format and return date in Humanize format
|
* Format and return date in Humanize format
|
||||||
* Intl docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format
|
* Intl docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format
|
||||||
|
|||||||
@@ -314,6 +314,8 @@ export interface MediaInfo {
|
|||||||
production_countries?: any[]
|
production_countries?: any[]
|
||||||
// 语种
|
// 语种
|
||||||
spoken_languages?: string[]
|
spoken_languages?: string[]
|
||||||
|
// 数字/实体发行日期
|
||||||
|
release_dates?: MediaRelease[]
|
||||||
// 状态
|
// 状态
|
||||||
status?: string
|
status?: string
|
||||||
// 标签
|
// 标签
|
||||||
@@ -368,6 +370,18 @@ export interface TmdbSeason {
|
|||||||
vote_average?: number
|
vote_average?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发行信息
|
||||||
|
export interface MediaRelease {
|
||||||
|
// 发行日期
|
||||||
|
date: string
|
||||||
|
// 发行地区
|
||||||
|
iso_code: string
|
||||||
|
// 备注
|
||||||
|
note?: string
|
||||||
|
// 发行类型
|
||||||
|
type: number
|
||||||
|
}
|
||||||
|
|
||||||
// TMDB集信息
|
// TMDB集信息
|
||||||
export interface TmdbEpisode {
|
export interface TmdbEpisode {
|
||||||
// 上映日期
|
// 上映日期
|
||||||
@@ -992,6 +1006,8 @@ export interface MediaServerPlayItem {
|
|||||||
percent?: number
|
percent?: number
|
||||||
// 媒体服务器类型
|
// 媒体服务器类型
|
||||||
server_type?: string
|
server_type?: string
|
||||||
|
// 图片是否需要Cookies
|
||||||
|
use_cookies?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 媒体服务器媒体库
|
// 媒体服务器媒体库
|
||||||
@@ -1014,6 +1030,8 @@ export interface MediaServerLibrary {
|
|||||||
link?: string
|
link?: string
|
||||||
// 媒体服务器类型
|
// 媒体服务器类型
|
||||||
server_type?: string
|
server_type?: string
|
||||||
|
// 图片是否需要Cookies
|
||||||
|
use_cookies?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息通知
|
// 消息通知
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ async function goPlay() {
|
|||||||
// 计算图片地址
|
// 计算图片地址
|
||||||
const getImgUrl = computed(() => {
|
const getImgUrl = computed(() => {
|
||||||
const image = props.media?.image || ''
|
const image = props.media?.image || ''
|
||||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
let url = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
||||||
|
const use_cookies = props.media?.use_cookies
|
||||||
|
if (use_cookies) {
|
||||||
|
url += `&use_cookies=${encodeURIComponent(use_cookies)}`
|
||||||
|
}
|
||||||
|
return url
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -52,20 +52,28 @@ async function goPlay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成图片代理路径
|
// 生成图片代理路径
|
||||||
function getImgUrl(url: string) {
|
function getImgUrl(url: string, use_cookies?: boolean) {
|
||||||
if (!url) return getDefaultImage()
|
if (!url) return getDefaultImage()
|
||||||
else return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
|
let imgurl = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
|
||||||
|
if (use_cookies) {
|
||||||
|
imgurl += `&use_cookies=${encodeURIComponent(use_cookies)}`
|
||||||
|
}
|
||||||
|
return imgurl
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据多张图片生成媒体库封面
|
// 根据多张图片生成媒体库封面
|
||||||
async function drawImages(imageList: string[]) {
|
async function drawImages(imageList: string[], use_cookies?: boolean) {
|
||||||
// 图片
|
// 图片
|
||||||
const IMAGES = imageList
|
const IMAGES = imageList
|
||||||
if (IMAGES.length === 0) return getDefaultImage()
|
if (IMAGES.length === 0) return getDefaultImage()
|
||||||
|
|
||||||
// 为所有图片添加system/img前缀
|
// 为所有图片添加system/img前缀
|
||||||
for (let i = 0; i < IMAGES.length; i++)
|
for (let i = 0; i < IMAGES.length; i++) {
|
||||||
IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(IMAGES[i])}`
|
IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(IMAGES[i])}`
|
||||||
|
if (use_cookies) {
|
||||||
|
IMAGES[i] += `&use_cookies=${encodeURIComponent(use_cookies)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// canvas
|
// canvas
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
@@ -137,8 +145,8 @@ async function drawImages(imageList: string[]) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (props.media?.image_list && props.media?.image_list.length > 0)
|
if (props.media?.image_list && props.media?.image_list.length > 0)
|
||||||
imgUrl.value = await drawImages(props.media?.image_list || [])
|
imgUrl.value = await drawImages(props.media?.image_list || [], props.media?.use_cookies)
|
||||||
else imgUrl.value = getImgUrl(props.media?.image || '')
|
else imgUrl.value = getImgUrl(props.media?.image || '', props.media?.use_cookies)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Plugin } from '@/api/types'
|
|||||||
import { getLogoUrl } from '@/utils/imageUtils'
|
import { getLogoUrl } from '@/utils/imageUtils'
|
||||||
import { getDominantColor } from '@/@core/utils/image'
|
import { getDominantColor } from '@/@core/utils/image'
|
||||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||||
|
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ const dropdownItems = ref([
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="props.count" class="ms-2 flex-shrink-0 download-count align-middle items-center">
|
<div v-if="props.count" class="ms-2 flex-shrink-0 download-count align-middle items-center">
|
||||||
<VIcon size="small" icon="mdi-download" />
|
<VIcon size="small" icon="mdi-download" />
|
||||||
<span class="text-sm">{{ props.count?.toLocaleString() }}</span>
|
<span class="text-sm">{{ formatDownloadCount(props.count) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 right-0">
|
<div class="absolute bottom-0 right-0">
|
||||||
@@ -327,7 +328,7 @@ const dropdownItems = ref([
|
|||||||
}}</VBtn>
|
}}</VBtn>
|
||||||
<div class="text-xs mt-2" v-if="props.count">
|
<div class="text-xs mt-2" v-if="props.count">
|
||||||
<VIcon icon="mdi-fire" />{{
|
<VIcon icon="mdi-fire" />{{
|
||||||
t('plugin.totalDownloads', { count: props.count?.toLocaleString() })
|
t('plugin.totalDownloads', { count: formatDownloadCount(props.count) })
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Plugin } from '@/api/types'
|
|||||||
import { isNullOrEmptyObject } from '@core/utils'
|
import { isNullOrEmptyObject } from '@core/utils'
|
||||||
import { getLogoUrl } from '@/utils/imageUtils'
|
import { getLogoUrl } from '@/utils/imageUtils'
|
||||||
import { getDominantColor } from '@/@core/utils/image'
|
import { getDominantColor } from '@/@core/utils/image'
|
||||||
|
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||||
import VersionHistory from '@/components/misc/VersionHistory.vue'
|
import VersionHistory from '@/components/misc/VersionHistory.vue'
|
||||||
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||||
import PluginConfigDialog from '../dialog/PluginConfigDialog.vue'
|
import PluginConfigDialog from '../dialog/PluginConfigDialog.vue'
|
||||||
@@ -492,7 +493,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<span v-if="props.count" class="ms-2 flex-shrink-0 download-count items-center align-middle">
|
<span v-if="props.count" class="ms-2 flex-shrink-0 download-count items-center align-middle">
|
||||||
<VIcon size="small" icon="mdi-download" />
|
<VIcon size="small" icon="mdi-download" />
|
||||||
<span class="text-sm">{{ props.count?.toLocaleString() }}</span>
|
<span class="text-sm">{{ formatDownloadCount(props.count) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 right-0">
|
<div class="absolute bottom-0 right-0">
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ function getChipColor(type: string) {
|
|||||||
const getImgUrl = computed(() => {
|
const getImgUrl = computed(() => {
|
||||||
if (imageLoadError.value) return noImage
|
if (imageLoadError.value) return noImage
|
||||||
const image = props.media?.image || ''
|
const image = props.media?.image || ''
|
||||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
let url = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
||||||
|
const use_cookies = props.media?.use_cookies
|
||||||
|
if (use_cookies) {
|
||||||
|
url += `&use_cookies=${encodeURIComponent(use_cookies)}`
|
||||||
|
}
|
||||||
|
return url
|
||||||
})
|
})
|
||||||
|
|
||||||
// 跳转播放
|
// 跳转播放
|
||||||
|
|||||||
@@ -6,10 +6,20 @@ import type { DownloaderConf, MediaInfo, TorrentInfo, TransferDirectoryConf } fr
|
|||||||
import { formatFileSize } from '@/@core/utils/formatters'
|
import { formatFileSize } from '@/@core/utils/formatters'
|
||||||
import { VCardTitle, VChip } from 'vuetify/lib/components/index.mjs'
|
import { VCardTitle, VChip } from 'vuetify/lib/components/index.mjs'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
||||||
|
import { numberValidator } from '@/@validators'
|
||||||
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
|
|
||||||
// 多语言支持
|
// 多语言支持
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 从 provide 中获取全局设置
|
||||||
|
const globalSettingsStore = useGlobalSettingsStore()
|
||||||
|
const globalSettings = globalSettingsStore.globalSettings
|
||||||
|
|
||||||
|
// 当前识别类型
|
||||||
|
const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: String,
|
title: String,
|
||||||
@@ -38,6 +48,18 @@ const directories = ref<TransferDirectoryConf[]>([])
|
|||||||
// 是否正在加载
|
// 是否正在加载
|
||||||
const loading = ref(false)
|
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'))
|
const icon = computed(() => (loading.value ? 'mdi-progress-download' : 'mdi-download'))
|
||||||
|
|
||||||
@@ -96,6 +118,14 @@ async function addDownload() {
|
|||||||
payload.media_in = props.media
|
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'
|
const endpoint = props.media ? 'download/' : 'download/add'
|
||||||
|
|
||||||
result = await api.post(endpoint, payload)
|
result = await api.post(endpoint, payload)
|
||||||
@@ -202,6 +232,56 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</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>
|
||||||
<VCardText class="text-center">
|
<VCardText class="text-center">
|
||||||
<VBtn variant="elevated" :disabled="loading" @click="addDownload" :prepend-icon="icon" class="px-5">
|
<VBtn variant="elevated" :disabled="loading" @click="addDownload" :prepend-icon="icon" class="px-5">
|
||||||
@@ -209,5 +289,15 @@ onMounted(() => {
|
|||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</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>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -91,10 +91,6 @@ onUnmounted(() => {
|
|||||||
<!-- Vue 渲染模式 -->
|
<!-- Vue 渲染模式 -->
|
||||||
<div v-if="pluginRenderMode === 'vue'">
|
<div v-if="pluginRenderMode === 'vue'">
|
||||||
<component :is="dynamicPluginComponent" :config="props.config" :allow-refresh="props.allowRefresh" :api="api" />
|
<component :is="dynamicPluginComponent" :config="props.config" :allow-refresh="props.allowRefresh" :api="api" />
|
||||||
<!-- Vue 模式下也可以显示拖拽句柄 -->
|
|
||||||
<div class="absolute right-5 top-5">
|
|
||||||
<VIcon class="cursor-move">mdi-drag</VIcon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Vuetify 渲染模式 -->
|
<!-- Vuetify 渲染模式 -->
|
||||||
<VHover v-else-if="pluginRenderMode === 'vuetify'">
|
<VHover v-else-if="pluginRenderMode === 'vuetify'">
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export function useSetupWizard() {
|
|||||||
'emby': 'EmbyModule',
|
'emby': 'EmbyModule',
|
||||||
'jellyfin': 'JellyfinModule',
|
'jellyfin': 'JellyfinModule',
|
||||||
'plex': 'PlexModule',
|
'plex': 'PlexModule',
|
||||||
|
'trimemedia': 'TrimeMediaModule',
|
||||||
},
|
},
|
||||||
// 通知映射
|
// 通知映射
|
||||||
notification: {
|
notification: {
|
||||||
|
|||||||
@@ -788,6 +788,8 @@ export default {
|
|||||||
originalTitle: 'Original Title',
|
originalTitle: 'Original Title',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
releaseDate: 'Release Date',
|
releaseDate: 'Release Date',
|
||||||
|
digitalRelease: 'Digital Release',
|
||||||
|
physicalRelease: 'Physical Release',
|
||||||
originalLanguage: 'Original Language',
|
originalLanguage: 'Original Language',
|
||||||
productionCountries: 'Production Countries',
|
productionCountries: 'Production Countries',
|
||||||
productionCompanies: 'Production Companies',
|
productionCompanies: 'Production Companies',
|
||||||
@@ -1242,6 +1244,18 @@ export default {
|
|||||||
'Used to increase the rate limit threshold when plugins access Github API,it is recommended to configure, otherwise plugins may not work properly',
|
'Used to increase the rate limit threshold when plugins access Github API,it is recommended to configure, otherwise plugins may not work properly',
|
||||||
ocrHost: 'OCR Server',
|
ocrHost: 'OCR Server',
|
||||||
ocrHostHint: 'Used for site check-in, updating site cookies and other captcha recognition',
|
ocrHostHint: 'Used for site check-in, updating site cookies and other captcha recognition',
|
||||||
|
aiAgent: 'Enable AI Assistant',
|
||||||
|
aiAgentEnable: 'Enable AI Assistant',
|
||||||
|
aiAgentEnableHint: 'Enable AI assistant functionality, requires LLM configuration',
|
||||||
|
llmProvider: 'LLM Provider',
|
||||||
|
llmProviderHint: 'Select the LLM service provider to use',
|
||||||
|
llmModel: 'LLM Model Name',
|
||||||
|
llmModelHint: 'Specify the LLM model to use, such as gpt-3.5-turbo, deepseek-chat, etc.',
|
||||||
|
llmApiKey: 'LLM API Key',
|
||||||
|
llmApiKeyHint: 'API key from the LLM service provider for authentication',
|
||||||
|
llmApiKeyPlaceholder: 'Please enter API key',
|
||||||
|
llmBaseUrl: 'LLM Base URL',
|
||||||
|
llmBaseUrlHint: 'Base URL for LLM API, used for custom API endpoints',
|
||||||
advancedSettings: 'Advanced Settings',
|
advancedSettings: 'Advanced Settings',
|
||||||
advancedSettingsDesc: 'System advanced settings, only need to be adjusted in special cases',
|
advancedSettingsDesc: 'System advanced settings, only need to be adjusted in special cases',
|
||||||
downloaders: 'Downloaders',
|
downloaders: 'Downloaders',
|
||||||
@@ -1900,6 +1914,8 @@ export default {
|
|||||||
startDownload: 'Start Download',
|
startDownload: 'Start Download',
|
||||||
downloadSuccess: '{site} {title} downloaded successfully!',
|
downloadSuccess: '{site} {title} downloaded successfully!',
|
||||||
downloadFailed: '{site} {title} download failed: {message}!',
|
downloadFailed: '{site} {title} download failed: {message}!',
|
||||||
|
showAdvancedOptions: 'Show Advanced Options',
|
||||||
|
hideAdvancedOptions: 'Hide Advanced Options',
|
||||||
},
|
},
|
||||||
subscribeShare: {
|
subscribeShare: {
|
||||||
shareSubscription: 'Share Subscription',
|
shareSubscription: 'Share Subscription',
|
||||||
|
|||||||
@@ -786,6 +786,8 @@ export default {
|
|||||||
originalTitle: '原始标题',
|
originalTitle: '原始标题',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
releaseDate: '上映日期',
|
releaseDate: '上映日期',
|
||||||
|
digitalRelease: '数字发行',
|
||||||
|
physicalRelease: '实体发行',
|
||||||
originalLanguage: '原始语言',
|
originalLanguage: '原始语言',
|
||||||
productionCountries: '出品国家',
|
productionCountries: '出品国家',
|
||||||
productionCompanies: '制作公司',
|
productionCompanies: '制作公司',
|
||||||
@@ -1238,6 +1240,18 @@ export default {
|
|||||||
githubTokenHint: '用于提高插件等访问Github API时的限流阈值,建议配置,否则插件可能无法正常使用',
|
githubTokenHint: '用于提高插件等访问Github API时的限流阈值,建议配置,否则插件可能无法正常使用',
|
||||||
ocrHost: '验证码识别服务器',
|
ocrHost: '验证码识别服务器',
|
||||||
ocrHostHint: '用于站点签到、更新站点Cookie等识别验证码',
|
ocrHostHint: '用于站点签到、更新站点Cookie等识别验证码',
|
||||||
|
aiAgent: '启用智能助手',
|
||||||
|
aiAgentEnable: '启用智能助手',
|
||||||
|
aiAgentEnableHint: '启用后可使用智能助手功能,需要配置LLM相关参数',
|
||||||
|
llmProvider: 'LLM提供商',
|
||||||
|
llmProviderHint: '选择使用的LLM服务提供商',
|
||||||
|
llmModel: 'LLM模型名称',
|
||||||
|
llmModelHint: '指定使用的LLM模型,如gpt-3.5-turbo、deepseek-chat等',
|
||||||
|
llmApiKey: 'LLM API密钥',
|
||||||
|
llmApiKeyHint: 'LLM服务提供商的API密钥,用于身份验证',
|
||||||
|
llmApiKeyPlaceholder: '请输入API密钥',
|
||||||
|
llmBaseUrl: 'LLM基础URL',
|
||||||
|
llmBaseUrlHint: 'LLM API的基础URL地址,用于自定义API端点',
|
||||||
advancedSettings: '高级设置',
|
advancedSettings: '高级设置',
|
||||||
advancedSettingsDesc: '系统进阶设置,特殊情况下才需要调整',
|
advancedSettingsDesc: '系统进阶设置,特殊情况下才需要调整',
|
||||||
downloaders: '下载器',
|
downloaders: '下载器',
|
||||||
@@ -1876,6 +1890,8 @@ export default {
|
|||||||
startDownload: '开始下载',
|
startDownload: '开始下载',
|
||||||
downloadSuccess: '{site} {title} 下载成功!',
|
downloadSuccess: '{site} {title} 下载成功!',
|
||||||
downloadFailed: '{site} {title} 下载失败:{message}!',
|
downloadFailed: '{site} {title} 下载失败:{message}!',
|
||||||
|
showAdvancedOptions: '显示高级选项',
|
||||||
|
hideAdvancedOptions: '隐藏高级选项',
|
||||||
},
|
},
|
||||||
subscribeShare: {
|
subscribeShare: {
|
||||||
shareSubscription: '分享订阅',
|
shareSubscription: '分享订阅',
|
||||||
|
|||||||
@@ -773,6 +773,8 @@ export default {
|
|||||||
originalTitle: '原始標題',
|
originalTitle: '原始標題',
|
||||||
status: '狀態',
|
status: '狀態',
|
||||||
releaseDate: '上映日期',
|
releaseDate: '上映日期',
|
||||||
|
digitalRelease: '數位發行',
|
||||||
|
physicalRelease: '實體發行',
|
||||||
originalLanguage: '原始語言',
|
originalLanguage: '原始語言',
|
||||||
productionCountries: '出品國家',
|
productionCountries: '出品國家',
|
||||||
productionCompanies: '製作公司',
|
productionCompanies: '製作公司',
|
||||||
@@ -1226,6 +1228,18 @@ export default {
|
|||||||
githubTokenHint: '用於提高插件等訪問Github API時的限流閾值,建議配置,否則插件可能無法正常使用',
|
githubTokenHint: '用於提高插件等訪問Github API時的限流閾值,建議配置,否則插件可能無法正常使用',
|
||||||
ocrHost: '驗證碼識別服務器',
|
ocrHost: '驗證碼識別服務器',
|
||||||
ocrHostHint: '用於站點簽到、更新站點Cookie等識別驗證碼',
|
ocrHostHint: '用於站點簽到、更新站點Cookie等識別驗證碼',
|
||||||
|
aiAgent: '啟用智能助手',
|
||||||
|
aiAgentEnable: '啟用智能助手',
|
||||||
|
aiAgentEnableHint: '啟用後可使用智能助手功能,需要配置LLM相關參數',
|
||||||
|
llmProvider: 'LLM提供商',
|
||||||
|
llmProviderHint: '選擇使用的LLM服務提供商',
|
||||||
|
llmModel: 'LLM模型名稱',
|
||||||
|
llmModelHint: '指定使用的LLM模型,如gpt-3.5-turbo、deepseek-chat等',
|
||||||
|
llmApiKey: 'LLM API密鑰',
|
||||||
|
llmApiKeyHint: 'LLM服務提供商的API密鑰,用於身份驗證',
|
||||||
|
llmApiKeyPlaceholder: '請輸入API密鑰',
|
||||||
|
llmBaseUrl: 'LLM基礎URL',
|
||||||
|
llmBaseUrlHint: 'LLM API的基礎URL地址,用於自定義API端點',
|
||||||
advancedSettings: '高級設置',
|
advancedSettings: '高級設置',
|
||||||
advancedSettingsDesc: '系統進階設置,特殊情況下才需要調整',
|
advancedSettingsDesc: '系統進階設置,特殊情況下才需要調整',
|
||||||
downloaders: '下載器',
|
downloaders: '下載器',
|
||||||
@@ -1862,6 +1876,8 @@ export default {
|
|||||||
startDownload: '開始下載',
|
startDownload: '開始下載',
|
||||||
downloadSuccess: '{site} {title} 下載成功!',
|
downloadSuccess: '{site} {title} 下載成功!',
|
||||||
downloadFailed: '{site} {title} 下載失敗:{message}!',
|
downloadFailed: '{site} {title} 下載失敗:{message}!',
|
||||||
|
showAdvancedOptions: '顯示高級選項',
|
||||||
|
hideAdvancedOptions: '隱藏高級選項',
|
||||||
},
|
},
|
||||||
subscribeShare: {
|
subscribeShare: {
|
||||||
shareSubscription: '分享訂閱',
|
shareSubscription: '分享訂閱',
|
||||||
|
|||||||
@@ -428,6 +428,17 @@ const getProductionCompanies = computed(() => {
|
|||||||
return mediaDetail.value.production_companies?.map(company => company.name)
|
return mediaDetail.value.production_companies?.map(company => company.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取最早实体/数字发行日期
|
||||||
|
const getEarliestReleaseDate = computed(() => {
|
||||||
|
const filteredDates = mediaDetail.value.release_dates?.filter(date => [4, 5].includes(date.type))
|
||||||
|
if (!filteredDates || filteredDates.length === 0)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return filteredDates.reduce((earliest, current) =>
|
||||||
|
new Date(current.date) < new Date(earliest.date) ? current : earliest,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// 计算存在状态的颜色
|
// 计算存在状态的颜色
|
||||||
function getExistColor(season: number) {
|
function getExistColor(season: number) {
|
||||||
const state = seasonsNotExisted.value[season]
|
const state = seasonsNotExisted.value[season]
|
||||||
@@ -840,6 +851,17 @@ onBeforeMount(() => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="mediaDetail.type === '电影' && getEarliestReleaseDate" class="media-fact">
|
||||||
|
<span>{{ t(getEarliestReleaseDate.type === 4 ? 'media.info.digitalRelease' : 'media.info.physicalRelease') }}</span>
|
||||||
|
<span class="media-fact-value">
|
||||||
|
<span class="flex items-center justify-end">
|
||||||
|
<span class="inline-flex items-center justify-center h-4 w-4 text-[0.6rem] font-bold text-current border border-current leading-none">
|
||||||
|
{{ getEarliestReleaseDate.iso_code }}
|
||||||
|
</span>
|
||||||
|
<span class="ml-1.5">{{ getEarliestReleaseDate.date.slice(0, 10) }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div v-if="mediaDetail.original_language" class="media-fact">
|
<div v-if="mediaDetail.original_language" class="media-fact">
|
||||||
<span>{{ t('media.info.originalLanguage') }}</span>
|
<span>{{ t('media.info.originalLanguage') }}</span>
|
||||||
<span class="media-fact-value">{{ mediaDetail.original_language }}</span>
|
<span class="media-fact-value">{{ mediaDetail.original_language }}</span>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ const SystemSettings = ref<any>({
|
|||||||
GITHUB_TOKEN: null,
|
GITHUB_TOKEN: null,
|
||||||
OCR_HOST: null,
|
OCR_HOST: null,
|
||||||
CUSTOMIZE_WALLPAPER_API_URL: null,
|
CUSTOMIZE_WALLPAPER_API_URL: null,
|
||||||
|
AI_AGENT_ENABLE: false,
|
||||||
|
LLM_PROVIDER: 'deepseek',
|
||||||
|
LLM_MODEL: 'deepseek-chat',
|
||||||
|
LLM_API_KEY: null,
|
||||||
|
LLM_BASE_URL: 'https://api.deepseek.com',
|
||||||
},
|
},
|
||||||
// 高级系统设置
|
// 高级系统设置
|
||||||
Advanced: {
|
Advanced: {
|
||||||
@@ -114,6 +119,10 @@ const progressDialog = ref(false)
|
|||||||
// 高级设置对话框
|
// 高级设置对话框
|
||||||
const advancedDialog = ref(false)
|
const advancedDialog = ref(false)
|
||||||
|
|
||||||
|
// LLM 模型列表
|
||||||
|
const llmModels = ref<string[]>([])
|
||||||
|
const loadingModels = ref(false)
|
||||||
|
|
||||||
const activeTab = ref('system')
|
const activeTab = ref('system')
|
||||||
|
|
||||||
// 元数据语言
|
// 元数据语言
|
||||||
@@ -149,6 +158,30 @@ const logLevelItems = [
|
|||||||
// 安全域名添加变量
|
// 安全域名添加变量
|
||||||
const newSecurityDomain = ref('')
|
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() {
|
function addSecurityDomain() {
|
||||||
if (
|
if (
|
||||||
@@ -607,6 +640,74 @@ onDeactivated(() => {
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
<VDivider class="my-4" />
|
||||||
|
<VRow>
|
||||||
|
<VCol cols="12">
|
||||||
|
<VSwitch
|
||||||
|
v-model="SystemSettings.Basic.AI_AGENT_ENABLE"
|
||||||
|
:label="t('setting.system.aiAgentEnable')"
|
||||||
|
:hint="t('setting.system.aiAgentEnableHint')"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||||
|
<VSelect
|
||||||
|
v-model="SystemSettings.Basic.LLM_PROVIDER"
|
||||||
|
:label="t('setting.system.llmProvider')"
|
||||||
|
:hint="t('setting.system.llmProviderHint')"
|
||||||
|
persistent-hint
|
||||||
|
:items="[
|
||||||
|
{ title: 'OpenAI', value: 'openai' },
|
||||||
|
{ title: 'Google', value: 'google' },
|
||||||
|
{ title: 'DeepSeek', value: 'deepseek' },
|
||||||
|
]"
|
||||||
|
prepend-inner-icon="mdi-robot"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
persistent-hint
|
||||||
|
prepend-inner-icon="mdi-link"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="SystemSettings.Basic.LLM_API_KEY"
|
||||||
|
:label="t('setting.system.llmApiKey')"
|
||||||
|
:hint="t('setting.system.llmApiKeyHint')"
|
||||||
|
:placeholder="t('setting.system.llmApiKeyPlaceholder')"
|
||||||
|
persistent-hint
|
||||||
|
type="password"
|
||||||
|
prepend-inner-icon="mdi-key-variant"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol v-if="SystemSettings.Basic.AI_AGENT_ENABLE" cols="12" md="6">
|
||||||
|
<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>
|
||||||
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
Reference in New Issue
Block a user