mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
1084 lines
36 KiB
Vue
1084 lines
36 KiB
Vue
<template>
|
||
<div class="session-management-page">
|
||
<v-container fluid class="pa-0">
|
||
<v-row class="d-flex justify-space-between align-center px-4 py-3 pb-8">
|
||
<div>
|
||
<h1 class="text-h1 font-weight-bold mb-2" style="display: flex; align-items: center;">
|
||
<v-icon color="black" class="me-2">mdi-account-group</v-icon>{{ tm('title') }}
|
||
</h1>
|
||
<p class="text-subtitle-1 text-medium-emphasis mb-4">
|
||
{{ subtitle }}
|
||
</p>
|
||
</div>
|
||
<v-btn
|
||
color="primary"
|
||
prepend-icon="mdi-refresh"
|
||
variant="tonal"
|
||
@click="refreshSessions"
|
||
:loading="loading"
|
||
rounded="xl"
|
||
size="x-large"
|
||
>
|
||
{{ tm('buttons.refresh') }}
|
||
</v-btn>
|
||
</v-row>
|
||
|
||
<v-card elevation="0" rounded="12" class="session-card">
|
||
<v-card-title class="bg-primary text-white py-3 px-4 session-card-title">
|
||
<v-icon color="white" class="me-2">mdi-account-group</v-icon>
|
||
<span class="text-h4 mr-4">{{ tm('sessions.activeSessions') }}</span>
|
||
<v-chip color="white" text-color="primary" small>
|
||
{{ sessions.length }} {{ tm('sessions.sessionCount') }}
|
||
</v-chip>
|
||
</v-card-title>
|
||
|
||
<v-card-text class="pa-0">
|
||
<!-- 搜索栏 -->
|
||
<v-toolbar flat class="px-4">
|
||
<v-text-field
|
||
v-model="searchQuery"
|
||
prepend-inner-icon="mdi-magnify"
|
||
:label="tm('search.placeholder')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
class="me-4"
|
||
density="compact"
|
||
></v-text-field>
|
||
|
||
<v-select
|
||
v-model="filterPlatform"
|
||
:items="platformOptions"
|
||
:label="tm('search.platformFilter')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
class="me-4"
|
||
style="max-width: 150px;"
|
||
density="compact"
|
||
></v-select>
|
||
</v-toolbar>
|
||
|
||
<v-divider></v-divider>
|
||
|
||
<!-- 会话列表 -->
|
||
<v-data-table
|
||
:headers="headers"
|
||
:items="filteredSessions"
|
||
:loading="loading"
|
||
:items-per-page="itemsPerPage"
|
||
class="elevation-0"
|
||
>
|
||
<!-- 会话信息 -->
|
||
<template v-slot:item.session_info="{ item }">
|
||
<div class="py-2">
|
||
<div class="d-flex align-center">
|
||
<div class="flex-grow-1">
|
||
<div class="font-weight-medium d-flex align-center">
|
||
<v-tooltip
|
||
location="top"
|
||
>
|
||
<template v-slot:activator="{ props: tooltipProps }">
|
||
<span v-bind="tooltipProps">{{ item.session_name }}</span>
|
||
</template>
|
||
使用 /sid 指令可查看会话 ID。
|
||
</v-tooltip>
|
||
|
||
<v-tooltip
|
||
v-if="item.session_name !== item.session_raw_name"
|
||
activator="parent"
|
||
location="top"
|
||
>
|
||
<span class="text-caption">实际UMO: {{ item.session_raw_name }}</span>
|
||
</v-tooltip>
|
||
<v-icon
|
||
v-if="item.session_name !== item.session_raw_name"
|
||
size="12"
|
||
color="warning"
|
||
class="ml-1"
|
||
>
|
||
mdi-information-outline
|
||
</v-icon>
|
||
</div>
|
||
<div class="text-caption text-grey-600">
|
||
<v-chip
|
||
:color="getPlatformColor(item.platform)"
|
||
size="x-small"
|
||
class="me-1"
|
||
>
|
||
{{ item.platform }}
|
||
</v-chip>
|
||
{{ item.message_type }}
|
||
</div>
|
||
</div>
|
||
<v-btn
|
||
icon
|
||
size="small"
|
||
variant="text"
|
||
color="primary"
|
||
@click="openNameEditor(item)"
|
||
:loading="item.updating"
|
||
>
|
||
<v-icon size="16">mdi-pencil</v-icon>
|
||
<v-tooltip activator="parent" location="top">
|
||
{{ tm('buttons.editName') }}
|
||
</v-tooltip>
|
||
</v-btn>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 人格 -->
|
||
<template v-slot:item.persona="{ item }">
|
||
<v-select
|
||
:model-value="item.persona_id || ''"
|
||
:items="personaOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
@update:model-value="(value) => updatePersona(item, value)"
|
||
:loading="item.updating"
|
||
:disabled="!item.session_enabled"
|
||
>
|
||
<template v-slot:selection="{ item: selection }">
|
||
<v-chip
|
||
size="small"
|
||
:color="selection.raw.value === '[%None]' ? 'grey' : 'primary'"
|
||
>
|
||
{{ selection.raw.label }}
|
||
</v-chip>
|
||
</template>
|
||
</v-select>
|
||
</template>
|
||
|
||
<!-- Chat Provider -->
|
||
<template v-slot:item.chat_provider="{ item }">
|
||
<v-select
|
||
:model-value="item.chat_provider_id || ''"
|
||
:items="chatProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
@update:model-value="(value) => updateProvider(item, value, 'chat_completion')"
|
||
:loading="item.updating"
|
||
:disabled="!item.session_enabled"
|
||
>
|
||
<template v-slot:selection="{ item: selection }">
|
||
<v-chip size="small" color="success">
|
||
{{ selection.raw.label }}
|
||
</v-chip>
|
||
</template>
|
||
</v-select>
|
||
</template>
|
||
|
||
<!-- STT Provider -->
|
||
<template v-slot:item.stt_provider="{ item }">
|
||
<v-select
|
||
:model-value="item.stt_provider_id || ''"
|
||
:items="sttProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
@update:model-value="(value) => updateProvider(item, value, 'speech_to_text')"
|
||
:loading="item.updating"
|
||
:disabled="sttProviderOptions.length === 0 || !item.session_enabled"
|
||
>
|
||
<template v-slot:selection="{ item: selection }">
|
||
<v-chip size="small" color="info">
|
||
{{ selection.raw.label }}
|
||
</v-chip>
|
||
</template>
|
||
</v-select>
|
||
</template>
|
||
|
||
<!-- TTS Provider -->
|
||
<template v-slot:item.tts_provider="{ item }">
|
||
<v-select
|
||
:model-value="item.tts_provider_id || ''"
|
||
:items="ttsProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
@update:model-value="(value) => updateProvider(item, value, 'text_to_speech')"
|
||
:loading="item.updating"
|
||
:disabled="ttsProviderOptions.length === 0 || !item.session_enabled"
|
||
>
|
||
<template v-slot:selection="{ item: selection }">
|
||
<v-chip size="small" color="warning">
|
||
{{ selection.raw.label }}
|
||
</v-chip>
|
||
</template>
|
||
</v-select> </template>
|
||
|
||
<!-- 会话启停 -->
|
||
<template v-slot:item.session_enabled="{ item }">
|
||
<v-switch
|
||
:model-value="item.session_enabled"
|
||
@update:model-value="(value) => updateSessionStatus(item, value)"
|
||
:loading="item.updating"
|
||
hide-details
|
||
density="compact"
|
||
color="success"
|
||
inset
|
||
>
|
||
</v-switch>
|
||
</template>
|
||
|
||
<!-- LLM启停 -->
|
||
<template v-slot:item.llm_enabled="{ item }">
|
||
<v-switch
|
||
:model-value="item.llm_enabled"
|
||
@update:model-value="(value) => updateLLM(item, value)"
|
||
:loading="item.updating"
|
||
:disabled="!item.session_enabled"
|
||
hide-details
|
||
density="compact"
|
||
color="primary"
|
||
inset
|
||
>
|
||
</v-switch>
|
||
</template>
|
||
|
||
<!-- TTS启停 -->
|
||
<template v-slot:item.tts_enabled="{ item }">
|
||
<v-switch
|
||
:model-value="item.tts_enabled"
|
||
@update:model-value="(value) => updateTTS(item, value)"
|
||
:loading="item.updating"
|
||
:disabled="!item.session_enabled"
|
||
hide-details
|
||
density="compact"
|
||
color="secondary"
|
||
inset
|
||
>
|
||
</v-switch>
|
||
</template>
|
||
|
||
<!-- 插件管理 -->
|
||
<template v-slot:item.plugins="{ item }">
|
||
<v-btn
|
||
size="small"
|
||
variant="outlined"
|
||
color="primary"
|
||
@click="openPluginManager(item)"
|
||
:loading="item.loadingPlugins"
|
||
:disabled="!item.session_enabled"
|
||
>
|
||
{{ tm('buttons.edit') }}
|
||
</v-btn>
|
||
</template>
|
||
|
||
<!-- 空状态 -->
|
||
<template v-slot:no-data>
|
||
<div class="text-center py-8">
|
||
<v-icon size="64" color="grey-400">mdi-account-group-outline</v-icon>
|
||
<div class="text-h6 mt-4 text-grey-600">{{ tm('sessions.noActiveSessions') }}</div>
|
||
<div class="text-body-2 text-grey-500">{{ tm('sessions.noActiveSessionsDesc') }}</div>
|
||
</div>
|
||
</template>
|
||
</v-data-table>
|
||
</v-card-text>
|
||
</v-card>
|
||
|
||
<!-- 批量操作面板 -->
|
||
<v-card elevation="0" class="mt-4 batch-operations-card" rounded="12">
|
||
<v-card-title class="bg-secondary text-white py-3 px-4">
|
||
<v-icon color="white" class="me-2">mdi-cog-outline</v-icon>
|
||
<span class="text-h4">{{ tm('batchOperations.title') }}</span>
|
||
</v-card-title>
|
||
|
||
<v-card-text>
|
||
<div style="padding: 16px;">
|
||
<v-row>
|
||
<v-col cols="12" md="6" lg="3" v-if="availablePersonas.length > 0">
|
||
<v-select
|
||
v-model="batchPersona"
|
||
:items="personaOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setPersona')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
></v-select>
|
||
</v-col>
|
||
|
||
<v-col cols="12" md="6" lg="3" v-if="availableChatProviders.length > 0">
|
||
<v-select
|
||
v-model="batchChatProvider"
|
||
:items="chatProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setChatProvider')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
></v-select>
|
||
</v-col>
|
||
|
||
<v-col cols="12" md="6" lg="3">
|
||
<v-select
|
||
v-model="batchSttProvider"
|
||
:items="sttProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setSttProvider')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
:disabled="availableSttProviders.length === 0"
|
||
:placeholder="availableSttProviders.length === 0 ? tm('batchOperations.noSttProvider') : ''"
|
||
></v-select>
|
||
</v-col>
|
||
|
||
<v-col cols="12" md="6" lg="3">
|
||
<v-select
|
||
v-model="batchTtsProvider"
|
||
:items="ttsProviderOptions"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setTtsProvider')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
:disabled="availableTtsProviders.length === 0"
|
||
:placeholder="availableTtsProviders.length === 0 ? tm('batchOperations.noTtsProvider') : ''"
|
||
></v-select>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<v-row>
|
||
<v-col cols="12" md="6" lg="3">
|
||
<v-select
|
||
v-model="batchLlmStatus"
|
||
:items="[{label: tm('status.enabled'), value: true}, {label: tm('status.disabled'), value: false}]"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setLlmStatus')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
></v-select>
|
||
</v-col>
|
||
|
||
<v-col cols="12" md="6" lg="3">
|
||
<v-select
|
||
v-model="batchTtsStatus"
|
||
:items="[{label: tm('status.enabled'), value: true}, {label: tm('status.disabled'), value: false}]"
|
||
item-title="label"
|
||
item-value="value"
|
||
:label="tm('batchOperations.setTtsStatus')"
|
||
hide-details
|
||
clearable
|
||
variant="outlined"
|
||
density="comfortable"
|
||
class="batch-select"
|
||
></v-select>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<div class="d-flex justify-end align-center mt-8">
|
||
<v-btn
|
||
color="primary"
|
||
variant="tonal"
|
||
size="large"
|
||
rounded="lg"
|
||
@click="applyBatchChanges"
|
||
:disabled="!batchPersona && !batchChatProvider && !batchSttProvider && !batchTtsProvider && batchLlmStatus === null && batchTtsStatus === null"
|
||
:loading="batchUpdating"
|
||
class="me-3"
|
||
>
|
||
<v-icon start>mdi-check-all</v-icon>
|
||
{{ tm('buttons.apply') }}
|
||
</v-btn>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</v-card-text>
|
||
</v-card>
|
||
|
||
<!-- 插件管理对话框 -->
|
||
<v-dialog v-model="pluginDialog" max-width="800" min-height="80%">
|
||
<v-card v-if="selectedSessionForPlugin">
|
||
<v-card-title class="bg-primary text-white py-3 px-4" style="display: flex; align-items: center;">
|
||
<v-icon color="white" class="me-2">mdi-puzzle</v-icon>
|
||
<span>{{ tm('pluginManagement.title') }} - {{ selectedSessionForPlugin.session_name }}</span>
|
||
<v-spacer></v-spacer>
|
||
<v-btn icon variant="text" color="white" @click="pluginDialog = false">
|
||
<v-icon>mdi-close</v-icon>
|
||
</v-btn>
|
||
</v-card-title>
|
||
|
||
<v-card-text v-if="!loadingPlugins">
|
||
<div style="padding-left: 16px; padding-right: 16px;">
|
||
<div v-if="sessionPlugins.length === 0" class="text-center py-8">
|
||
<v-icon size="64" color="grey-400">mdi-puzzle-outline</v-icon>
|
||
<div class="text-h6 mt-4 text-grey-600">{{ tm('pluginManagement.noPlugins') }}</div>
|
||
<div class="text-body-2 text-grey-500">{{ tm('pluginManagement.noPluginsDesc') }}</div>
|
||
</div>
|
||
|
||
<v-list v-else>
|
||
<v-list-item
|
||
v-for="plugin in sessionPlugins"
|
||
:key="plugin.name"
|
||
class="px-0"
|
||
>
|
||
<template v-slot:prepend>
|
||
<v-icon :color="plugin.enabled ? 'success' : 'grey'">
|
||
{{ plugin.enabled ? 'mdi-check-circle' : 'mdi-circle-outline' }}
|
||
</v-icon>
|
||
</template>
|
||
|
||
<v-list-item-title class="font-weight-medium">
|
||
{{ plugin.name }}
|
||
</v-list-item-title>
|
||
|
||
<v-list-item-subtitle>
|
||
{{ tm('pluginManagement.author') }}: {{ plugin.author }}
|
||
</v-list-item-subtitle>
|
||
|
||
<template v-slot:append>
|
||
<v-switch
|
||
:model-value="plugin.enabled"
|
||
hide-details
|
||
color="primary"
|
||
@update:model-value="(value) => togglePlugin(plugin, value)"
|
||
:loading="plugin.updating"
|
||
></v-switch>
|
||
</template>
|
||
</v-list-item>
|
||
</v-list>
|
||
</div>
|
||
|
||
</v-card-text>
|
||
|
||
<v-card-text v-else class="text-center py-8">
|
||
<v-progress-circular indeterminate color="primary" size="48"></v-progress-circular>
|
||
<div class="text-body-1 mt-4">{{ tm('pluginManagement.loading') }}</div>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<!-- 会话命名编辑对话框 -->
|
||
<v-dialog v-model="nameEditDialog" max-width="500" min-height="60%">
|
||
<v-card v-if="selectedSessionForName">
|
||
<v-card-title class="bg-primary text-white py-3 px-4" style="display: flex; align-items: center;">
|
||
<v-icon color="white" class="me-2">mdi-rename-box</v-icon>
|
||
<span>{{ tm('nameEditor.title') }}</span>
|
||
<v-spacer></v-spacer>
|
||
<v-btn icon variant="text" color="white" @click="nameEditDialog = false">
|
||
<v-icon>mdi-close</v-icon>
|
||
</v-btn>
|
||
</v-card-title>
|
||
|
||
<v-card-text>
|
||
<div style="padding-left: 16px; padding-right: 16px;">
|
||
<v-text-field
|
||
v-model="newSessionName"
|
||
:label="tm('nameEditor.customName')"
|
||
:placeholder="tm('nameEditor.placeholder')"
|
||
variant="outlined"
|
||
hide-details="auto"
|
||
clearable
|
||
class="mb-4"
|
||
@keyup.enter="saveSessionName"
|
||
></v-text-field>
|
||
|
||
<div class="text-caption text-grey-600 mb-2">
|
||
{{ tm('nameEditor.originalName') }}: {{ selectedSessionForName.session_raw_name }}
|
||
</div>
|
||
|
||
<div class="text-caption text-grey-600 mb-2">
|
||
{{ tm('nameEditor.fullSessionId') }}: {{ selectedSessionForName.session_id }}
|
||
</div>
|
||
|
||
<v-alert
|
||
variant="tonal"
|
||
type="info"
|
||
density="compact"
|
||
class="mb-4"
|
||
>
|
||
{{ tm('nameEditor.hint') }}
|
||
</v-alert>
|
||
</div>
|
||
</v-card-text>
|
||
|
||
<v-card-actions class="px-4 pb-4">
|
||
<v-spacer></v-spacer>
|
||
<v-btn
|
||
color="grey"
|
||
variant="text"
|
||
@click="nameEditDialog = false"
|
||
:disabled="nameEditLoading"
|
||
>
|
||
{{ tm('buttons.cancel') }}
|
||
</v-btn>
|
||
<v-btn
|
||
color="primary"
|
||
variant="tonal"
|
||
@click="saveSessionName"
|
||
:loading="nameEditLoading"
|
||
>
|
||
{{ tm('buttons.save') }}
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<!-- 提示信息 -->
|
||
<v-snackbar v-model="snackbar" :timeout="3000" elevation="24" :color="snackbarColor" location="top">
|
||
{{ snackbarText }}
|
||
</v-snackbar>
|
||
</v-container>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import axios from 'axios'
|
||
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
||
|
||
export default {
|
||
name: 'SessionManagementPage',
|
||
setup() {
|
||
const { t } = useI18n()
|
||
const { tm } = useModuleI18n('features/session-management')
|
||
|
||
return {
|
||
t,
|
||
tm
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
sessions: [],
|
||
searchQuery: '',
|
||
filterPlatform: null,
|
||
|
||
// 分页相关
|
||
itemsPerPage: 10,
|
||
|
||
// 可用选项
|
||
availablePersonas: [],
|
||
availableChatProviders: [],
|
||
availableSttProviders: [],
|
||
availableTtsProviders: [],
|
||
|
||
// 批量操作
|
||
batchPersona: null,
|
||
batchChatProvider: null,
|
||
batchSttProvider: null,
|
||
batchTtsProvider: null,
|
||
batchLlmStatus: null,
|
||
batchTtsStatus: null,
|
||
batchUpdating: false,
|
||
|
||
// 插件管理
|
||
pluginDialog: false,
|
||
selectedSessionForPlugin: null,
|
||
sessionPlugins: [],
|
||
loadingPlugins: false,
|
||
|
||
// 会话命名编辑器
|
||
nameEditDialog: false,
|
||
selectedSessionForName: null,
|
||
newSessionName: '',
|
||
nameEditLoading: false,
|
||
|
||
// 提示信息
|
||
snackbar: false,
|
||
snackbarText: '',
|
||
snackbarColor: 'success',
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
// 安全访问翻译的计算属性
|
||
messages() {
|
||
return {
|
||
updateSuccess: this.tm('messages.updateSuccess'),
|
||
addSuccess: this.tm('messages.addSuccess'),
|
||
deleteSuccess: this.tm('messages.deleteSuccess'),
|
||
statusUpdateSuccess: this.tm('messages.statusUpdateSuccess'),
|
||
deleteConfirm: this.tm('messages.deleteConfirm')
|
||
};
|
||
},
|
||
|
||
subtitle() {
|
||
return this.tm('subtitle') || '管理所有活跃的会话,配置人格、LLM提供商和插件';
|
||
},
|
||
|
||
headers() {
|
||
return [
|
||
{ title: this.tm('table.headers.sessionStatus'), key: 'session_enabled', sortable: false, width: '120px' },
|
||
{ title: this.tm('table.headers.sessionInfo'), key: 'session_info', sortable: false, width: '200px' },
|
||
{ title: this.tm('table.headers.persona'), key: 'persona', sortable: false, width: '180px' },
|
||
{ title: this.tm('table.headers.chatProvider'), key: 'chat_provider', sortable: false, width: '180px' },
|
||
{ title: this.tm('table.headers.sttProvider'), key: 'stt_provider', sortable: false, width: '150px' },
|
||
{ title: this.tm('table.headers.ttsProvider'), key: 'tts_provider', sortable: false, width: '150px' },
|
||
{ title: this.tm('table.headers.llmStatus'), key: 'llm_enabled', sortable: false, width: '120px' },
|
||
{ title: this.tm('table.headers.ttsStatus'), key: 'tts_enabled', sortable: false, width: '120px' },
|
||
{ title: this.tm('table.headers.pluginManagement'), key: 'plugins', sortable: false, width: '120px' },
|
||
]
|
||
},
|
||
|
||
// 懒加载过滤会话 - 使用客户端分页
|
||
filteredSessions() {
|
||
let filtered = this.sessions;
|
||
|
||
// 搜索筛选
|
||
if (this.searchQuery) {
|
||
const query = this.searchQuery.toLowerCase();
|
||
filtered = filtered.filter(session =>
|
||
session.session_name.toLowerCase().includes(query) ||
|
||
session.platform.toLowerCase().includes(query) ||
|
||
session.persona_name?.toLowerCase().includes(query) ||
|
||
session.chat_provider_name?.toLowerCase().includes(query)
|
||
);
|
||
}
|
||
|
||
// 平台筛选
|
||
if (this.filterPlatform) {
|
||
filtered = filtered.filter(session => session.platform === this.filterPlatform);
|
||
}
|
||
|
||
return filtered;
|
||
},
|
||
|
||
platformOptions() {
|
||
const platforms = [...new Set(this.sessions.map(s => s.platform))];
|
||
return platforms.map(p => ({ title: p, value: p }));
|
||
},
|
||
|
||
personaOptions() {
|
||
const options = [
|
||
{ label: this.tm('persona.none'), value: '[%None]' },
|
||
...this.availablePersonas.map(p => ({
|
||
label: p.name,
|
||
value: p.name
|
||
}))
|
||
];
|
||
return options;
|
||
},
|
||
|
||
chatProviderOptions() {
|
||
return this.availableChatProviders.map(p => ({
|
||
label: `${p.name} (${p.model})`,
|
||
value: p.id
|
||
}));
|
||
},
|
||
|
||
sttProviderOptions() {
|
||
return this.availableSttProviders.map(p => ({
|
||
label: `${p.name} (${p.model})`,
|
||
value: p.id
|
||
}));
|
||
},
|
||
|
||
ttsProviderOptions() {
|
||
return this.availableTtsProviders.map(p => ({
|
||
label: `${p.name} (${p.model})`,
|
||
value: p.id
|
||
}));
|
||
},
|
||
},
|
||
|
||
mounted() {
|
||
this.loadSessions();
|
||
},
|
||
|
||
methods: {
|
||
async loadSessions() {
|
||
this.loading = true;
|
||
try {
|
||
const response = await axios.get('/api/session/list');
|
||
if (response.data.status === 'ok') {
|
||
const data = response.data.data;
|
||
this.sessions = data.sessions.map(session => ({
|
||
...session,
|
||
updating: false, // 添加更新状态标志
|
||
loadingPlugins: false // 添加插件加载状态标志
|
||
}));
|
||
this.availablePersonas = data.available_personas;
|
||
this.availableChatProviders = data.available_chat_providers;
|
||
this.availableSttProviders = data.available_stt_providers;
|
||
this.availableTtsProviders = data.available_tts_providers;
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.loadSessionsError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.loadSessionsError'));
|
||
}
|
||
this.loading = false;
|
||
},
|
||
|
||
async refreshSessions() {
|
||
await this.loadSessions();
|
||
this.showSuccess(this.tm('messages.refreshSuccess'));
|
||
},
|
||
|
||
async updatePersona(session, personaName) {
|
||
session.updating = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_persona', {
|
||
session_id: session.session_id,
|
||
persona_name: personaName
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
session.persona_id = personaName;
|
||
session.persona_name = personaName === '[%None]' ? this.tm('persona.none') :
|
||
this.availablePersonas.find(p => p.name === personaName)?.name || personaName;
|
||
this.showSuccess(this.tm('messages.personaUpdateSuccess'));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.personaUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.personaUpdateError'));
|
||
}
|
||
session.updating = false;
|
||
},
|
||
|
||
async updateProvider(session, providerId, providerType) {
|
||
session.updating = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_provider', {
|
||
session_id: session.session_id,
|
||
provider_id: providerId,
|
||
provider_type: providerType
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
// 更新本地数据
|
||
if (providerType === 'chat_completion') {
|
||
session.chat_provider_id = providerId;
|
||
const provider = this.availableChatProviders.find(p => p.id === providerId);
|
||
session.chat_provider_name = provider?.name || providerId;
|
||
} else if (providerType === 'speech_to_text') {
|
||
session.stt_provider_id = providerId;
|
||
const provider = this.availableSttProviders.find(p => p.id === providerId);
|
||
session.stt_provider_name = provider?.name || providerId;
|
||
} else if (providerType === 'text_to_speech') {
|
||
session.tts_provider_id = providerId;
|
||
const provider = this.availableTtsProviders.find(p => p.id === providerId);
|
||
session.tts_provider_name = provider?.name || providerId;
|
||
}
|
||
this.showSuccess(this.tm('messages.providerUpdateSuccess'));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.providerUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.providerUpdateError'));
|
||
} session.updating = false;
|
||
},
|
||
|
||
async updateSessionStatus(session, enabled) {
|
||
session.updating = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_status', {
|
||
session_id: session.session_id,
|
||
session_enabled: enabled
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
session.session_enabled = enabled;
|
||
this.showSuccess(this.tm('messages.sessionStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
session.updating = false;
|
||
},
|
||
|
||
async updateLLM(session, enabled) {
|
||
session.updating = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_llm', {
|
||
session_id: session.session_id,
|
||
enabled: enabled
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
session.llm_enabled = enabled;
|
||
this.showSuccess(this.tm('messages.llmStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
session.updating = false;
|
||
},
|
||
|
||
async updateTTS(session, enabled) {
|
||
session.updating = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_tts', {
|
||
session_id: session.session_id,
|
||
enabled: enabled
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
session.tts_enabled = enabled;
|
||
this.showSuccess(this.tm('messages.ttsStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
|
||
}
|
||
session.updating = false;
|
||
},
|
||
|
||
async applyBatchChanges() {
|
||
if (!this.batchPersona && !this.batchChatProvider && !this.batchSttProvider && !this.batchTtsProvider && this.batchLlmStatus === null && this.batchTtsStatus === null) {
|
||
return;
|
||
}
|
||
|
||
this.batchUpdating = true;
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
// 使用过滤后的会话数据进行批量操作
|
||
for (const session of this.filteredSessions) {
|
||
try {
|
||
// 批量更新人格
|
||
if (this.batchPersona) {
|
||
await this.updatePersona(session, this.batchPersona);
|
||
successCount++;
|
||
}
|
||
|
||
// 批量更新 Chat Provider
|
||
if (this.batchChatProvider) {
|
||
await this.updateProvider(session, this.batchChatProvider, 'chat_completion');
|
||
successCount++;
|
||
}
|
||
|
||
// 批量更新 STT Provider
|
||
if (this.batchSttProvider) {
|
||
await this.updateProvider(session, this.batchSttProvider, 'speech_to_text');
|
||
successCount++;
|
||
}
|
||
|
||
// 批量更新 TTS Provider
|
||
if (this.batchTtsProvider) {
|
||
await this.updateProvider(session, this.batchTtsProvider, 'text_to_speech');
|
||
successCount++;
|
||
}
|
||
|
||
// 批量更新 LLM 状态
|
||
if (this.batchLlmStatus !== null) {
|
||
await this.updateLLM(session, this.batchLlmStatus);
|
||
successCount++;
|
||
}
|
||
|
||
// 批量更新 TTS 状态
|
||
if (this.batchTtsStatus !== null) {
|
||
await this.updateTTS(session, this.batchTtsStatus);
|
||
successCount++;
|
||
}
|
||
} catch (error) {
|
||
errorCount++;
|
||
}
|
||
}
|
||
|
||
this.batchUpdating = false;
|
||
|
||
if (errorCount === 0) {
|
||
this.showSuccess(this.tm('messages.batchUpdateSuccess', { count: successCount }));
|
||
} else {
|
||
this.showError(this.tm('messages.batchUpdatePartial', { success: successCount, error: errorCount }));
|
||
}
|
||
|
||
// 清空批量设置
|
||
this.batchPersona = null;
|
||
this.batchChatProvider = null;
|
||
this.batchSttProvider = null;
|
||
this.batchTtsProvider = null;
|
||
this.batchLlmStatus = null;
|
||
this.batchTtsStatus = null;
|
||
},
|
||
|
||
async openPluginManager(session) {
|
||
this.selectedSessionForPlugin = session;
|
||
this.pluginDialog = true;
|
||
this.loadingPlugins = true;
|
||
this.sessionPlugins = [];
|
||
|
||
try {
|
||
const response = await axios.get('/api/session/plugins', {
|
||
params: { session_id: session.session_id }
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
this.sessionPlugins = response.data.data.plugins.map(plugin => ({
|
||
...plugin,
|
||
updating: false
|
||
}));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.loadPluginsError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.loadPluginsError'));
|
||
}
|
||
|
||
this.loadingPlugins = false;
|
||
},
|
||
|
||
async togglePlugin(plugin, enabled) {
|
||
plugin.updating = true;
|
||
|
||
try {
|
||
const response = await axios.post('/api/session/update_plugin', {
|
||
session_id: this.selectedSessionForPlugin.session_id,
|
||
plugin_name: plugin.name,
|
||
enabled: enabled
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
plugin.enabled = enabled;
|
||
this.showSuccess(this.tm('messages.pluginStatusSuccess', {
|
||
name: plugin.name,
|
||
status: enabled ? this.tm('status.enabled') : this.tm('status.disabled')
|
||
}));
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.pluginStatusError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.pluginStatusError'));
|
||
}
|
||
|
||
plugin.updating = false;
|
||
},
|
||
|
||
openNameEditor(session) {
|
||
this.selectedSessionForName = session;
|
||
this.newSessionName = session.session_name === session.session_raw_name ? '' : session.session_name;
|
||
this.nameEditDialog = true;
|
||
},
|
||
|
||
async saveSessionName() {
|
||
if (!this.selectedSessionForName) return;
|
||
|
||
this.nameEditLoading = true;
|
||
try {
|
||
const response = await axios.post('/api/session/update_name', {
|
||
session_id: this.selectedSessionForName.session_id,
|
||
custom_name: this.newSessionName || ''
|
||
});
|
||
|
||
if (response.data.status === 'ok') {
|
||
// 更新本地数据
|
||
this.selectedSessionForName.session_name = response.data.data.display_name;
|
||
this.showSuccess(response.data.data.message || this.tm('messages.nameUpdateSuccess'));
|
||
this.nameEditDialog = false;
|
||
} else {
|
||
this.showError(response.data.message || this.tm('messages.nameUpdateError'));
|
||
}
|
||
} catch (error) {
|
||
this.showError(error.response?.data?.message || this.tm('messages.nameUpdateError'));
|
||
}
|
||
|
||
this.nameEditLoading = false;
|
||
},
|
||
|
||
getPlatformColor(platform) {
|
||
const colors = {
|
||
'aiocqhttp': 'blue',
|
||
'wechatpadpro': 'green',
|
||
'qq_official': 'purple',
|
||
'telegram': 'light-blue',
|
||
'discord': 'indigo',
|
||
'default': 'grey'
|
||
};
|
||
return colors[platform] || colors.default;
|
||
},
|
||
|
||
showSuccess(message) {
|
||
this.snackbarText = message;
|
||
this.snackbarColor = 'success';
|
||
this.snackbar = true;
|
||
},
|
||
|
||
showError(message) {
|
||
this.snackbarText = message;
|
||
this.snackbarColor = 'error';
|
||
this.snackbar = true;
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.session-management-page {
|
||
padding: 20px;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.v-data-table >>> .v-data-table__td {
|
||
padding: 8px 16px !important;
|
||
vertical-align: middle !important;
|
||
}
|
||
|
||
.v-data-table >>> .v-data-table__th {
|
||
vertical-align: middle !important;
|
||
}
|
||
|
||
.v-select >>> .v-field__input {
|
||
padding-top: 4px !important;
|
||
padding-bottom: 4px !important;
|
||
}
|
||
|
||
.session-card {
|
||
border-radius: 12px !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.session-card-title {
|
||
border-top-left-radius: 12px !important;
|
||
border-top-right-radius: 12px !important;
|
||
}
|
||
|
||
/* 对话框标题栏样式 */
|
||
.v-card .v-card-title {
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 12px;
|
||
}
|
||
|
||
/* 表格行悬停效果 */
|
||
.v-data-table >>> .v-data-table__tr:hover {
|
||
background-color: rgba(var(--v-theme-primary), 0.02) !important;
|
||
}
|
||
|
||
/* 工具栏样式改进 */
|
||
.v-toolbar {
|
||
background-color: rgba(var(--v-theme-surface), 0.8) !important;
|
||
}
|
||
|
||
/* 卡片内边距改进 */
|
||
.v-card-text {
|
||
padding: 16px 0 !important;
|
||
}
|
||
</style>
|