ваше сообщение коммита

This commit is contained in:
2025-09-02 15:33:18 +03:00
parent c007c0b296
commit a6360ccd2e
21 changed files with 1269 additions and 316 deletions

View File

@@ -46,28 +46,68 @@
<div class="rag-test-section">
<h4>🧠 Тест RAG-функциональности</h4>
<!-- Выбор RAG-таблицы -->
<div class="rag-table-selection">
<label>Выберите RAG-таблицу:</label>
<div class="rag-table-controls">
<select v-model="selectedRagTable" class="rag-table-select">
<option v-if="availableRagTables.length === 0" value="" disabled>
Нет доступных RAG-таблиц
</option>
<option v-for="table in availableRagTables" :key="table.id" :value="table.id">
{{ table.name }} (ID: {{ table.id }})
</option>
</select>
<button @click="loadRagTables" class="refresh-tables-btn" title="Обновить список таблиц">
🔄
</button>
</div>
<div v-if="availableRagTables.length === 0" class="no-rag-tables">
<p>Для тестирования RAG необходимо создать таблицу и установить её как источник для ИИ-ассистента.</p>
<p>Перейдите в <router-link to="/tables">Таблицы</router-link> и создайте таблицу с вопросами и ответами.</p>
</div>
</div>
<div class="rag-test-controls">
<input
v-model="ragQuestion"
placeholder="Введите вопрос"
class="rag-input"
/>
<button @click="testRAG" :disabled="ragTesting" class="rag-test-btn">
<button @click="testRAG" :disabled="ragTesting || !selectedRagTable" class="rag-test-btn">
{{ ragTesting ? 'Тестирование...' : 'Тестировать RAG' }}
</button>
</div>
<!-- Прогресс-бар и статус -->
<div v-if="ragTesting" class="rag-progress-section">
<div class="rag-status">{{ ragStatus }}</div>
<div class="rag-progress-bar">
<div class="rag-progress-fill" :style="{ width: ragProgress + '%' }"></div>
</div>
<div class="rag-progress-text">{{ Math.round(ragProgress) }}%</div>
</div>
<div v-if="ragResult" :class="['rag-result', getRagResultClass()]">
<div v-if="ragResult.success">
<strong> Успешно!</strong><br>
Таблица: {{ availableRagTables.find(t => t.id === selectedRagTable)?.name || 'Неизвестно' }}<br>
Вопрос: "{{ ragQuestion }}"<br>
Ответ: "{{ ragResult.answer || 'Нет ответа' }}"<br>
Score: {{ ragResult.score || 'N/A' }}
</div>
<div v-else>
<strong> Ошибка!</strong><br>
{{ ragResult.error }}
</div>
</div>
</div>
</div>
</template>
@@ -78,10 +118,14 @@ import axios from 'axios';
const loading = ref(false);
const lastUpdate = ref('никогда');
const ragQuestion = ref('вопрос 1');
const ragQuestion = ref('Как работает ИИ-ассистент?');
const ragTesting = ref(false);
const ragResult = ref(null);
const monitoringData = ref(null);
const availableRagTables = ref([]);
const selectedRagTable = ref(null);
const ragProgress = ref(0);
const ragStatus = ref('');
const serviceLabels = {
backend: 'Backend',
@@ -127,6 +171,25 @@ const getRagResultClass = () => {
return ragResult.value.success ? 'success' : 'error';
};
const loadRagTables = async () => {
try {
const response = await axios.get('/tables');
const tables = response.data || [];
// Фильтруем только таблицы, которые являются источниками для RAG
const ragTables = tables.filter(table => table.is_rag_source_id === 1);
availableRagTables.value = ragTables;
// Если есть доступные таблицы, выбираем первую по умолчанию
if (availableRagTables.value.length > 0 && !selectedRagTable.value) {
selectedRagTable.value = availableRagTables.value[0].id;
}
} catch (e) {
availableRagTables.value = [];
}
};
const refreshStatus = async () => {
loading.value = true;
try {
@@ -142,31 +205,74 @@ const refreshStatus = async () => {
const testRAG = async () => {
if (!ragQuestion.value.trim()) return;
if (!selectedRagTable.value) {
ragResult.value = {
success: false,
error: 'Не выбрана RAG-таблица для тестирования'
};
return;
}
ragTesting.value = true;
ragResult.value = null;
ragProgress.value = 0;
ragStatus.value = '🔍 Ищем ответ в базе знаний...';
// Симуляция прогресса для лучшего UX
const progressInterval = setInterval(() => {
if (ragProgress.value < 90) {
ragProgress.value += Math.random() * 15;
}
}, 1000);
try {
ragStatus.value = '🤖 Генерируем ответ с помощью ИИ...';
const response = await axios.post('/rag/answer', {
tableId: 28,
tableId: selectedRagTable.value,
question: ragQuestion.value,
product: null
});
clearInterval(progressInterval);
ragProgress.value = 100;
ragStatus.value = '✅ Готово!';
ragResult.value = {
success: true,
answer: response.data.answer,
score: response.data.score,
llmResponse: response.data.llmResponse
};
// Обновляем список таблиц после успешного тестирования
await loadRagTables();
} catch (error) {
clearInterval(progressInterval);
ragProgress.value = 0;
ragStatus.value = '';
ragResult.value = {
success: false,
error: error.response?.data?.error || error.message || 'Неизвестная ошибка'
};
}
ragTesting.value = false;
};
onMounted(() => {
refreshStatus();
loadRagTables();
// Подписываемся на обновление плейсхолдеров (когда создаются новые таблицы)
window.addEventListener('placeholders-updated', loadRagTables);
});
onUnmounted(() => {
// Отписываемся от события
window.removeEventListener('placeholders-updated', loadRagTables);
});
</script>
@@ -311,6 +417,132 @@ onMounted(() => {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.rag-table-selection {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.rag-table-selection label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.rag-table-controls {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 10px;
}
.rag-table-select {
flex: 1;
min-width: 200px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
}
.refresh-tables-btn {
background: #6c757d;
color: white;
border: none;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s;
}
.refresh-tables-btn:hover {
background: #5a6268;
}
.no-rag-tables {
padding: 15px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
color: #856404;
}
.no-rag-tables p {
margin: 5px 0;
font-size: 14px;
}
.no-rag-tables a {
color: #007bff;
text-decoration: none;
}
.no-rag-tables a:hover {
text-decoration: underline;
}
.rag-progress-section {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.rag-status {
text-align: center;
font-weight: 500;
color: #333;
margin-bottom: 15px;
font-size: 16px;
}
.rag-progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.rag-progress-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
border-radius: 10px;
transition: width 0.3s ease;
position: relative;
}
.rag-progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.rag-progress-text {
text-align: center;
font-weight: 600;
color: #28a745;
font-size: 14px;
}
.rag-test-section h4 {
margin: 0 0 15px 0;
color: #333;

View File

@@ -168,6 +168,9 @@ function autoResize() {
watch(editing, (val) => {
if (val) {
if (props.column.type === 'multiselect-relation') {
loadMultiRelationOptions();
}
nextTick(() => {
if (textareaRef.value) {
autoResize();
@@ -220,6 +223,7 @@ let unsubscribeFromTags = null;
// Флаг для предотвращения повторных вызовов
let isInitialized = false;
let isMultiRelationValuesLoaded = false;
let lastLoadedOptionsKey = null;
onMounted(async () => {
const startTime = Date.now();
@@ -250,14 +254,12 @@ onMounted(async () => {
} else if (props.column.type === 'multiselect-relation') {
// Загружаем опции только один раз
if (!isInitialized) {
// console.log(`[TableCell] 📥 Загружаем опции для row:${props.rowId} col:${props.column.id}`);
await loadMultiRelationOptions();
isInitialized = true;
}
// Загружаем relations только один раз для каждой комбинации rowId + columnId
if (!isMultiRelationValuesLoaded) {
// console.log(`[TableCell] 📥 Загружаем relations для row:${props.rowId} col:${props.column.id}`);
await loadMultiRelationValues();
isMultiRelationValuesLoaded = true;
}
@@ -326,6 +328,12 @@ onUnmounted(() => {
watch(
() => [props.rowId, props.column.id, props.cellValues],
async () => {
// Сбрасываем флаги при изменении столбца
if (props.column.type === 'multiselect-relation') {
isMultiRelationValuesLoaded = false;
lastLoadedOptionsKey = null;
isInitialized = false;
}
if (props.column.type === 'multiselect') {
multiOptions.value = (props.column.options && props.column.options.options) || [];
const cell = props.cellValues.find(
@@ -485,9 +493,9 @@ async function loadLookupValues() {
}
async function loadMultiRelationOptions() {
// Проверяем, не загружены ли уже опции
if (multiRelationOptions.value.length > 0) {
// console.log('[loadMultiRelationOptions] Опции уже загружены, пропускаем');
// Проверяем, не загружены ли уже опции для текущего столбца
const cacheKey = `${props.column.id}_${props.column.options?.relatedTableId}`;
if (multiRelationOptions.value.length > 0 && lastLoadedOptionsKey === cacheKey) {
return;
}
@@ -517,7 +525,16 @@ async function loadMultiRelationOptions() {
opts.push({ id: row.id, display: cell ? cell.value : `ID ${row.id}` });
}
multiRelationOptions.value = opts;
// console.log(`[loadMultiRelationOptions] Загружено ${opts.length} опций для таблицы ${rel.relatedTableId}`);
lastLoadedOptionsKey = cacheKey;
// Обновляем selectedMultiRelationNames на основе текущих значений
if (editMultiRelationValues.value.length > 0) {
selectedMultiRelationNames.value = opts
.filter(opt => editMultiRelationValues.value.includes(String(opt.id)))
.map(opt => opt.display);
} else {
selectedMultiRelationNames.value = [];
}
} catch (e) {
// console.error('[loadMultiRelationOptions] Error:', e);
}
@@ -531,7 +548,6 @@ const LOAD_DEBOUNCE_DELAY = 50; // 50ms (уменьшено для ускоре
async function loadMultiRelationValues() {
// Проверяем, не загружены ли уже данные
if (isMultiRelationValuesLoaded) {
// console.log('[loadMultiRelationValues] Данные уже загружены, пропускаем');
return;
}
@@ -603,7 +619,6 @@ async function loadMultiRelationValues() {
selectedMultiRelationNames.value = multiRelationOptions.value
.filter(opt => relatedRowIds.includes(String(opt.id)))
.map(opt => opt.display);
// console.log('[loadMultiRelationValues] selectedMultiRelationNames:', selectedMultiRelationNames.value);
// Отмечаем, что данные загружены
isMultiRelationValuesLoaded = true;