ваше сообщение коммита
This commit is contained in:
@@ -15,6 +15,12 @@
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="message.content" class="message-content" v-html="formattedContent" />
|
||||
|
||||
<!-- Кнопки для системного сообщения -->
|
||||
<div v-if="message.sender_type === 'system' && (message.telegramBotUrl || message.supportEmail)" class="system-actions">
|
||||
<button v-if="message.telegramBotUrl" @click="openTelegram(message.telegramBotUrl)" class="system-btn">Перейти в Telegram-бот</button>
|
||||
<button v-if="message.supportEmail" @click="copyEmail(message.supportEmail)" class="system-btn">Скопировать email</button>
|
||||
</div>
|
||||
|
||||
<!-- Блок для отображения прикрепленного файла (теперь с плеерами/изображением/ссылкой) -->
|
||||
<div v-if="attachment" class="message-attachments">
|
||||
<div class="attachment-item">
|
||||
@@ -168,6 +174,14 @@ const formatFileSize = (bytes) => {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
function openTelegram(url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
function copyEmail(email) {
|
||||
navigator.clipboard.writeText(email);
|
||||
// Можно добавить уведомление "Email скопирован"
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -360,4 +374,23 @@ const formatFileSize = (bytes) => {
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.system-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.system-btn {
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 14px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.system-btn:hover {
|
||||
background: var(--color-primary-dark, #2563eb);
|
||||
}
|
||||
</style>
|
||||
122
frontend/src/components/ai-assistant/RuleEditor.vue
Normal file
122
frontend/src/components/ai-assistant/RuleEditor.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class='modal-bg'>
|
||||
<div class='modal'>
|
||||
<h3>{{ rule ? 'Редактировать' : 'Создать' }} набор правил</h3>
|
||||
<label>Название</label>
|
||||
<input v-model="name" />
|
||||
<label>Описание</label>
|
||||
<textarea v-model="description" rows="3" placeholder="Опишите правило в свободной форме" />
|
||||
<button type="button" @click="convertToJson" style="margin: 0.5rem 0;">Преобразовать в JSON</button>
|
||||
<label>Правила (JSON)</label>
|
||||
<textarea v-model="rulesJson" rows="6"></textarea>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div class="actions">
|
||||
<button @click="save">Сохранить</button>
|
||||
<button @click="close">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
const emit = defineEmits(['close']);
|
||||
const props = defineProps({ rule: Object });
|
||||
const name = ref(props.rule ? props.rule.name : '');
|
||||
const description = ref(props.rule ? props.rule.description : '');
|
||||
const rulesJson = ref(props.rule ? JSON.stringify(props.rule.rules, null, 2) : '{\n "checkUserTags": true\n}');
|
||||
const error = ref('');
|
||||
|
||||
watch(() => props.rule, (newRule) => {
|
||||
name.value = newRule ? newRule.name : '';
|
||||
description.value = newRule ? newRule.description : '';
|
||||
rulesJson.value = newRule ? JSON.stringify(newRule.rules, null, 2) : '{\n "checkUserTags": true\n}';
|
||||
});
|
||||
|
||||
function convertToJson() {
|
||||
// Простейший пример: если в описании есть "теги", выставляем checkUserTags
|
||||
// В реальном проекте здесь можно интегрировать LLM или шаблоны
|
||||
try {
|
||||
if (/тег[а-я]* пользов/.test(description.value.toLowerCase())) {
|
||||
rulesJson.value = JSON.stringify({ checkUserTags: true }, null, 2);
|
||||
error.value = '';
|
||||
} else {
|
||||
rulesJson.value = JSON.stringify({ customRule: description.value }, null, 2);
|
||||
error.value = '';
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = 'Не удалось преобразовать описание в JSON';
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
let rules;
|
||||
try {
|
||||
rules = JSON.parse(rulesJson.value);
|
||||
} catch (e) {
|
||||
error.value = 'Ошибка в формате JSON!';
|
||||
return;
|
||||
}
|
||||
if (props.rule && props.rule.id) {
|
||||
await axios.put(`/api/settings/ai-assistant-rules/${props.rule.id}`, { name: name.value, description: description.value, rules });
|
||||
} else {
|
||||
await axios.post('/api/settings/ai-assistant-rules', { name: name.value, description: description.value, rules });
|
||||
}
|
||||
emit('close', true);
|
||||
}
|
||||
function close() { emit('close', false); }
|
||||
</script>
|
||||
<style scoped>
|
||||
.modal-bg {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 16px rgba(0,0,0,0.12);
|
||||
padding: 2rem;
|
||||
min-width: 320px;
|
||||
max-width: 420px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ddd;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
button {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 1.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
button:last-child {
|
||||
background: #eee;
|
||||
color: #333;
|
||||
}
|
||||
.error {
|
||||
color: #c00;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user