mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
feat(frontend): improve agent and KB editor ID display and intent prompts UX
Expose copyable resource IDs in edit modals and replace the intent prompt dropdown with independent toggle buttons so multi-intent selection wraps cleanly.
This commit is contained in:
@@ -1900,6 +1900,8 @@ export default {
|
||||
basic: {
|
||||
title: 'Basic Information',
|
||||
description: 'Configure the knowledge base name and description',
|
||||
kbId: 'Knowledge Base ID',
|
||||
kbIdDesc: 'Use this ID to target the knowledge base in API integrations',
|
||||
typeLabel: 'Knowledge Base Type',
|
||||
typeDocument: 'Document-based',
|
||||
typeFAQ: 'FAQ Q&A',
|
||||
@@ -4280,9 +4282,12 @@ export default {
|
||||
},
|
||||
intentPrompts: {
|
||||
title: 'Intent Prompts',
|
||||
sectionDesc: 'Configure intent-specific system prompts; defaults apply when not customized',
|
||||
intentLabel: 'Intent',
|
||||
intentDescription: 'Select the intent-specific system prompt to edit',
|
||||
promptPlaceholder: 'Enter a custom system prompt...',
|
||||
customized: 'Customized',
|
||||
empty: 'No intent templates available',
|
||||
},
|
||||
selection: {
|
||||
all: 'All',
|
||||
|
||||
@@ -2722,6 +2722,8 @@ export default {
|
||||
basic: {
|
||||
title: "기본 정보",
|
||||
description: "지식베이스의 이름과 설명 정보 설정",
|
||||
kbId: "지식베이스 ID",
|
||||
kbIdDesc: "API 연동에서 이 ID로 지식베이스를 지정할 수 있습니다",
|
||||
typeLabel: "지식베이스 유형",
|
||||
typeDocument: "문서",
|
||||
typeFAQ: "Q&A",
|
||||
@@ -4342,9 +4344,12 @@ export default {
|
||||
},
|
||||
intentPrompts: {
|
||||
title: '의도 프롬프트',
|
||||
sectionDesc: '쿼리 의도별 시스템 프롬프트를 설정합니다. 사용자 정의가 없으면 기본 템플릿이 적용됩니다',
|
||||
intentLabel: '의도',
|
||||
intentDescription: '편집할 의도별 시스템 프롬프트를 선택하세요',
|
||||
promptPlaceholder: '사용자 정의 시스템 프롬프트 입력...',
|
||||
customized: '사용자 정의됨',
|
||||
empty: '사용 가능한 의도 템플릿이 없습니다',
|
||||
},
|
||||
selection: {
|
||||
all: '전체',
|
||||
|
||||
@@ -2235,6 +2235,8 @@ export default {
|
||||
basic: {
|
||||
title: 'Основная информация',
|
||||
description: 'Укажите название и описание базы знаний',
|
||||
kbId: 'ID базы знаний',
|
||||
kbIdDesc: 'Используйте этот ID для указания базы знаний в API-интеграциях',
|
||||
typeLabel: 'Тип базы знаний',
|
||||
typeDocument: 'Документальная',
|
||||
typeFAQ: 'FAQ (вопрос-ответ)',
|
||||
@@ -3842,9 +3844,12 @@ export default {
|
||||
},
|
||||
intentPrompts: {
|
||||
title: 'Промпты намерений',
|
||||
sectionDesc: 'Настройте системные промпты для разных намерений запроса; по умолчанию используются шаблоны системы',
|
||||
intentLabel: 'Намерение',
|
||||
intentDescription: 'Выберите системный промпт для редактирования',
|
||||
promptPlaceholder: 'Введите пользовательский системный промпт...',
|
||||
customized: 'Настроено',
|
||||
empty: 'Нет доступных шаблонов намерений',
|
||||
},
|
||||
selection: {
|
||||
all: 'Все',
|
||||
|
||||
@@ -2697,6 +2697,8 @@ export default {
|
||||
basic: {
|
||||
title: "基本信息",
|
||||
description: "设置知识库的名称和描述信息",
|
||||
kbId: "知识库 ID",
|
||||
kbIdDesc: "API 集成时可使用此 ID 指定知识库",
|
||||
typeLabel: "知识库类型",
|
||||
typeDocument: "文档",
|
||||
typeFAQ: "问答",
|
||||
@@ -4274,9 +4276,12 @@ export default {
|
||||
},
|
||||
intentPrompts: {
|
||||
title: "意图提示词",
|
||||
sectionDesc: "为不同查询意图配置专属系统提示词;未自定义时使用系统默认模板",
|
||||
intentLabel: "意图",
|
||||
intentDescription: "选择要编辑的意图专属系统提示词",
|
||||
promptPlaceholder: "输入自定义系统提示词...",
|
||||
customized: "已自定义",
|
||||
empty: "暂无可用的意图模板",
|
||||
},
|
||||
selection: {
|
||||
all: "全部",
|
||||
|
||||
@@ -32,17 +32,19 @@
|
||||
<!-- 基础设置 -->
|
||||
<div v-show="currentSection === 'basic'" class="section">
|
||||
<div class="section-header">
|
||||
<h2>{{ $t('agent.editor.basicInfo') }}</h2>
|
||||
<div class="section-header-title">
|
||||
<h2>{{ $t('agent.editor.basicInfo') }}</h2>
|
||||
<t-tooltip v-if="isBuiltinAgent" :content="$t('agentEditor.builtinHint')" placement="top">
|
||||
<span class="builtin-agent-hint" tabindex="0" role="img"
|
||||
:aria-label="$t('agentEditor.builtinHint')">
|
||||
<t-icon name="info-circle" />
|
||||
</span>
|
||||
</t-tooltip>
|
||||
</div>
|
||||
<p class="section-description">{{ $t('agent.editor.basicInfoDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<!-- 内置智能体提示 -->
|
||||
<div v-if="isBuiltinAgent" class="builtin-agent-notice">
|
||||
<t-icon name="info-circle" />
|
||||
<span>{{ $t('agentEditor.builtinHint') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 智能体 ID(用于 API 集成) -->
|
||||
<div v-if="mode === 'edit' && props.agent?.id" class="setting-row">
|
||||
<div class="setting-info">
|
||||
@@ -50,13 +52,12 @@
|
||||
<p class="desc">{{ $t('agent.editor.agentIdDesc') }}</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="agent-id-control">
|
||||
<t-input :value="props.agent.id" readonly class="agent-id-input" />
|
||||
<div class="agent-id-field">
|
||||
<code class="agent-id-value" :title="props.agent.id">{{ props.agent.id }}</code>
|
||||
<t-tooltip :content="$t('common.copy')" placement="top">
|
||||
<t-button variant="outline" theme="default" shape="square" @click="copyAgentId">
|
||||
<template #icon>
|
||||
<t-icon name="file-copy" />
|
||||
</template>
|
||||
<t-button theme="default" size="small" variant="text" class="agent-id-copy"
|
||||
@click="copyAgentId">
|
||||
<t-icon name="file-copy" />
|
||||
</t-button>
|
||||
</t-tooltip>
|
||||
</div>
|
||||
@@ -246,60 +247,72 @@
|
||||
<div v-if="!isAgentMode" class="setting-row setting-row-vertical">
|
||||
<div class="setting-info">
|
||||
<label>{{ $t('agentEditor.intentPrompts.title') }}</label>
|
||||
<p class="desc">{{ $t('agentEditor.intentPrompts.sectionDesc') }}</p>
|
||||
</div>
|
||||
<div class="setting-control setting-control-full">
|
||||
<div class="intent-prompts-editor">
|
||||
<div class="intent-selector-row">
|
||||
<span class="intent-label">{{ $t('agentEditor.intentPrompts.intentLabel') }}</span>
|
||||
<t-select v-model="selectedIntent" size="small"
|
||||
:disabled="props.readOnly || intentPromptTemplates.length === 0" class="intent-select">
|
||||
<t-option v-for="template in intentPromptTemplates" :key="template.id"
|
||||
:value="template.id" :label="template.name || template.id">
|
||||
{{ template.name || template.id }}
|
||||
</t-option>
|
||||
</t-select>
|
||||
<span v-if="currentIntentTemplateDesc" class="intent-desc">{{ currentIntentTemplateDesc
|
||||
}}</span>
|
||||
<div v-if="intentPromptTemplates.length === 0" class="prompt-disabled-hint">
|
||||
{{ $t('agentEditor.intentPrompts.empty') }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="intent-toggle-group" role="tablist"
|
||||
:aria-label="$t('agentEditor.intentPrompts.intentLabel')">
|
||||
<t-button v-for="template in intentPromptTemplates" :key="template.id" theme="default"
|
||||
variant="outline" size="small" class="intent-toggle-btn"
|
||||
:class="{ 'intent-toggle-btn--active': selectedIntent === template.id }"
|
||||
:disabled="props.readOnly" @click="selectedIntent = template.id">
|
||||
<span class="intent-toggle-label">
|
||||
{{ template.name || template.id }}
|
||||
<t-tooltip v-if="isIntentCustomized(template.id)"
|
||||
:content="$t('agentEditor.intentPrompts.customized')" placement="top">
|
||||
<span class="intent-toggle-dot" />
|
||||
</t-tooltip>
|
||||
</span>
|
||||
</t-button>
|
||||
</div>
|
||||
<p v-if="currentIntentTemplateDesc" class="intent-active-desc">{{ currentIntentTemplateDesc
|
||||
}}</p>
|
||||
|
||||
<div v-if="placeholderData.system_prompt.length > 0" class="placeholder-tags">
|
||||
<span class="placeholder-label">{{ $t('agentEditor.placeholders.available') }}</span>
|
||||
<t-tooltip v-for="placeholder in placeholderData.system_prompt" :key="placeholder.name"
|
||||
:content="placeholder.description + $t('agentEditor.placeholders.clickToInsert')"
|
||||
placement="top">
|
||||
<span class="placeholder-tag" @click="handlePlaceholderClick('intent', placeholder.name)"
|
||||
v-text="'{{' + placeholder.name + '}}'" />
|
||||
</t-tooltip>
|
||||
<span class="placeholder-hint">{{ $t('agentEditor.placeholders.hint') }}</span>
|
||||
</div>
|
||||
<div v-if="placeholderData.system_prompt.length > 0" class="placeholder-tags">
|
||||
<span class="placeholder-label">{{ $t('agentEditor.placeholders.available') }}</span>
|
||||
<t-tooltip v-for="placeholder in placeholderData.system_prompt" :key="placeholder.name"
|
||||
:content="placeholder.description + $t('agentEditor.placeholders.clickToInsert')"
|
||||
placement="top">
|
||||
<span class="placeholder-tag"
|
||||
@click="handlePlaceholderClick('intent', placeholder.name)"
|
||||
v-text="'{{' + placeholder.name + '}}'" />
|
||||
</t-tooltip>
|
||||
<span class="placeholder-hint">{{ $t('agentEditor.placeholders.hint') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="textarea-with-template">
|
||||
<t-textarea ref="intentPromptTextareaRef" v-model="intentEditorValue"
|
||||
class="system-prompt-textarea" :autosize="{ minRows: 10, maxRows: 25 }"
|
||||
:disabled="props.readOnly || !selectedIntent"
|
||||
:placeholder="currentIntentTemplate?.content || $t('agentEditor.intentPrompts.promptPlaceholder')"
|
||||
@input="handleIntentPromptInput" />
|
||||
<PromptTemplateSelector type="intentPrompt" position="corner" :intent-id="selectedIntent"
|
||||
:show-template-picker="false" @reset-default="resetCurrentIntentPrompt" />
|
||||
</div>
|
||||
<div class="textarea-with-template">
|
||||
<t-textarea ref="intentPromptTextareaRef" v-model="intentEditorValue"
|
||||
class="system-prompt-textarea" :autosize="{ minRows: 10, maxRows: 25 }"
|
||||
:disabled="props.readOnly || !selectedIntent"
|
||||
:placeholder="currentIntentTemplate?.content || $t('agentEditor.intentPrompts.promptPlaceholder')"
|
||||
@input="handleIntentPromptInput" />
|
||||
<PromptTemplateSelector type="intentPrompt" position="corner" :intent-id="selectedIntent"
|
||||
:show-template-picker="false" @reset-default="resetCurrentIntentPrompt" />
|
||||
</div>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="intentPromptPopup.show && filteredIntentPlaceholders.length > 0"
|
||||
class="placeholder-popup-wrapper" :style="intentPromptPopup.style">
|
||||
<div class="placeholder-popup">
|
||||
<div v-for="(placeholder, index) in filteredIntentPlaceholders" :key="placeholder.name"
|
||||
class="placeholder-item"
|
||||
:class="{ active: intentPromptPopup.selectedIndex === index }"
|
||||
@mousedown.prevent="insertGenericPlaceholder('intent', placeholder.name, true)"
|
||||
@mouseenter="intentPromptPopup.selectedIndex = index">
|
||||
<div class="placeholder-name">
|
||||
<code v-html="`{{${placeholder.name}}}`" />
|
||||
<Teleport to="body">
|
||||
<div v-if="intentPromptPopup.show && filteredIntentPlaceholders.length > 0"
|
||||
class="placeholder-popup-wrapper" :style="intentPromptPopup.style">
|
||||
<div class="placeholder-popup">
|
||||
<div v-for="(placeholder, index) in filteredIntentPlaceholders" :key="placeholder.name"
|
||||
class="placeholder-item"
|
||||
:class="{ active: intentPromptPopup.selectedIndex === index }"
|
||||
@mousedown.prevent="insertGenericPlaceholder('intent', placeholder.name, true)"
|
||||
@mouseenter="intentPromptPopup.selectedIndex = index">
|
||||
<div class="placeholder-name">
|
||||
<code v-html="`{{${placeholder.name}}}`" />
|
||||
</div>
|
||||
<div class="placeholder-desc">{{ placeholder.description }}</div>
|
||||
</div>
|
||||
<div class="placeholder-desc">{{ placeholder.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</Teleport>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1862,9 +1875,17 @@ const currentIntentTemplate = computed(() =>
|
||||
);
|
||||
|
||||
const currentIntentTemplateDesc = computed(() =>
|
||||
currentIntentTemplate.value?.description || t('agentEditor.intentPrompts.intentDescription'),
|
||||
currentIntentTemplate.value?.description || '',
|
||||
);
|
||||
|
||||
const isIntentCustomized = (intentId: string) => {
|
||||
const overrides = formData.value.config.intent_prompts || {};
|
||||
const override = overrides[intentId];
|
||||
if (!override?.trim()) return false;
|
||||
const template = intentPromptTemplates.value.find((item) => item.id === intentId);
|
||||
return override.trim() !== (template?.content || '').trim();
|
||||
};
|
||||
|
||||
const filteredIntentPlaceholders = computed(() => {
|
||||
if (!intentPromptPopup.value.prefix) {
|
||||
return placeholderData.value.system_prompt;
|
||||
@@ -3812,6 +3833,17 @@ const handleSave = async () => {
|
||||
.section-header {
|
||||
margin-bottom: 32px;
|
||||
|
||||
.section-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
@@ -3984,16 +4016,40 @@ const handleSave = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.agent-id-control {
|
||||
.agent-id-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
padding: 6px 8px 6px 12px;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
border: 1px solid var(--td-component-stroke);
|
||||
border-radius: 6px;
|
||||
|
||||
.agent-id-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
.agent-id-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--app-font-family-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--td-text-color-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-id-copy {
|
||||
flex-shrink: 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
|
||||
&:hover {
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-footer {
|
||||
@@ -4516,29 +4572,53 @@ const handleSave = async () => {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.intent-toggle-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.intent-selector-row {
|
||||
display: flex;
|
||||
.intent-toggle-group :deep(.intent-toggle-btn--active) {
|
||||
background-color: rgba(7, 192, 95, 0.1);
|
||||
border-color: var(--td-brand-color);
|
||||
color: var(--td-brand-color);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: rgba(7, 192, 95, 0.14);
|
||||
border-color: var(--td-brand-color);
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
|
||||
.intent-toggle-btn {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.intent-toggle-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.intent-label {
|
||||
font-size: 12px;
|
||||
color: var(--td-text-color-secondary);
|
||||
.intent-toggle-dot {
|
||||
flex-shrink: 0;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.intent-select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.intent-desc {
|
||||
.intent-active-desc {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--td-text-color-placeholder);
|
||||
line-height: 1.4;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// 系统提示词输入框样式
|
||||
@@ -4663,22 +4743,24 @@ const handleSave = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 内置智能体提示
|
||||
.builtin-agent-notice {
|
||||
display: flex;
|
||||
.builtin-agent-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--td-warning-color-light);
|
||||
border: 1px solid var(--td-warning-color-focus);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--td-warning-color);
|
||||
font-size: 14px;
|
||||
color: var(--td-text-color-placeholder);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: help;
|
||||
transition: color 0.2s;
|
||||
|
||||
.t-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
color: var(--td-warning-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 2px var(--td-warning-color-focus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,20 @@
|
||||
<p class="section-desc">{{ $t('knowledgeEditor.basic.description') }}</p>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div v-if="mode === 'edit' && props.kbId" class="form-item">
|
||||
<label class="form-label">{{ $t('knowledgeEditor.basic.kbId') }}</label>
|
||||
<p class="form-tip">{{ $t('knowledgeEditor.basic.kbIdDesc') }}</p>
|
||||
<div class="kb-id-field">
|
||||
<code class="kb-id-value" :title="props.kbId">{{ props.kbId }}</code>
|
||||
<t-tooltip :content="$t('common.copy')" placement="top">
|
||||
<t-button theme="default" size="small" variant="text" class="kb-id-copy"
|
||||
@click="copyKbId">
|
||||
<t-icon name="file-copy" />
|
||||
</t-button>
|
||||
</t-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label required">{{ $t('knowledgeEditor.basic.typeLabel') }}</label>
|
||||
<t-radio-group
|
||||
@@ -405,6 +419,30 @@ const emit = defineEmits<{
|
||||
(e: 'success', kbId: string): void
|
||||
}>()
|
||||
|
||||
const copyKbId = async () => {
|
||||
const id = props.kbId
|
||||
if (!id) return
|
||||
|
||||
try {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(id)
|
||||
} else {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = id
|
||||
textarea.setAttribute('readonly', '')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
MessagePlugin.success(t('common.copied'))
|
||||
} catch {
|
||||
MessagePlugin.error(t('common.copyFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
const currentSection = ref<string>('basic')
|
||||
const saving = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -1498,6 +1536,44 @@ watch(
|
||||
color: var(--td-text-color-placeholder);
|
||||
}
|
||||
|
||||
.kb-id-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin-top: 8px;
|
||||
padding: 6px 8px 6px 12px;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
border: 1px solid var(--td-component-stroke);
|
||||
border-radius: 6px;
|
||||
|
||||
.kb-id-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--app-font-family-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--td-text-color-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kb-id-copy {
|
||||
flex-shrink: 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
|
||||
&:hover {
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.granularity-radio-group {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user