Добавлено новое поле ai_enabled в запросы к базе данных для получения настроек AI пользователя. Реализована проверка включения функций ИИ в API для улучшения обработки запросов. Обновлены компоненты MergeNotesModal и NotesPage для поддержки удаления оригинальных заметок. Изменены уведомления и стили для улучшения пользовательского интерфейса.
This commit is contained in:
parent
300e881245
commit
772f5b1955
@ -2423,7 +2423,7 @@ app.post("/api/ai/improve", requireApiAuth, async (req, res) => {
|
||||
try {
|
||||
// Получаем AI настройки пользователя
|
||||
const getSettingsSql =
|
||||
"SELECT openai_api_key, openai_base_url, openai_model FROM users WHERE id = ?";
|
||||
"SELECT openai_api_key, openai_base_url, openai_model, ai_enabled FROM users WHERE id = ?";
|
||||
db.get(getSettingsSql, [req.session.userId], async (err, settings) => {
|
||||
if (err) {
|
||||
console.error("Ошибка получения AI настроек:", err.message);
|
||||
@ -2441,6 +2441,13 @@ app.post("/api/ai/improve", requireApiAuth, async (req, res) => {
|
||||
.json({ error: "Настройте AI настройки в параметрах" });
|
||||
}
|
||||
|
||||
// Проверяем, включены ли функции ИИ
|
||||
if (!settings.ai_enabled || settings.ai_enabled === 0) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Функции ИИ отключены в настройках" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Парсим URL
|
||||
const url = new URL(settings.openai_base_url);
|
||||
@ -2559,7 +2566,7 @@ app.post("/api/ai/merge", requireApiAuth, async (req, res) => {
|
||||
try {
|
||||
// Получаем AI настройки пользователя
|
||||
const getSettingsSql =
|
||||
"SELECT openai_api_key, openai_base_url, openai_model FROM users WHERE id = ?";
|
||||
"SELECT openai_api_key, openai_base_url, openai_model, ai_enabled FROM users WHERE id = ?";
|
||||
db.get(getSettingsSql, [req.session.userId], async (err, settings) => {
|
||||
if (err) {
|
||||
console.error("Ошибка получения AI настроек:", err.message);
|
||||
@ -2577,6 +2584,13 @@ app.post("/api/ai/merge", requireApiAuth, async (req, res) => {
|
||||
.json({ error: "Настройте AI настройки в параметрах" });
|
||||
}
|
||||
|
||||
// Проверяем, включены ли функции ИИ
|
||||
if (!settings.ai_enabled || settings.ai_enabled === 0) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: "Функции ИИ отключены в настройках" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Парсим URL
|
||||
const url = new URL(settings.openai_base_url);
|
||||
|
||||
@ -82,7 +82,7 @@ define(['./workbox-47da91e0'], (function (workbox) { 'use strict';
|
||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "/index.html",
|
||||
"revision": "0.u6qfhq29adg"
|
||||
"revision": "0.pkl0gk07ge8"
|
||||
}], {
|
||||
"ignoreURLParametersMatching": [/^utm_/, /^fbclid$/]
|
||||
});
|
||||
|
||||
@ -21,6 +21,7 @@ export const MergeNotesModal: React.FC<MergeNotesModalProps> = ({
|
||||
const [mergedContent, setMergedContent] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [deleteOriginalNotes, setDeleteOriginalNotes] = useState(false);
|
||||
const isClosedRef = useRef(false);
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
@ -34,6 +35,7 @@ export const MergeNotesModal: React.FC<MergeNotesModalProps> = ({
|
||||
setMergedContent("");
|
||||
setIsLoading(false);
|
||||
setIsSaving(false);
|
||||
setDeleteOriginalNotes(false);
|
||||
isClosedRef.current = false;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -44,6 +46,7 @@ export const MergeNotesModal: React.FC<MergeNotesModalProps> = ({
|
||||
setIsLoading(false);
|
||||
setIsSaving(false);
|
||||
setMergedContent("");
|
||||
setDeleteOriginalNotes(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
@ -95,7 +98,27 @@ export const MergeNotesModal: React.FC<MergeNotesModalProps> = ({
|
||||
time,
|
||||
});
|
||||
|
||||
showNotification("Объединенная заметка сохранена!", "success");
|
||||
// Удаляем исходные заметки, если тумблер включен
|
||||
if (deleteOriginalNotes) {
|
||||
try {
|
||||
await Promise.all(
|
||||
selectedNotes.map((note) => offlineNotesApi.delete(note.id))
|
||||
);
|
||||
showNotification(
|
||||
`Объединенная заметка сохранена! Удалено ${selectedNotes.length} исходных заметок.`,
|
||||
"success"
|
||||
);
|
||||
} catch (deleteError) {
|
||||
console.error("Ошибка удаления исходных заметок:", deleteError);
|
||||
showNotification(
|
||||
"Объединенная заметка сохранена, но произошла ошибка при удалении исходных заметок",
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showNotification("Объединенная заметка сохранена!", "success");
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
@ -169,6 +192,34 @@ export const MergeNotesModal: React.FC<MergeNotesModalProps> = ({
|
||||
>
|
||||
<NotePreview content={mergedContent} />
|
||||
</div>
|
||||
<div
|
||||
className="form-group ai-toggle-group"
|
||||
style={{ marginTop: "20px", marginBottom: "10px" }}
|
||||
>
|
||||
<label className="ai-toggle-label">
|
||||
<div className="toggle-label-content">
|
||||
<span className="toggle-text-main">
|
||||
Удалить исходные заметки
|
||||
</span>
|
||||
<span className="toggle-text-desc">
|
||||
{deleteOriginalNotes
|
||||
? "Исходные заметки будут удалены после сохранения объединенной заметки"
|
||||
: "Исходные заметки останутся в списке после сохранения объединенной заметки"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="toggle-switch-wrapper">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="delete-original-notes-toggle"
|
||||
className="toggle-checkbox"
|
||||
checked={deleteOriginalNotes}
|
||||
onChange={(e) => setDeleteOriginalNotes(e.target.checked)}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<span className="toggle-slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1136,19 +1136,6 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
||||
>
|
||||
<div className="date">
|
||||
<span className="date-text">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => onSelect && onSelect(note.id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
width: "18px",
|
||||
height: "18px",
|
||||
cursor: "pointer",
|
||||
marginRight: "10px",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
{formatDate()}
|
||||
{note.is_pinned ? (
|
||||
<span className="pin-indicator">
|
||||
@ -1182,13 +1169,19 @@ export const NoteItem: React.FC<NoteItemProps> = ({
|
||||
>
|
||||
<Icon icon="mdi:pencil" />
|
||||
</div>
|
||||
<div
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => onSelect && onSelect(note.id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{/* <div
|
||||
className="notesHeaderBtn"
|
||||
onClick={handleArchiveClick}
|
||||
title="В архив"
|
||||
>
|
||||
<Icon icon="mdi:delete" />
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ const NotesPage: React.FC = () => {
|
||||
const selectedDate = useAppSelector((state) => state.notes.selectedDate);
|
||||
const selectedTag = useAppSelector((state) => state.notes.selectedTag);
|
||||
const searchQuery = useAppSelector((state) => state.notes.searchQuery);
|
||||
const aiEnabled = useAppSelector((state) => state.profile.aiEnabled);
|
||||
|
||||
const hasFilters = !!(selectedDate || selectedTag || searchQuery);
|
||||
|
||||
@ -115,13 +116,13 @@ const NotesPage: React.FC = () => {
|
||||
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
// Удаляем все выбранные заметки
|
||||
// Архивируем все выбранные заметки
|
||||
await Promise.all(
|
||||
selectedNoteIds.map((id) => offlineNotesApi.delete(id))
|
||||
selectedNoteIds.map((id) => offlineNotesApi.archive(id))
|
||||
);
|
||||
|
||||
showNotification(
|
||||
`Удалено заметок: ${selectedNoteIds.length}`,
|
||||
`Архивировано заметок: ${selectedNoteIds.length}`,
|
||||
"success"
|
||||
);
|
||||
setSelectedNoteIds([]);
|
||||
@ -131,8 +132,8 @@ const NotesPage: React.FC = () => {
|
||||
notesListRef.current.reloadNotes();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка удаления заметок:", error);
|
||||
showNotification("Ошибка удаления заметок", "error");
|
||||
console.error("Ошибка архивирования заметок:", error);
|
||||
showNotification("Ошибка архивирования заметок", "error");
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
@ -193,7 +194,7 @@ const NotesPage: React.FC = () => {
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
{selectedNoteIds.length >= 2 && (
|
||||
{selectedNoteIds.length >= 2 && aiEnabled && (
|
||||
<button
|
||||
onClick={handleMergeNotes}
|
||||
style={{
|
||||
@ -241,15 +242,15 @@ const NotesPage: React.FC = () => {
|
||||
width: "56px",
|
||||
height: "56px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme === "dark" ? "#F44336" : "#E53935",
|
||||
backgroundColor: theme === "dark" ? "#FF9800" : "#FF9800",
|
||||
color: "white",
|
||||
border: "none",
|
||||
cursor: isDeleting ? "not-allowed" : "pointer",
|
||||
opacity: isDeleting ? 0.6 : 1,
|
||||
boxShadow:
|
||||
theme === "dark"
|
||||
? "0 4px 12px rgba(244, 67, 54, 0.4)"
|
||||
: "0 4px 12px rgba(229, 57, 53, 0.4)",
|
||||
? "0 4px 12px rgba(255, 152, 0, 0.4)"
|
||||
: "0 4px 12px rgba(255, 152, 0, 0.4)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@ -261,20 +262,20 @@ const NotesPage: React.FC = () => {
|
||||
e.currentTarget.style.transform = "scale(1.1)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
theme === "dark"
|
||||
? "0 6px 16px rgba(244, 67, 54, 0.6)"
|
||||
: "0 6px 16px rgba(229, 57, 53, 0.6)";
|
||||
? "0 6px 16px rgba(255, 152, 0, 0.6)"
|
||||
: "0 6px 16px rgba(255, 152, 0, 0.6)";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "scale(1)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
theme === "dark"
|
||||
? "0 4px 12px rgba(244, 67, 54, 0.4)"
|
||||
: "0 4px 12px rgba(229, 57, 53, 0.4)";
|
||||
? "0 4px 12px rgba(255, 152, 0, 0.4)"
|
||||
: "0 4px 12px rgba(255, 152, 0, 0.4)";
|
||||
}}
|
||||
title={`Удалить ${selectedNoteIds.length} ${selectedNoteIds.length === 1 ? "заметку" : selectedNoteIds.length > 4 ? "заметок" : "заметки"}`}
|
||||
title={`Архивировать ${selectedNoteIds.length} ${selectedNoteIds.length === 1 ? "заметку" : selectedNoteIds.length > 4 ? "заметок" : "заметки"}`}
|
||||
>
|
||||
<Icon icon="mdi:delete" />
|
||||
<Icon icon="mdi:archive" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@ -289,22 +290,22 @@ const NotesPage: React.FC = () => {
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="Удаление заметок"
|
||||
title="Архивирование заметок"
|
||||
message={
|
||||
<p>
|
||||
Вы уверены, что хотите удалить{" "}
|
||||
Вы уверены, что хотите архивировать{" "}
|
||||
<strong>{selectedNoteIds.length}</strong>{" "}
|
||||
{selectedNoteIds.length === 1
|
||||
? "заметку"
|
||||
: selectedNoteIds.length > 4
|
||||
? "заметок"
|
||||
: "заметки"}
|
||||
? Это действие нельзя отменить.
|
||||
? Заметки можно будет восстановить из архива в настройках.
|
||||
</p>
|
||||
}
|
||||
confirmText={isDeleting ? "Удаление..." : "Удалить"}
|
||||
confirmText={isDeleting ? "Архивирование..." : "Архивировать"}
|
||||
cancelText="Отмена"
|
||||
confirmType="danger"
|
||||
confirmType="primary"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -245,7 +245,7 @@ const SettingsPage: React.FC = () => {
|
||||
setAiEnabled(checked);
|
||||
localStorage.setItem("ai_enabled", checked ? "1" : "0");
|
||||
showNotification(
|
||||
checked ? "Помощь ИИ включена" : "Помощь ИИ отключена",
|
||||
checked ? "Функции ИИ включены" : "Функции ИИ отключены",
|
||||
"success"
|
||||
);
|
||||
} catch (error: any) {
|
||||
@ -726,11 +726,16 @@ const SettingsPage: React.FC = () => {
|
||||
}`}
|
||||
>
|
||||
<div className="toggle-label-content">
|
||||
<span className="toggle-text-main">Включить помощь ИИ</span>
|
||||
<span className="toggle-text-main">Включить функции ИИ</span>
|
||||
<span className="toggle-text-desc">
|
||||
{checkAiSettingsFilled()
|
||||
? 'Показывать кнопку "Помощь ИИ" в редакторах заметок'
|
||||
: "Сначала заполните API Key, Base URL и Модель ниже"}
|
||||
{checkAiSettingsFilled() ? (
|
||||
<ul style={{ margin: "8px 0 0 20px", padding: 0 }}>
|
||||
<li>Улучшение текста заметок</li>
|
||||
<li>Объединение заметок</li>
|
||||
</ul>
|
||||
) : (
|
||||
"Сначала заполните API Key, Base URL и Модель ниже"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="toggle-switch-wrapper">
|
||||
|
||||
@ -1154,6 +1154,58 @@ textarea:focus {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
border: 2px solid var(--border-primary);
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-secondary);
|
||||
transition: all 0.2s ease;
|
||||
accent-color: var(--accent-color);
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"]:hover {
|
||||
border-color: var(--accent-color);
|
||||
background-color: var(--bg-quaternary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--accent-color-rgb), 0.1);
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"]:checked {
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"]:checked::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -60%) rotate(45deg);
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"]:checked:hover {
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 0 0 2px rgba(var(--accent-color-rgb), 0.2);
|
||||
}
|
||||
|
||||
.note-actions input[type="checkbox"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(var(--accent-color-rgb), 0.15);
|
||||
}
|
||||
|
||||
.notesHeaderBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user