feat: новая функция

This commit is contained in:
2025-11-01 15:58:17 +03:00
parent c33999edc7
commit 772d4cff54
22 changed files with 600 additions and 71 deletions

View File

@@ -156,7 +156,17 @@
</div>
<label>Плейсхолдер</label>
<input v-model="newColPlaceholder" class="notion-input" placeholder="Плейсхолдер (авто)" />
<!-- Удаляю блок назначения столбца -->
<label>Назначение (для RAG)</label>
<select v-model="newColPurpose" class="notion-input">
<option value="">Без назначения</option>
<option value="question">Вопрос для AI</option>
<option value="answer">Ответ AI</option>
<option value="product">Продукт</option>
<option value="userTags">Теги пользователя</option>
<option value="context">Контекст</option>
<option value="priority">Приоритет</option>
<option value="date">Дата</option>
</select>
<div class="modal-actions">
<button class="save-btn" @click="handleAddColumn">Добавить</button>
<button class="cancel-btn" @click="closeAddColModal">Отмена</button>
@@ -184,7 +194,7 @@ onMounted(() => {
window.addEventListener('refresh-application-data', () => {
console.log('[UserTableView] Refreshing table data');
loadTableData(); // Обновляем данные при входе в систему
fetchTable(); // Обновляем данные при входе в систему
});
});
// Импортируем компоненты Element Plus
@@ -236,6 +246,7 @@ const relatedColumnId = ref(null);
const relatedTableColumns = ref([]);
const newColPlaceholder = ref('');
const multiOptionsInput = ref('');
const newColPurpose = ref('');
// Новые фильтры по relation/multiselect/lookup
const relationFilters = ref({});
@@ -304,6 +315,7 @@ function closeAddColModal() {
selectedTagIds.value = [];
newColPlaceholder.value = '';
multiOptionsInput.value = '';
newColPurpose.value = '';
}
async function handleAddColumn() {
@@ -325,6 +337,9 @@ async function handleAddColumn() {
options.relatedTableId = relatedTableId.value;
options.relatedColumnId = relatedColumnId.value;
}
if (newColPurpose.value) {
options.purpose = newColPurpose.value;
}
if (Object.keys(options).length > 0) {
data.options = options;
}
@@ -902,9 +917,6 @@ async function updateRowData(rowId) {
min-width: 80px;
max-width: 600px;
}
.el-table-row-custom {
/* Можно добавить стили для высоты строк, если нужно */
}
.notion-input {
width: 100%;

View File

@@ -562,9 +562,6 @@ export function useChat(auth) {
console.log('[useChat] Clearing chat data');
// Очищаем данные при выходе из системы
messages.value = [];
newMessages.value = [];
readUserIds.value = [];
lastReadMessageDate.value = {};
});
window.addEventListener('refresh-application-data', () => {

View File

@@ -33,7 +33,7 @@
:messages="messages"
:is-loading="isLoading || isConnectingWallet"
:has-more-messages="messageLoading.hasMoreMessages"
:currentUserId="auth.userId"
:currentUserId="auth.userId.value"
v-model:newMessage="newMessage"
v-model:attachments="attachments"
@send-message="handleSendMessage"
@@ -45,7 +45,7 @@
:messages="messages"
:is-loading="isLoading || isConnectingWallet"
:has-more-messages="messageLoading.hasMoreMessages"
:currentUserId="auth.userId"
:currentUserId="auth.userId.value"
v-model:newMessage="newMessage"
v-model:attachments="attachments"
@send-message="handleSendMessage"

View File

@@ -51,7 +51,7 @@
<!-- Основной контент -->
<div class="page-content">
<h2>Содержание</h2>
<div class="content-text" v-if="page.format === 'html'" v-html="formatContent(page.content)"></div>
<div class="content-text" v-if="page.format === 'html'" v-html="formatContent"></div>
<div v-else-if="page.format === 'pdf' && page.file_path" class="file-preview">
<embed :src="page.file_path" type="application/pdf" class="pdf-embed" />
<a class="btn btn-outline" :href="page.file_path" target="_blank" download>Скачать PDF</a>
@@ -106,10 +106,12 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
// Props
const props = defineProps({
@@ -161,10 +163,26 @@ function formatAddress(address) {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function formatContent(content) {
const formatContent = computed(() => {
if (!page.value || !page.value.content) return '';
const content = page.value.content;
// Проверяем, является ли контент markdown (содержит markdown синтаксис)
const isMarkdown = /^#{1,6}\s|^\*\s|^\-\s|^\d+\.\s|```|\[.+\]\(.+\)|!\[.+\]\(.+\)/m.test(content);
if (isMarkdown) {
// Конвертируем markdown в HTML
const rawHtml = marked.parse(content);
return DOMPurify.sanitize(rawHtml);
} else {
// Простое форматирование - замена переносов строк на <br>
return content.replace(/\n/g, '<br>');
}
});
function formatContentAsFunc(content) {
if (!content) return '';
if (typeof content !== 'string') return '';
// Простое форматирование - замена переносов строк на <br>
return content.replace(/\n/g, '<br>');
}
@@ -284,6 +302,119 @@ onMounted(() => {
font-size: 1rem;
line-height: 1.7;
}
/* Markdown стили */
.content-text h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 1.5rem 0 1rem;
font-weight: 600;
}
.content-text h2 {
color: var(--color-primary);
font-size: 1.5rem;
margin: 1.25rem 0 0.75rem;
font-weight: 600;
}
.content-text h3 {
color: var(--color-primary);
font-size: 1.25rem;
margin: 1rem 0 0.5rem;
font-weight: 600;
}
.content-text h4 {
color: var(--color-primary);
font-size: 1.1rem;
margin: 0.75rem 0 0.5rem;
font-weight: 600;
}
.content-text p {
margin: 0.75rem 0;
}
.content-text ul,
.content-text ol {
margin: 1rem 0;
padding-left: 2rem;
}
.content-text li {
margin: 0.5rem 0;
}
.content-text code {
background: #f4f4f4;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
.content-text pre {
background: #f4f4f4;
padding: 1rem;
border-radius: var(--radius-sm);
overflow-x: auto;
margin: 1rem 0;
}
.content-text pre code {
background: none;
padding: 0;
}
.content-text blockquote {
border-left: 4px solid var(--color-primary);
padding-left: 1rem;
margin: 1rem 0;
color: #666;
}
.content-text table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.content-text table th,
.content-text table td {
border: 1px solid #ddd;
padding: 0.5rem;
text-align: left;
}
.content-text table th {
background: #f8f9fa;
font-weight: 600;
}
.content-text a {
color: var(--color-primary);
text-decoration: none;
}
.content-text a:hover {
text-decoration: underline;
}
.content-text hr {
border: none;
border-top: 2px solid #f0f0f0;
margin: 2rem 0;
}
.content-text strong {
font-weight: 600;
color: #333;
}
.content-text em {
font-style: italic;
}
.seo-info { display: grid; gap: 12px; }
.seo-item { display: flex; justify-content: space-between; align-items: flex-start; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
.seo-item:last-child { border-bottom: none; }

View File

@@ -85,7 +85,7 @@ const canManageLegalDocs = computed(() => hasPermission(SHARED_PERMISSIONS.MANAG
function goBack() { router.push({ name: 'content-list' }); }
function openPublic(id) { router.push({ name: 'public-page-view', params: { id } }); }
function goEdit(id) { router.push({ name: 'page-edit', params: { id } }); }
function goEdit(id) { router.push({ name: 'content-create', query: { edit: id } }); }
async function reindex(id) {
try {
await api.post(`/pages/${id}/reindex`);

View File

@@ -300,7 +300,17 @@ async function loadEmbeddingModels() {
}
async function loadPlaceholders() {
const { data } = await axios.get('/tables/placeholders/all');
placeholders.value = Array.isArray(data) ? data : [];
const allPlaceholders = Array.isArray(data) ? data : [];
// Фильтруем только плейсхолдеры из выбранных RAG таблиц
if (settings.value.selected_rag_tables) {
const selectedTableId = typeof settings.value.selected_rag_tables === 'object'
? settings.value.selected_rag_tables[0]
: settings.value.selected_rag_tables;
placeholders.value = allPlaceholders.filter(ph => ph.table_id === Number(selectedTableId));
} else {
placeholders.value = [];
}
}
function openEditPlaceholder(ph) {
editingPlaceholder.value = { ...ph };
@@ -316,6 +326,11 @@ async function savePlaceholderEdit() {
await loadPlaceholders();
closeEditPlaceholder();
}
// Обновляем плейсхолдеры при изменении выбранной RAG таблицы
watch(() => settings.value.selected_rag_tables, () => {
loadPlaceholders();
});
onMounted(async () => {
await loadSettings();
await loadUserTables();