Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b781c83faa |
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.svg" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="keywords" content="AstrBot Soulter" />
|
<meta name="keywords" content="AstrBot Soulter" />
|
||||||
<meta name="description" content="AstrBot Dashboard" />
|
<meta name="description" content="AstrBot Dashboard" />
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
|||||||
@@ -37,14 +37,7 @@
|
|||||||
|
|
||||||
<!-- 正常聊天界面 -->
|
<!-- 正常聊天界面 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="conversation-header fade-in" v-if="isMobile">
|
|
||||||
<!-- 手机端菜单按钮 -->
|
|
||||||
<v-btn icon class="mobile-menu-btn" @click="toggleMobileSidebar" variant="text">
|
|
||||||
<v-icon>mdi-menu</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 面包屑导航 -->
|
|
||||||
<div v-if="currentSessionProject && messages && messages.length > 0" class="breadcrumb-container">
|
<div v-if="currentSessionProject && messages && messages.length > 0" class="breadcrumb-container">
|
||||||
<div class="breadcrumb-content">
|
<div class="breadcrumb-content">
|
||||||
<span class="breadcrumb-emoji">{{ currentSessionProject.emoji || '📁' }}</span>
|
<span class="breadcrumb-emoji">{{ currentSessionProject.emoji || '📁' }}</span>
|
||||||
@@ -241,6 +234,7 @@ const route = useRoute();
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { tm } = useModuleI18n('features/chat');
|
const { tm } = useModuleI18n('features/chat');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const customizer = useCustomizerStore();
|
||||||
|
|
||||||
// UI 状态
|
// UI 状态
|
||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
@@ -342,19 +336,28 @@ function checkMobile() {
|
|||||||
isMobile.value = window.innerWidth <= 768;
|
isMobile.value = window.innerWidth <= 768;
|
||||||
if (!isMobile.value) {
|
if (!isMobile.value) {
|
||||||
mobileMenuOpen.value = false;
|
mobileMenuOpen.value = false;
|
||||||
|
customizer.SET_CHAT_SIDEBAR(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMobileSidebar() {
|
function toggleMobileSidebar() {
|
||||||
mobileMenuOpen.value = !mobileMenuOpen.value;
|
mobileMenuOpen.value = !mobileMenuOpen.value;
|
||||||
|
customizer.SET_CHAT_SIDEBAR(mobileMenuOpen.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMobileSidebar() {
|
function closeMobileSidebar() {
|
||||||
mobileMenuOpen.value = false;
|
mobileMenuOpen.value = false;
|
||||||
|
customizer.SET_CHAT_SIDEBAR(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步 nav header 中的 sidebar toggle
|
||||||
|
watch(() => customizer.chatSidebarOpen, (val) => {
|
||||||
|
if (isMobile.value) {
|
||||||
|
mobileMenuOpen.value = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
const customizer = useCustomizerStore();
|
|
||||||
const newTheme = customizer.uiTheme === 'PurpleTheme' ? 'PurpleThemeDark' : 'PurpleTheme';
|
const newTheme = customizer.uiTheme === 'PurpleTheme' ? 'PurpleThemeDark' : 'PurpleTheme';
|
||||||
customizer.SET_UI_THEME(newTheme);
|
customizer.SET_UI_THEME(newTheme);
|
||||||
theme.global.name.value = newTheme;
|
theme.global.name.value = newTheme;
|
||||||
@@ -722,6 +725,7 @@ onBeforeUnmount(() => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
overscroll-behavior: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-page-container {
|
.chat-page-container {
|
||||||
|
|||||||
@@ -32,11 +32,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<textarea ref="inputField" v-model="localPrompt" @keydown="handleKeyDown" :disabled="disabled"
|
<textarea ref="inputField" v-model="localPrompt" @keydown="handleKeyDown" :disabled="disabled"
|
||||||
placeholder="Ask AstrBot..."
|
placeholder="Ask AstrBot..." class="chat-textarea"
|
||||||
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 12px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
autocomplete="off" autocorrect="off" autocapitalize="sentences" spellcheck="false"
|
||||||
|
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 16px 20px; min-height: 40px; max-height: 200px; overflow-y: auto; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 14px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 14px;">
|
||||||
<div
|
<div
|
||||||
style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
|
style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px; min-width: 0; flex: 1; overflow: hidden;">
|
||||||
<!-- Settings Menu -->
|
<!-- Settings Menu -->
|
||||||
<StyledMenu offset="8" location="top start" :close-on-content-click="false">
|
<StyledMenu offset="8" location="top start" :close-on-content-click="false">
|
||||||
<template v-slot:activator="{ props: activatorProps }">
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
@@ -72,9 +73,9 @@
|
|||||||
<!-- Provider/Model Selector Menu -->
|
<!-- Provider/Model Selector Menu -->
|
||||||
<ProviderModelMenu v-if="showProviderSelector" ref="providerModelMenuRef" />
|
<ProviderModelMenu v-if="showProviderSelector" ref="providerModelMenuRef" />
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center;">
|
<div style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center; flex-shrink: 0;">
|
||||||
<input type="file" ref="imageInputRef" @change="handleFileSelect" style="display: none" multiple />
|
<input type="file" ref="imageInputRef" @change="handleFileSelect" style="display: none" multiple />
|
||||||
<v-progress-circular v-if="disabled" indeterminate size="16" class="mr-1" width="1.5" />
|
<v-progress-circular v-if="disabled && !mobile" indeterminate size="16" class="mr-1" width="1.5" />
|
||||||
<!-- <v-btn @click="$emit('openLiveMode')"
|
<!-- <v-btn @click="$emit('openLiveMode')"
|
||||||
icon
|
icon
|
||||||
variant="text"
|
variant="text"
|
||||||
@@ -87,36 +88,21 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn> -->
|
</v-btn> -->
|
||||||
<v-btn @click="handleRecordClick" icon variant="text" :color="isRecording ? 'error' : 'deep-purple'"
|
<v-btn @click="handleRecordClick" icon variant="text" :color="isRecording ? 'error' : 'deep-purple'"
|
||||||
class="record-btn" size="small">
|
class="record-btn">
|
||||||
<v-icon :icon="isRecording ? 'mdi-stop-circle' : 'mdi-microphone'" variant="text"
|
<v-icon :icon="isRecording ? 'mdi-stop-circle' : 'mdi-microphone'" variant="text"
|
||||||
plain></v-icon>
|
plain></v-icon>
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ isRecording ? tm('voice.speaking') : tm('voice.startRecording') }}
|
{{ isRecording ? tm('voice.speaking') : tm('voice.startRecording') }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn icon v-if="isRunning" @click="$emit('stop')" variant="tonal" color="deep-purple" class="send-btn">
|
||||||
icon
|
|
||||||
v-if="isRunning"
|
|
||||||
@click="$emit('stop')"
|
|
||||||
variant="text"
|
|
||||||
class="send-btn"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<v-icon icon="mdi-stop" variant="text" plain></v-icon>
|
<v-icon icon="mdi-stop" variant="text" plain></v-icon>
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ tm('input.stopGenerating') }}
|
{{ tm('input.stopGenerating') }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn v-else @click="$emit('send')" icon="mdi-send" variant="tonal" color="deep-purple"
|
||||||
v-else
|
:disabled="!canSend" class="send-btn" />
|
||||||
@click="$emit('send')"
|
|
||||||
icon="mdi-send"
|
|
||||||
variant="text"
|
|
||||||
color="deep-purple"
|
|
||||||
:disabled="!canSend"
|
|
||||||
class="send-btn"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,7 +138,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
import { useDisplay } from 'vuetify';
|
||||||
import { useModuleI18n } from '@/i18n/composables';
|
import { useModuleI18n } from '@/i18n/composables';
|
||||||
import { useCustomizerStore } from '@/stores/customizer';
|
import { useCustomizerStore } from '@/stores/customizer';
|
||||||
import ConfigSelector from './ConfigSelector.vue';
|
import ConfigSelector from './ConfigSelector.vue';
|
||||||
@@ -251,21 +238,34 @@ function handleReplyAfterLeave() {
|
|||||||
isReplyClosing.value = false;
|
isReplyClosing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
const { mobile } = useDisplay();
|
||||||
// Enter 发送消息或触发命令
|
|
||||||
if (e.keyCode === 13 && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// 检查是否是 /astr_live_dev 命令
|
// Auto-resize textarea
|
||||||
|
function autoResize() {
|
||||||
|
const el = inputField.value;
|
||||||
|
if (!el) return;
|
||||||
|
el.style.height = 'auto';
|
||||||
|
el.style.height = Math.min(el.scrollHeight, 200) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(localPrompt, () => {
|
||||||
|
nextTick(autoResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
// Enter 插入换行(桌面和手机端均如此,发送通过右下角发送按鈕)
|
||||||
|
// Shift+Enter 发送(Ctrl+Enter / Cmd+Enter 也保留)
|
||||||
|
if (e.keyCode === 13 && (e.shiftKey || e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
if (localPrompt.value.trim() === '/astr_live_dev') {
|
if (localPrompt.value.trim() === '/astr_live_dev') {
|
||||||
emit('openLiveMode');
|
emit('openLiveMode');
|
||||||
localPrompt.value = '';
|
localPrompt.value = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canSend.value) {
|
if (canSend.value) {
|
||||||
emit('send');
|
emit('send');
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+B 录音
|
// Ctrl+B 录音
|
||||||
@@ -588,11 +588,20 @@ defineExpose({
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.input-area {
|
.input-area {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
padding-bottom: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-area textarea,
|
||||||
|
.chat-textarea {
|
||||||
|
min-height: 32px !important;
|
||||||
|
max-height: 160px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
padding: 16px 16px 12px 16px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
@deleteProject="$emit('deleteProject', $event)"
|
@deleteProject="$emit('deleteProject', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style="overflow-y: auto; flex-grow: 1;"
|
<div style="overflow-y: auto; flex-grow: 1; overscroll-behavior-y: contain;"
|
||||||
v-if="!sidebarCollapsed || isMobile">
|
v-if="!sidebarCollapsed || isMobile">
|
||||||
<v-card v-if="sessions.length > 0" flat style="background-color: transparent;">
|
<v-card v-if="sessions.length > 0" flat style="background-color: transparent;">
|
||||||
<v-list density="compact" nav class="conversation-list"
|
<v-list density="compact" nav class="conversation-list"
|
||||||
@@ -326,6 +326,13 @@ function handleTransportModeChange(mode: string | null) {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.conversation-actions {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.edit-title-btn,
|
.edit-title-btn,
|
||||||
.delete-conversation-btn {
|
.delete-conversation-btn {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|||||||
@@ -965,6 +965,7 @@ export default {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -468,6 +468,12 @@ onMounted(async () => {
|
|||||||
<v-icon>mdi-menu</v-icon>
|
<v-icon>mdi-menu</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- 移动端 chat sidebar 展开按钮 - 仅在 chat 模式下的小屏幕显示 -->
|
||||||
|
<v-btn v-if="customizer.viewMode === 'chat'" class="hidden-lg-and-up ms-1" icon rounded="sm" variant="flat"
|
||||||
|
@click.stop="customizer.TOGGLE_CHAT_SIDEBAR()">
|
||||||
|
<v-icon>mdi-menu</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
<div class="logo-container" :class="{ 'mobile-logo': $vuetify.display.xs, 'chat-mode-logo': customizer.viewMode === 'chat' }" @click="handleLogoClick">
|
<div class="logo-container" :class="{ 'mobile-logo': $vuetify.display.xs, 'chat-mode-logo': customizer.viewMode === 'chat' }" @click="handleLogoClick">
|
||||||
<span class="logo-text Outfit">Astr<span class="logo-text bot-text-wrapper">Bot
|
<span class="logo-text Outfit">Astr<span class="logo-text bot-text-wrapper">Bot
|
||||||
<img v-if="isChristmas" src="@/assets/images/xmas-hat.png" alt="Christmas hat" class="xmas-hat" />
|
<img v-if="isChristmas" src="@/assets/images/xmas-hat.png" alt="Christmas hat" class="xmas-hat" />
|
||||||
@@ -488,13 +494,13 @@ onMounted(async () => {
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bot/Chat 模式切换按钮 -->
|
<!-- Bot/Chat 模式切换按钮 - 手机端隐藏,移入 ... 菜单 -->
|
||||||
<v-btn-toggle
|
<v-btn-toggle
|
||||||
v-model="viewMode"
|
v-model="viewMode"
|
||||||
mandatory
|
mandatory
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="compact"
|
density="compact"
|
||||||
class="mr-4"
|
class="mr-4 hidden-xs"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
<v-btn value="bot" size="small">
|
<v-btn value="bot" size="small">
|
||||||
@@ -524,6 +530,30 @@ onMounted(async () => {
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Bot/Chat 模式切换 - 仅在手机端显示 -->
|
||||||
|
<template v-if="$vuetify.display.xs">
|
||||||
|
<div class="mobile-mode-toggle-wrapper">
|
||||||
|
<v-btn-toggle
|
||||||
|
v-model="viewMode"
|
||||||
|
mandatory
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
color="primary"
|
||||||
|
class="mobile-mode-toggle"
|
||||||
|
>
|
||||||
|
<v-btn value="bot" size="small">
|
||||||
|
<v-icon start>mdi-robot</v-icon>
|
||||||
|
Bot
|
||||||
|
</v-btn>
|
||||||
|
<v-btn value="chat" size="small">
|
||||||
|
<v-icon start>mdi-chat</v-icon>
|
||||||
|
Chat
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</div>
|
||||||
|
<v-divider class="my-1" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 语言切换 -->
|
<!-- 语言切换 -->
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="lang in languages"
|
v-for="lang in languages"
|
||||||
@@ -888,6 +918,10 @@ onMounted(async () => {
|
|||||||
margin-left: 22px;
|
margin-left: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-logo.chat-mode-logo {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-text {
|
.logo-text {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
@@ -926,6 +960,20 @@ onMounted(async () => {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-mode-toggle-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px 12px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-mode-toggle {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-mode-toggle .v-btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* 移动端对话框标题样式 */
|
/* 移动端对话框标题样式 */
|
||||||
.mobile-card-title {
|
.mobile-card-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -15,3 +15,7 @@
|
|||||||
@import './components/VScrollbar';
|
@import './components/VScrollbar';
|
||||||
|
|
||||||
@import './pages/dashboards';
|
@import './pages/dashboards';
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
overscroll-behavior-y: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export const useCustomizerStore = defineStore({
|
|||||||
fontTheme: "Poppins",
|
fontTheme: "Poppins",
|
||||||
uiTheme: config.uiTheme,
|
uiTheme: config.uiTheme,
|
||||||
inputBg: config.inputBg,
|
inputBg: config.inputBg,
|
||||||
viewMode: (localStorage.getItem('viewMode') as 'bot' | 'chat') || 'bot' // 'bot' 或 'chat'
|
viewMode: (localStorage.getItem('viewMode') as 'bot' | 'chat') || 'bot', // 'bot' 或 'chat'
|
||||||
|
chatSidebarOpen: false // chat mode mobile sidebar state
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {},
|
getters: {},
|
||||||
@@ -30,7 +31,13 @@ export const useCustomizerStore = defineStore({
|
|||||||
},
|
},
|
||||||
SET_VIEW_MODE(payload: 'bot' | 'chat') {
|
SET_VIEW_MODE(payload: 'bot' | 'chat') {
|
||||||
this.viewMode = payload;
|
this.viewMode = payload;
|
||||||
localStorage.setItem("viewMode", payload);
|
localStorage.setItem('viewMode', payload);
|
||||||
|
},
|
||||||
|
TOGGLE_CHAT_SIDEBAR() {
|
||||||
|
this.chatSidebarOpen = !this.chatSidebarOpen;
|
||||||
|
},
|
||||||
|
SET_CHAT_SIDEBAR(payload: boolean) {
|
||||||
|
this.chatSidebarOpen = payload;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user