feat: новая функция
This commit is contained in:
@@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS user_cell_values (
|
||||
id SERIAL PRIMARY KEY,
|
||||
row_id INTEGER NOT NULL REFERENCES user_rows(id) ON DELETE CASCADE,
|
||||
column_id INTEGER NOT NULL REFERENCES user_columns(id) ON DELETE CASCADE,
|
||||
value TEXT,
|
||||
value_encrypted TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(row_id, column_id)
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"generate:abi": "node scripts/generate-abi.js",
|
||||
"generate:flattened": "node scripts/generate-flattened.js",
|
||||
"compile:full": "npx hardhat compile && npm run generate:abi && npm run generate:flattened",
|
||||
"seed:legal": "node scripts/seed/legalTemplatesSeed.js"
|
||||
"seed:legal": "node scripts/seed/legalTemplatesSeed.js",
|
||||
"import:legal": "node scripts/import-legal-docs.js"
|
||||
},
|
||||
"bin": {},
|
||||
"engines": {
|
||||
|
||||
291
backend/scripts/import-legal-docs.js
Normal file
291
backend/scripts/import-legal-docs.js
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is proprietary and confidential.
|
||||
* Unauthorized copying, modification, or distribution is prohibited.
|
||||
*
|
||||
* For licensing inquiries: info@hb3-accelerator.com
|
||||
* Website: https://hb3-accelerator.com
|
||||
* GitHub: https://github.com/VC-HB3-Accelerator
|
||||
*/
|
||||
|
||||
/**
|
||||
* Импорт правовых документов из папки legal в admin_pages_simple
|
||||
* Конвертирует markdown файлы в HTML и добавляет их как опубликованные страницы
|
||||
*/
|
||||
|
||||
const db = require('../db');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Маппинг файлов на документацию
|
||||
const legalDocsMapping = {
|
||||
'CONSENT_PERSONAL_DATA_RU.md': {
|
||||
title: 'Согласие на обработку персональных данных',
|
||||
summary: 'Шаблон пользовательского согласия на обработку персональных данных'
|
||||
},
|
||||
'COOKIE_CONSENT_RU.md': {
|
||||
title: 'Согласие на использование файлов cookie',
|
||||
summary: 'Шаблон согласия на использование cookie по категориям'
|
||||
},
|
||||
'PDN_RIGHTS_AND_REVOCATION_RU.md': {
|
||||
title: 'Права субъектов персональных данных и отзыв согласия',
|
||||
summary: 'Информация о правах субъектов ПДн и форма отзыва согласия'
|
||||
},
|
||||
'PRIVACY_POLICY_RU.md': {
|
||||
title: 'Политика конфиденциальности',
|
||||
summary: 'Публичная политика конфиденциальности сервиса'
|
||||
},
|
||||
'SERVICE_ACT_TEMPLATE_RU.md': {
|
||||
title: 'Акт выполненных работ',
|
||||
summary: 'Шаблон акта выполненных работ для подтверждения оказанных услуг'
|
||||
},
|
||||
'SERVICE_AGREEMENT_RU.md': {
|
||||
title: 'Договор оказания услуг',
|
||||
summary: 'Минимальный договор оказания услуг / лицензионный договор'
|
||||
},
|
||||
'service-terms.md': {
|
||||
title: 'Условия приобретения и обслуживания Digital Legal Entity',
|
||||
summary: 'Условия приобретения, лицензирования и обслуживания DLE'
|
||||
}
|
||||
};
|
||||
|
||||
async function ensureTable(tableName) {
|
||||
const existsRes = await db.getQuery()(
|
||||
`SELECT to_regclass($1) as exists`,
|
||||
[tableName]
|
||||
);
|
||||
if (!existsRes.rows[0].exists) {
|
||||
await db.getQuery()(`
|
||||
CREATE TABLE ${tableName} (
|
||||
id SERIAL PRIMARY KEY,
|
||||
author_address TEXT NULL,
|
||||
title TEXT,
|
||||
summary TEXT,
|
||||
content TEXT,
|
||||
seo JSONB,
|
||||
status TEXT,
|
||||
visibility TEXT,
|
||||
required_permission TEXT,
|
||||
format TEXT,
|
||||
mime_type TEXT,
|
||||
storage_type TEXT,
|
||||
file_path TEXT,
|
||||
size_bytes BIGINT,
|
||||
checksum TEXT,
|
||||
is_system_template BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureColumns(tableName) {
|
||||
const needed = {
|
||||
author_address: 'TEXT',
|
||||
title: 'TEXT',
|
||||
summary: 'TEXT',
|
||||
content: 'TEXT',
|
||||
seo: 'JSONB',
|
||||
status: 'TEXT',
|
||||
visibility: 'TEXT',
|
||||
required_permission: 'TEXT',
|
||||
format: 'TEXT',
|
||||
mime_type: 'TEXT',
|
||||
storage_type: 'TEXT',
|
||||
file_path: 'TEXT',
|
||||
size_bytes: 'BIGINT',
|
||||
checksum: 'TEXT',
|
||||
is_system_template: 'BOOLEAN DEFAULT FALSE',
|
||||
created_at: 'TIMESTAMP DEFAULT NOW()',
|
||||
updated_at: 'TIMESTAMP DEFAULT NOW()'
|
||||
};
|
||||
|
||||
const existingRes = await db.getQuery()(
|
||||
`SELECT column_name FROM information_schema.columns WHERE table_name = $1`,
|
||||
[tableName]
|
||||
);
|
||||
const existing = existingRes.rows.map(r => r.column_name);
|
||||
|
||||
for (const [col, type] of Object.entries(needed)) {
|
||||
if (!existing.includes(col)) {
|
||||
await db.getQuery()(`ALTER TABLE ${tableName} ADD COLUMN ${col} ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Простая функция конвертации markdown в HTML
|
||||
function markdownToHtml(markdown) {
|
||||
let html = markdown;
|
||||
|
||||
// Заголовки
|
||||
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
||||
|
||||
// Списки
|
||||
html = html.replace(/^\- (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/^(\d+)\. (.*$)/gim, '<li>$2</li>');
|
||||
html = html.replace(/(<li>.*<\/li>\n)+/g, '<ul>$&</ul>');
|
||||
|
||||
// Жирный текст
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
html = html.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
||||
|
||||
// Курсив
|
||||
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/_(.*?)_/g, '<em>$1</em>');
|
||||
|
||||
// Ссылки
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||
|
||||
// Изображения
|
||||
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
|
||||
|
||||
// Code blocks
|
||||
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
// Blockquotes
|
||||
html = html.replace(/^> (.*$)/gim, '<blockquote>$1</blockquote>');
|
||||
|
||||
// Горизонтальная линия
|
||||
html = html.replace(/^---$/gim, '<hr>');
|
||||
|
||||
// Параграфы
|
||||
html = html.split('\n\n').map(p => {
|
||||
p = p.trim();
|
||||
if (p && !p.match(/^<[hul]/) && !p.match(/<\/[hul]/) && !p.match(/^<pre>/) && !p.match(/<\/pre>/) && !p.match(/^<blockquote>/) && !p.match(/<\/blockquote>/)) {
|
||||
return `<p>${p}</p>`;
|
||||
}
|
||||
return p;
|
||||
}).join('\n\n');
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
async function importLegalDocument(filename, metadata) {
|
||||
// Пробуем разные пути в зависимости от окружения
|
||||
const possiblePaths = [
|
||||
path.join('/legal', filename), // Docker окружение
|
||||
path.join(__dirname, '..', '..', 'legal', filename), // Локальное окружение
|
||||
path.join(process.cwd(), '..', 'legal', filename) // Альтернативный путь
|
||||
];
|
||||
|
||||
let filePath = null;
|
||||
for (const possiblePath of possiblePaths) {
|
||||
if (fs.existsSync(possiblePath)) {
|
||||
filePath = possiblePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
console.error(`Файл не найден: ${filename}`);
|
||||
console.error(`Проверенные пути: ${possiblePaths.join(', ')}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Чтение файла: ${filename}`);
|
||||
const markdownContent = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Конвертируем markdown в HTML
|
||||
const htmlContent = markdownToHtml(markdownContent);
|
||||
|
||||
// Проверяем, существует ли уже документ с таким названием
|
||||
const existing = await db.getQuery()(
|
||||
`SELECT id FROM admin_pages_simple WHERE title = $1 LIMIT 1`,
|
||||
[metadata.title]
|
||||
);
|
||||
|
||||
const pageData = {
|
||||
title: metadata.title,
|
||||
summary: metadata.summary,
|
||||
content: htmlContent,
|
||||
seo: {
|
||||
title: metadata.title,
|
||||
description: metadata.summary,
|
||||
keywords: 'ПДн, политика, согласие, правовые документы, DLE'
|
||||
},
|
||||
status: 'published',
|
||||
visibility: 'public',
|
||||
required_permission: null,
|
||||
format: 'html',
|
||||
mime_type: 'text/html',
|
||||
storage_type: 'embedded'
|
||||
};
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
// Обновляем существующий документ
|
||||
const sql = `UPDATE admin_pages_simple
|
||||
SET summary = $2, content = $3, seo = $4, status = $5, visibility = $6,
|
||||
format = $7, mime_type = $8, storage_type = $9, updated_at = NOW()
|
||||
WHERE id = $1`;
|
||||
await db.getQuery()(sql, [
|
||||
existing.rows[0].id,
|
||||
pageData.summary,
|
||||
pageData.content,
|
||||
JSON.stringify(pageData.seo),
|
||||
pageData.status,
|
||||
pageData.visibility,
|
||||
pageData.format,
|
||||
pageData.mime_type,
|
||||
pageData.storage_type
|
||||
]);
|
||||
console.log(`✓ Обновлен: ${metadata.title}`);
|
||||
return { updated: 1, inserted: 0 };
|
||||
} else {
|
||||
// Вставляем новый документ
|
||||
const sql = `INSERT INTO admin_pages_simple
|
||||
(author_address, title, summary, content, seo, status, visibility, required_permission, format, mime_type, storage_type)
|
||||
VALUES (NULL, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`;
|
||||
await db.getQuery()(sql, [
|
||||
pageData.title,
|
||||
pageData.summary,
|
||||
pageData.content,
|
||||
JSON.stringify(pageData.seo),
|
||||
pageData.status,
|
||||
pageData.visibility,
|
||||
pageData.required_permission,
|
||||
pageData.format,
|
||||
pageData.mime_type,
|
||||
pageData.storage_type
|
||||
]);
|
||||
console.log(`✓ Создан: ${metadata.title}`);
|
||||
return { updated: 0, inserted: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Начало импорта правовых документов...');
|
||||
|
||||
const tableName = 'admin_pages_simple';
|
||||
await ensureTable(tableName);
|
||||
await ensureColumns(tableName);
|
||||
|
||||
let totalInserted = 0;
|
||||
let totalUpdated = 0;
|
||||
|
||||
// Импортируем все документы из маппинга
|
||||
for (const [filename, metadata] of Object.entries(legalDocsMapping)) {
|
||||
const result = await importLegalDocument(filename, metadata);
|
||||
if (result) {
|
||||
totalInserted += result.inserted;
|
||||
totalUpdated += result.updated;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✓ Импорт завершен: создано=${totalInserted}, обновлено=${totalUpdated}`);
|
||||
} catch (error) {
|
||||
console.error('Ошибка импорта правовых документов:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().then(() => process.exit(0)).catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -137,7 +137,8 @@ class AIAssistant {
|
||||
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||
history: conversationHistory,
|
||||
model: aiSettings ? aiSettings.model : undefined,
|
||||
rules: rules ? rules.rules : null
|
||||
rules: rules ? rules.rules : null,
|
||||
selectedRagTables: aiSettings ? aiSettings.selected_rag_tables : []
|
||||
});
|
||||
|
||||
if (!aiResponse) {
|
||||
|
||||
@@ -216,8 +216,18 @@ class EncryptedDataService {
|
||||
continue;
|
||||
}
|
||||
const currentParamIndex = paramIndex++;
|
||||
filteredData[key] = value; // Добавляем в отфильтрованные данные
|
||||
console.log(`✅ Добавили зашифрованное поле ${key} = "${value}" в filteredData`);
|
||||
|
||||
// Преобразуем значение в строку для шифрования
|
||||
let valueToEncrypt;
|
||||
if (typeof value === 'object') {
|
||||
// Если это объект/массив, преобразуем в JSON
|
||||
valueToEncrypt = JSON.stringify(value);
|
||||
} else {
|
||||
valueToEncrypt = value;
|
||||
}
|
||||
|
||||
filteredData[key] = valueToEncrypt; // Добавляем в отфильтрованные данные
|
||||
console.log(`✅ Добавили зашифрованное поле ${key} = "${valueToEncrypt}" в filteredData`);
|
||||
if (encryptedColumn.data_type === 'jsonb') {
|
||||
encryptedData[`${key}_encrypted`] = `encrypt_json($${currentParamIndex}, ${hasEncryptedFields ? '$1::text' : 'NULL'})`;
|
||||
} else {
|
||||
@@ -289,7 +299,27 @@ class EncryptedDataService {
|
||||
const placeholders = Object.keys(allData).map(key => allData[key]).join(', ');
|
||||
|
||||
const query = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders}) RETURNING *`;
|
||||
const params = hasEncryptedFields ? [this.encryptionKey, ...Object.values(filteredData)] : [...Object.values(filteredData)];
|
||||
|
||||
// Собираем параметры в правильном порядке: сначала для encrypted, потом для unencrypted
|
||||
const paramsArray = [];
|
||||
if (hasEncryptedFields) paramsArray.push(this.encryptionKey);
|
||||
|
||||
// Добавляем параметры для encrypted колонок
|
||||
for (const key of Object.keys(encryptedData)) {
|
||||
const originalKey = key.replace('_encrypted', '');
|
||||
if (filteredData[originalKey] !== undefined) {
|
||||
paramsArray.push(filteredData[originalKey]);
|
||||
} else if (filteredData[originalKey + '_unencrypted'] !== undefined) {
|
||||
paramsArray.push(filteredData[originalKey + '_unencrypted']);
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем параметры для unencrypted колонок
|
||||
for (const key of Object.keys(unencryptedData)) {
|
||||
paramsArray.push(filteredData[key + '_unencrypted'] || filteredData[key]);
|
||||
}
|
||||
|
||||
const params = paramsArray;
|
||||
|
||||
console.log(`🔍 Выполняем INSERT запрос:`, query);
|
||||
console.log(`🔍 Параметры:`, params);
|
||||
@@ -359,9 +389,13 @@ class EncryptedDataService {
|
||||
params.push(...paramsToAdd);
|
||||
}
|
||||
|
||||
// Добавляем ключ шифрования в начало, если есть зашифрованные поля
|
||||
const hasEncryptedFields = columns.some(col => col.column_name.endsWith('_encrypted'));
|
||||
if (hasEncryptedFields) {
|
||||
// Определяем, нужно ли добавлять ключ шифрования
|
||||
// Проверяем, есть ли в WHERE условиях зашифрованные колонки
|
||||
const hasEncryptedFieldsInConditions = Object.keys(conditions).some(key => {
|
||||
return columns.find(col => col.column_name === `${key}_encrypted`);
|
||||
});
|
||||
|
||||
if (hasEncryptedFieldsInConditions) {
|
||||
params.unshift(this.encryptionKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -201,13 +201,20 @@ async function ragAnswer({ tableId, userQuestion, product = null, threshold = 30
|
||||
/**
|
||||
* Загрузка всех плейсхолдеров и их значений из пользовательских таблиц
|
||||
* Возвращает объект: { placeholder1: value1, placeholder2: value2, ... }
|
||||
* @param {Array} selectedRagTables - Массив ID выбранных RAG таблиц для фильтрации
|
||||
*/
|
||||
async function getAllPlaceholdersWithValues() {
|
||||
async function getAllPlaceholdersWithValues(selectedRagTables = []) {
|
||||
try {
|
||||
console.log('[RAG] Начинаем загрузку плейсхолдеров...');
|
||||
|
||||
// Получаем все колонки с плейсхолдерами
|
||||
const columns = await encryptedDb.getData('user_columns', {});
|
||||
// Получаем колонки с плейсхолдерами
|
||||
let columns = await encryptedDb.getData('user_columns', {});
|
||||
|
||||
// Фильтруем по выбранным RAG таблицам, если они указаны
|
||||
if (selectedRagTables && selectedRagTables.length > 0) {
|
||||
columns = columns.filter(col => selectedRagTables.includes(col.table_id));
|
||||
console.log(`[RAG] Фильтруем по RAG таблицам: ${selectedRagTables.join(', ')}`);
|
||||
}
|
||||
console.log(`[RAG] Получено колонок: ${columns.length}`);
|
||||
|
||||
const columnsWithPlaceholders = columns.filter(col => col.placeholder && col.placeholder.trim() !== '');
|
||||
@@ -281,7 +288,8 @@ async function generateLLMResponse({
|
||||
date,
|
||||
rules,
|
||||
history,
|
||||
model
|
||||
model,
|
||||
selectedRagTables
|
||||
}) {
|
||||
console.log(`[RAG] generateLLMResponse called with:`, {
|
||||
userQuestion,
|
||||
@@ -338,7 +346,7 @@ async function generateLLMResponse({
|
||||
// --- ДОБАВЛЕНО: подстановка плейсхолдеров ---
|
||||
let finalSystemPrompt = systemPrompt;
|
||||
if (systemPrompt && systemPrompt.includes('{')) {
|
||||
const placeholders = await getAllPlaceholdersWithValues();
|
||||
const placeholders = await getAllPlaceholdersWithValues(selectedRagTables);
|
||||
finalSystemPrompt = replacePlaceholders(systemPrompt, placeholders);
|
||||
console.log(`[RAG] Подставлены плейсхолдеры в системный промпт`);
|
||||
}
|
||||
|
||||
@@ -242,11 +242,12 @@ ollama serve
|
||||
- Используйте эмодзи умеренно (1-2 на сообщение)
|
||||
|
||||
Правила ответа:
|
||||
1. Сначала ищите ответ в базе знаний (RAG)
|
||||
2. Если нашли — отвечайте на основе найденной информации
|
||||
3. Если не нашли — честно скажите и предложите помощь оператора
|
||||
4. Не придумывайте информацию о ценах, сроках, условиях
|
||||
5. При сложных вопросах предлагайте связаться с менеджером
|
||||
1. ОБЯЗАТЕЛЬНО: Отвечайте ТОЛЬКО на русском языке. Все вопросы и ответы должны быть на русском языке
|
||||
2. Сначала ищите ответ в базе знаний (RAG)
|
||||
3. Если нашли — отвечайте на основе найденной информации
|
||||
4. Если не нашли — честно скажите и предложите помощь оператора
|
||||
5. Не придумывайте информацию о ценах, сроках, условиях
|
||||
6. При сложных вопросах предлагайте связаться с менеджером
|
||||
|
||||
Всегда заканчивайте: "Чем еще могу помочь? 😊"
|
||||
```
|
||||
@@ -258,6 +259,16 @@ ollama serve
|
||||
|
||||
> 💡 **Подсказка**: Модели автоматически подтянутся из настроек Ollama
|
||||
|
||||
> 📊 **Размер контекстного окна**:
|
||||
> - **Qwen2.5:7b**: Базовый контекст = **32,768 токенов** (~24,000 русских слов)
|
||||
> - Всего данных, отправляемых в модель:
|
||||
> - Системный промпт: ~500-2000 символов (~300-1200 токенов)
|
||||
> - История диалога: до 20 сообщений (~100-500 токенов на сообщение = ~2000-10000 токенов)
|
||||
> - RAG контекст: ~500-2000 токенов (из найденных данных)
|
||||
> - Текущий вопрос: ~50-200 токенов
|
||||
> - **Итого**: примерно 3,000-15,000 токенов (запас достаточен)
|
||||
> - Если нужен больший контекст → используйте Qwen3 с YaRN (до 131K токенов)
|
||||
|
||||
### 4.4 Выбор RAG-таблицы
|
||||
|
||||
1. В поле **"Выбранные RAG-таблицы"** выберите созданную таблицу:
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
1.1. Правообладатель: Тарабанов Александр Викторович (Автор), паспорт/ИНН, адрес, e-mail: info@hb3-accelerator.com.
|
||||
|
||||
1.2. Дистрибьютор: ______________________________ (юр. лицо), ОГРН/ИНН, адрес, представитель, основание полномочий.
|
||||
1.2. Дистрибьютор: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), ОГРН: 1222600014383, ИНН: 2636220809, КПП: 263601001, адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2, представитель: Тарабанов Александр Викторович, генеральный директор, действующий на основании Устава.
|
||||
|
||||
## 2. Термины
|
||||
|
||||
@@ -153,16 +153,18 @@
|
||||
|
||||
Реквизиты и подписи
|
||||
|
||||
Правообладатель: _____________________________ Дата: __.__.20__
|
||||
Правообладатель: Тарабанов Александр Викторович (Автор), e-mail: info@hb3-accelerator.com.
|
||||
Дата: __.__.20__
|
||||
|
||||
Дистрибьютор: _____________________________ Дата: __.__.20__
|
||||
Дистрибьютор: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2, ИНН: 2636220809, ОГРН: 1222600014383, 18900@эрайти.рф, +7 (968) 269-92-64.
|
||||
Дата: __.__.20__
|
||||
|
||||
---
|
||||
|
||||
### Приложение 1. Порядок выдачи и учета токенов (лицензий)
|
||||
|
||||
1) Выпуск и пул токенов: создается и ведется Правообладателем.
|
||||
Адрес смарт‑контракта токенов (лицензий): ________________________________.
|
||||
Адрес смарт‑контракта токенов (лицензий): 0xdD27a91692da59d1Ee7dD1Fb342B9f1B5FF29386.
|
||||
|
||||
2) Распределение Дистрибьютору: по заявке/лотами с указанием лимитов и сроков.
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
# Согласие на обработку персональных данных (онлайн)
|
||||
# Согласие на обработку персональных данных
|
||||
|
||||
Субъект ПДн/Представитель Клиента подтверждает: ФИО, должность/основание представительства, e‑mail, телефон.
|
||||
При использовании приложения **эрайти.рф** даю согласие на обработку персональных данных **ООО "ЭРАЙТИ"**.
|
||||
|
||||
Оператор: {{company_name}}, адрес: {{company_address}}. Дается согласие на обработку персональных данных для заключения и исполнения договора, обслуживания и коммуникаций.
|
||||
Я, субъект персональных данных/представитель Клиента, предоставляю настоящее согласие на обработку персональных данных в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ "О персональных данных".
|
||||
|
||||
**Оператор персональных данных:**
|
||||
ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ")
|
||||
ОГРН: 1222600014383, ИНН: 2636220809
|
||||
Адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2
|
||||
|
||||
Я предоставляю согласие на обработку следующих персональных данных для заключения и исполнения договора, обслуживания и коммуникаций.
|
||||
|
||||
1. Перечень ПДн
|
||||
— ФИО, должность/основание представительства, контактные данные (e‑mail, телефон);
|
||||
@@ -19,11 +26,14 @@
|
||||
— хостинг‑провайдеру и иным лицам по договору/закону при соблюдении мер защиты; трансграничная передача не осуществляется, если отдельно не указано.
|
||||
|
||||
5. Отзыв согласия
|
||||
— по обращению на {{privacy_email}}. Отзыв не влияет на законность обработки до даты отзыва. При отзыве обработка прекращается, если иное не требуется законом.
|
||||
— по обращению на 18900@эрайти.рф Отзыв не влияет на законность обработки до даты отзыва. При отзыве обработка прекращается, если иное не требуется законом.
|
||||
|
||||
6. Подтверждение акцепта
|
||||
— согласие предоставляется онлайн (клик/чекбокс) с фиксацией даты/времени, IP и версии документа техническими средствами.
|
||||
— согласие предоставляется онлайн (клик/чекбокс) с автоматической фиксацией даты/времени, IP-адреса и версии документа техническими средствами.
|
||||
|
||||
Подтверждаю ознакомление с Политикой конфиденциальности на {{website}}. Версия согласия указывается в интерфейсе при акцепте.
|
||||
**Дата предоставления согласия:** автоматически фиксируется системой
|
||||
**IP-адрес:** автоматически фиксируется системой
|
||||
|
||||
Я подтверждаю, что ознакомился с Политикой конфиденциальности на эрайти.рф. Версия согласия указывается в интерфейсе при акцепте.
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Согласие на использование файлов cookie
|
||||
|
||||
Оператор: {{company_name}}. Мы используем cookie для работы сессий и улучшения сервиса.
|
||||
Оператор: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"). Мы используем cookie для работы сессий и улучшения сервиса.
|
||||
|
||||
1. Что такое cookie
|
||||
— небольшие файлы, сохраняемые браузером для корректной работы веб‑приложения.
|
||||
@@ -17,6 +17,6 @@
|
||||
5. Правовая основа
|
||||
— согласие пользователя, кроме строго необходимых cookie, необходимых для предоставления услуги.
|
||||
|
||||
Продолжая использовать сайт/приложение или настраивая панели cookie, вы даёте согласие на использование cookie в соответствии с Политикой конфиденциальности на {{website}}.
|
||||
Продолжая использовать сайт/приложение или настраивая панели cookie, вы даёте согласие на использование cookie в соответствии с Политикой конфиденциальности на эрайти.рф.
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Права субъектов персональных данных и отзыв согласия
|
||||
|
||||
Оператор: {{company_name}}, адрес: {{company_address}}, контакты: {{privacy_email}}, {{privacy_phone}}.
|
||||
Оператор: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2, контакты: 18900@эрайти.рф, +7 (968) 269-92-64.
|
||||
|
||||
1. Права субъекта (ст. 14 152‑ФЗ)
|
||||
— получать информацию об обработке ПДн; требовать уточнения, блокирования или уничтожения ПДн; отзывать согласие.
|
||||
|
||||
2. Порядок обращения
|
||||
— направьте запрос на {{privacy_email}}. Срок ответа — до 30 календарных дней. Ответ предоставляется в электронной форме по адресу, с которого поступил запрос (если не указано иное), при необходимости — на бумаге.
|
||||
— направьте запрос на 18900@эрайти.рф. Срок ответа — до 30 календарных дней. Ответ предоставляется в электронной форме по адресу, с которого поступил запрос (если не указано иное), при необходимости — на бумаге.
|
||||
|
||||
3. Отзыв согласия
|
||||
— возможен по электронному запросу. После отзыва обработка прекращается, если иное не требуется по закону.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Политика конфиденциальности
|
||||
|
||||
Оператор персональных данных: {{company_name}}, ОГРН {{company_ogrn}}, ИНН {{company_inn}}, адрес: {{company_address}}. Контакты: {{privacy_email}}, {{privacy_phone}}.
|
||||
Оператор персональных данных: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), ОГРН: 1222600014383, ИНН: 2636220809, адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2. Контакты: 18900@эрайти.рф, +7 (968) 269-92-64.
|
||||
|
||||
1. Цели обработки
|
||||
— заключение/исполнение договоров, обслуживание клиентов, биллинг, поддержка, улучшение сервиса, соблюдение законодательства.
|
||||
@@ -29,7 +29,7 @@
|
||||
— иные данные — до достижения целей или отзыва согласия, если иное не требуется по закону.
|
||||
|
||||
6. Передача третьим лицам
|
||||
— передаются по договору/закону при необходимости оказания услуг и соблюдении конфиденциальности и безопасности. Хостинг: {{hosting_provider}} ({{hosting_type}}). Трансграничная передача не осуществляется, если отдельно не указано.
|
||||
— личные данные не передаются третьим лицам. Хостинг осуществляется на арендованном VDS сервере ООО "Хостланд" (ИНН: 7811142520), все данные хранятся в изолированных Docker контейнерах в зашифрованном виде. Трансграничная передача не осуществляется.
|
||||
|
||||
7. Меры защиты
|
||||
— шифрование данных в БД, контроль и разграничение доступа, учёт действий, резервное копирование, изоляция в Docker‑контейнерах, организационные меры.
|
||||
@@ -40,11 +40,10 @@
|
||||
— сроки: сессионные — до завершения сессии; функциональные/аналитические — согласно настройкам браузера/панели, но не дольше 30 дней без продления.
|
||||
|
||||
9. Права субъектов и порядок обращений
|
||||
— получение информации об обработке, уточнение, блокирование/уничтожение, отзыв согласия; срок ответа — до 30 календарных дней. Контакты: {{privacy_email}}.
|
||||
— получение информации об обработке, уточнение, блокирование/уничтожение, отзыв согласия; срок ответа — до 30 календарных дней. Контакты: 18900@эрайти.рф.
|
||||
|
||||
10. Контакты Оператора
|
||||
— {{company_name}}, {{company_address}}, {{privacy_email}}, {{privacy_phone}}, сайт: {{website}}.
|
||||
|
||||
Эта политика действует с даты публикации и актуализируется по мере изменений. Уведомление в Роскомнадзор направляется после подготовки и публикации документов в приложении.
|
||||
— ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2, 18900@эрайти.рф, +7 (968) 269-92-64, сайт: эрайти.рф.
|
||||
|
||||
Эта политика действует с даты публикации и актуализируется по мере изменений.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Акт выполненных работ № ____ от «__» ________ 20__ г.
|
||||
|
||||
Исполнитель: {{company_name}}, ОГРН {{company_ogrn}}, ИНН {{company_inn}}, адрес: {{company_address}}.
|
||||
Исполнитель: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), ОГРН: 1222600014383, ИНН: 2636220809, адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2.
|
||||
Заказчик: ________________________________________________
|
||||
Адрес кошелька Заказчика (тестовая сеть): __________________________
|
||||
Tx/Hash передачи токена‑лицензии: _________________________________
|
||||
@@ -15,7 +15,7 @@ Tx/Hash передачи токена‑лицензии: ______________________
|
||||
|
||||
4. Замечания Заказчика: отсутствуют / изложены в приложении.
|
||||
|
||||
Стороны подтверждают, что услуги оказаны в полном объёме. Порядок приёмки и фиксация передачи лицензии (on‑chain) определяются документом «Условия приобретения и обслуживания Digital Legal Entity» (`docs/service-terms.md`). Указанные выше адрес кошелька и Tx/Hash используются для идентификации факта передачи.
|
||||
Стороны подтверждают, что услуги оказаны в полном объёме. Порядок приёмки и фиксация передачи лицензии (on‑chain) определяются документом «Условия приобретения и обслуживания Digital Legal Entity». Указанные выше адрес кошелька и Tx/Hash используются для идентификации факта передачи.
|
||||
|
||||
Подписи сторон
|
||||
Исполнитель: ______________________ /М.П./
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Договор оказания услуг / лицензионный договор (минимальный)
|
||||
|
||||
Исполнитель: {{company_name}}, ОГРН {{company_ogrn}}, ИНН {{company_inn}}, адрес: {{company_address}}.
|
||||
Заказчик: юрлицо/ИП или их представитель.
|
||||
Исполнитель: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), ОГРН: 1222600014383, ИНН: 2636220809, адрес: 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2.
|
||||
Заказчик: ________________________________________________
|
||||
ОГРН: ________________________________________________
|
||||
ИНН: ________________________________________________
|
||||
КПП: ________________________________________________
|
||||
Адрес: ________________________________________________
|
||||
|
||||
|
||||
1. Предмет
|
||||
1.1. Исполнитель предоставляет доступ к веб‑приложению DLE и/или неисключительную лицензию на ПО и оказывает связанные услуги; Заказчик принимает и оплачивает.
|
||||
@@ -17,16 +22,16 @@
|
||||
|
||||
3. Персональные данные
|
||||
3.1. Обработка ПДн осуществляется в соответствии с действующими документами Исполнителя:
|
||||
— Политика конфиденциальности (`PRIVACY_POLICY_RU.md`),
|
||||
— Согласие на обработку ПДн (`CONSENT_PERSONAL_DATA_RU.md`),
|
||||
— Права субъектов и отзыв (`PDN_RIGHTS_AND_REVOCATION_RU.md`),
|
||||
— Согласие на cookie (`COOKIE_CONSENT_RU.md`).
|
||||
— Политика конфиденциальности,
|
||||
— Согласие на обработку ПДн,
|
||||
— Права субъектов и отзыв,
|
||||
— Согласие на cookie.
|
||||
|
||||
4. Приёмка
|
||||
4.1. Результат услуг подтверждается Актом выполненных работ. Порядок и критерии приёмки, а также фиксация передачи лицензии (on‑chain) определяются документом «Условия приобретения и обслуживания Digital Legal Entity» (`docs/service-terms.md`).
|
||||
4.1. Результат услуг подтверждается Актом выполненных работ. Порядок и критерии приёмки, а также фиксация передачи лицензии (on‑chain) определяются документом «Условия приобретения и обслуживания Digital Legal Entity».
|
||||
|
||||
5. Условия обслуживания и возвратов
|
||||
5.1. Обновления, сопровождение, сроки и порядок возвратов регулируются документом «Условия приобретения и обслуживания Digital Legal Entity» (`docs/service-terms.md`).
|
||||
5.1. Обновления, сопровождение, сроки и порядок возвратов регулируются документом «Условия приобретения и обслуживания Digital Legal Entity».
|
||||
|
||||
6. Коммуникации
|
||||
6.1. Взаимодействие сторон осуществляется через веб‑приложение, электронную почту и мессенджеры.
|
||||
@@ -34,10 +39,14 @@
|
||||
7. Прочие условия
|
||||
7.1. Применяется право РФ. Споры рассматриваются в суде по месту нахождения Исполнителя.
|
||||
|
||||
Реквизиты и подписи
|
||||
Исполнитель: {{company_name}}, {{company_address}}, ИНН {{company_inn}}, ОГРН {{company_ogrn}}, {{privacy_email}}, {{privacy_phone}}.
|
||||
Заказчик: ____________________________________________
|
||||
Реквизиты и подписи
|
||||
Исполнитель: ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "ЭРАЙТИ" (ООО "ЭРАЙТИ"), 355007, СТАВРОПОЛЬСКИЙ КРАЙ, Г.О. ГОРОД СТАВРОПОЛЬ, Г СТАВРОПОЛЬ, УЛ БУРМИСТРОВА, Д. 65Б, ПОМЕЩ. 2, ИНН: 2636220809, ОГРН: 1222600014383, 18900@эрайти.рф, +7 (968) 269-92-64.
|
||||
Заказчик: ________________________________________________
|
||||
ОГРН: ________________________________________________
|
||||
ИНН: ________________________________________________
|
||||
КПП: ________________________________________________
|
||||
Адрес: ________________________________________________
|
||||
|
||||
Подписи:
|
||||
Исполнитель: ______________________ /М.П./
|
||||
Подписи:
|
||||
Исполнитель: ______________________ /М.П./
|
||||
Заказчик: _________________________ /М.П./
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
**Оператор персональных данных:** ООО "ЭРАЙТИ"
|
||||
**Ответственное лицо:** Тарабанов Александр Викторович (генеральный директор)
|
||||
**Контактный телефон:** +7 (968) 269-92-64
|
||||
**Email для обращений:**
|
||||
**Email для обращений:** 18900@эрайти.рф
|
||||
**Хостинг:** ООО "Хостланд" (ИНН: 7811142520/781401001) - арендованный VDS сервер
|
||||
**Сроки хранения персональных данных:**
|
||||
- **Идентификационные данные:** До отзыва согласия субъектом ПДн
|
||||
|
||||
@@ -30,6 +30,14 @@ INSERT INTO roles (id, name_encrypted) VALUES
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name_encrypted = EXCLUDED.name_encrypted;"
|
||||
|
||||
# Заполняем справочную таблицу is_rag_source
|
||||
docker exec dapp-postgres psql -U dapp_user -d dapp_db -c "
|
||||
INSERT INTO is_rag_source (id, name_encrypted) VALUES
|
||||
(1, encrypt_text('Да', '$ENCRYPTION_KEY')),
|
||||
(2, encrypt_text('Нет', '$ENCRYPTION_KEY'))
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name_encrypted = EXCLUDED.name_encrypted;"
|
||||
|
||||
docker exec dapp-postgres psql -U dapp_user -d dapp_db -c "
|
||||
INSERT INTO rpc_providers (network_id_encrypted, rpc_url_encrypted, chain_id)
|
||||
VALUES
|
||||
|
||||
Reference in New Issue
Block a user