diff --git a/apps/client/package.json b/apps/client/package.json index f6db1328..2f653f43 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -14,14 +14,14 @@ "@docmost/editor-ext": "workspace:*", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", - "@excalidraw/excalidraw": "0.18.0-c158187", - "@mantine/core": "^8.3.12", - "@mantine/dates": "^8.3.12", - "@mantine/form": "^8.3.12", - "@mantine/hooks": "^8.3.12", - "@mantine/modals": "^8.3.12", - "@mantine/notifications": "^8.3.12", - "@mantine/spotlight": "^8.3.12", + "@excalidraw/excalidraw": "0.18.0-3a5ef40", + "@mantine/core": "^8.3.14", + "@mantine/dates": "^8.3.14", + "@mantine/form": "^8.3.14", + "@mantine/hooks": "^8.3.14", + "@mantine/modals": "^8.3.14", + "@mantine/notifications": "^8.3.14", + "@mantine/spotlight": "^8.3.14", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.17", "alfaaz": "^1.1.0", @@ -41,7 +41,7 @@ "mantine-form-zod-resolver": "^1.3.0", "mermaid": "^11.12.2", "mitt": "^3.0.1", - "posthog-js": "^1.255.1", + "posthog-js": "1.345.5", "react": "^18.3.1", "react-arborist": "3.4.0", "react-clear-modal": "^2.0.17", @@ -66,7 +66,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^5.1.1", - "eslint": "^9.15.0", + "eslint": "^9.39.2", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.16", diff --git a/apps/client/public/locales/de-DE/translation.json b/apps/client/public/locales/de-DE/translation.json index 16df8599..ede6ef70 100644 --- a/apps/client/public/locales/de-DE/translation.json +++ b/apps/client/public/locales/de-DE/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Aktuelles Datum einfügen", "Draw and sketch excalidraw diagrams": "Excalidraw-Diagramme zeichnen und skizzieren", "Multiple": "Mehrere", + "Turn into": "In verwandeln", + "Text align": "Text ausrichten", "Heading {{level}}": "Überschrift {{level}}", "Toggle title": "Titel umschalten", "Write anything. Enter \"/\" for commands": "Schreiben Sie irgendetwas. Geben Sie \"/\" für Befehle ein", @@ -582,13 +584,33 @@ "Ask AI": "KI fragen", "AI is thinking...": "Die KI überlegt...", "Ask a question...": "Fragen stellen...", - "AI-powered search (Ask AI)": "KI-gestützte Suche (KI fragen)", + "AI Answers": "KI-Antworten", + "AI-powered search (AI Answers)": "KI-unterstützte Suche (KI-Antworten)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Die KI-Suche verwendet Vektor-Einbettungen, um semantische Suchfunktionen in Ihrem Arbeitsbereich bereitzustellen.", "Toggle AI search": "KI-Suche umschalten", + "Generative AI (Ask AI)": "Generative KI (KI fragen)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Aktivieren Sie die KI-unterstützte Inhaltserstellung im Editor. Ermöglicht Benutzern das Erzeugen, Verbessern, Übersetzen und Transformieren von Text.", + "Toggle generative AI": "Generative KI umschalten", "Sources": "Quellen", - "Ask AI not available for attachments": "KI fragen nicht für Anhänge verfügbar", + "AI Answers not available for attachments": "KI-Antworten sind für Anhänge nicht verfügbar", "No answer available": "Keine Antwort verfügbar", "Background color": "Hintergrundfarbe", "Highlight color": "Hervorhebungsfarbe", - "Remove color": "Farbe entfernen" + "Remove color": "Farbe entfernen", + "Notifications": "Benachrichtigungen", + "No notifications": "Keine Benachrichtigungen", + "No unread notifications": "Keine ungelesenen Benachrichtigungen", + "All notifications": "Alle Benachrichtigungen", + "Unread only": "Nur ungelesen", + "Mark all as read": "Alle als gelesen markieren", + "Mark as read": "Als gelesen markieren", + "More options": "Weitere Optionen", + "mentioned you in a comment": "hat Sie in einem Kommentar erwähnt", + "commented on a page": "hat auf einer Seite kommentiert", + "resolved a comment": "hat einen Kommentar gelöst", + "mentioned you on a page": "hat Sie auf einer Seite erwähnt", + "Today": "Heute", + "Yesterday": "Gestern", + "This week": "Diese Woche", + "Older": "Älter" } diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index fa040002..7011de7c 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Insert current date", "Draw and sketch excalidraw diagrams": "Draw and sketch excalidraw diagrams", "Multiple": "Multiple", + "Turn into": "Turn into", + "Text align": "Text align", "Heading {{level}}": "Heading {{level}}", "Toggle title": "Toggle title", "Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands", @@ -582,13 +584,33 @@ "Ask AI": "Ask AI", "AI is thinking...": "AI is thinking...", "Ask a question...": "Ask a question...", - "AI-powered search (Ask AI)": "AI-powered search (Ask AI)", + "AI Answers": "AI Answers", + "AI-powered search (AI Answers)": "AI-powered search (AI Answers)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.", "Toggle AI search": "Toggle AI search", + "Generative AI (Ask AI)": "Generative AI (Ask AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.", + "Toggle generative AI": "Toggle generative AI", "Sources": "Sources", - "Ask AI not available for attachments": "Ask AI not available for attachments", + "AI Answers not available for attachments": "AI Answers not available for attachments", "No answer available": "No answer available", "Background color": "Background color", "Highlight color": "Highlight color", - "Remove color": "Remove color" + "Remove color": "Remove color", + "Notifications": "Notifications", + "No notifications": "No notifications", + "No unread notifications": "No unread notifications", + "All notifications": "All notifications", + "Unread only": "Unread only", + "Mark all as read": "Mark all as read", + "Mark as read": "Mark as read", + "More options": "More options", + "mentioned you in a comment": "mentioned you in a comment", + "commented on a page": "commented on a page", + "resolved a comment": "resolved a comment", + "mentioned you on a page": "mentioned you on a page", + "Today": "Today", + "Yesterday": "Yesterday", + "This week": "This week", + "Older": "Older" } diff --git a/apps/client/public/locales/es-ES/translation.json b/apps/client/public/locales/es-ES/translation.json index 742bf751..be344ee4 100644 --- a/apps/client/public/locales/es-ES/translation.json +++ b/apps/client/public/locales/es-ES/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Insertar fecha actual", "Draw and sketch excalidraw diagrams": "Dibujar y esbozar diagramas de Excalidraw", "Multiple": "Múltiple", + "Turn into": "Convertir en", + "Text align": "Alineación del texto", "Heading {{level}}": "Encabezado {{level}}", "Toggle title": "Alternar título", "Write anything. Enter \"/\" for commands": "Escribe cualquier cosa. Ingresa \"/\" para comandos", @@ -582,13 +584,33 @@ "Ask AI": "Preguntar a IA", "AI is thinking...": "IA está pensando...", "Ask a question...": "Haz una pregunta...", - "AI-powered search (Ask AI)": "Búsqueda impulsada por IA (Preguntar a IA)", + "AI Answers": "Respuestas de IA", + "AI-powered search (AI Answers)": "Búsqueda impulsada por IA (Respuestas de IA)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La búsqueda de IA utiliza incrustaciones vectoriales para proporcionar capacidades de búsqueda semántica en todo el contenido de su espacio de trabajo.", "Toggle AI search": "Alternar búsqueda de IA", + "Generative AI (Ask AI)": "IA generativa (Preguntar a la IA)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Habilitar la generación de contenido impulsada por IA en el editor. Permite a los usuarios generar, mejorar, traducir y transformar texto.", + "Toggle generative AI": "Activar IA generativa", "Sources": "Fuentes", - "Ask AI not available for attachments": "Preguntar a IA no está disponible para adjuntos", + "AI Answers not available for attachments": "Respuestas de IA no disponibles para archivos adjuntos", "No answer available": "No hay respuesta disponible", "Background color": "Color de fondo", "Highlight color": "Color de resaltado", - "Remove color": "Eliminar color" + "Remove color": "Eliminar color", + "Notifications": "Notificaciones", + "No notifications": "Sin notificaciones", + "No unread notifications": "No hay notificaciones no leídas", + "All notifications": "Todas las notificaciones", + "Unread only": "Solo no leídas", + "Mark all as read": "Marcar todo como leído", + "Mark as read": "Marcar como leído", + "More options": "Más opciones", + "mentioned you in a comment": "te mencionó en un comentario", + "commented on a page": "comentó en una página", + "resolved a comment": "resolvió un comentario", + "mentioned you on a page": "te mencionó en una página", + "Today": "Hoy", + "Yesterday": "Ayer", + "This week": "Esta semana", + "Older": "Más antiguo" } diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index 5f9bf40f..fca27390 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Insérer la date actuelle", "Draw and sketch excalidraw diagrams": "Dessiner et esquisser des diagrammes Excalidraw", "Multiple": "Multiple", + "Turn into": "Transformer en", + "Text align": "Alignement du texte", "Heading {{level}}": "Titre {{level}}", "Toggle title": "Basculer le titre", "Write anything. Enter \"/\" for commands": "Écrivez n'importe quoi. Entrez \"/\" pour les commandes", @@ -582,13 +584,33 @@ "Ask AI": "Demander à l'IA", "AI is thinking...": "L'IA réfléchit...", "Ask a question...": "Posez une question...", - "AI-powered search (Ask AI)": "Recherche assistée par l'IA (Demander à l'IA)", + "AI Answers": "Réponses IA", + "AI-powered search (AI Answers)": "Recherche propulsée par IA (Réponses IA)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La recherche IA utilise des incorporations vectorielles pour fournir des capacités de recherche sémantique à travers le contenu de votre espace de travail.", "Toggle AI search": "Basculer la recherche IA", + "Generative AI (Ask AI)": "IA générative (Demandez à l'IA)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Activer la génération de contenu assistée par IA dans l'éditeur. Permet aux utilisateurs de générer, améliorer, traduire et transformer du texte.", + "Toggle generative AI": "Activer/désactiver l'IA générative", "Sources": "Sources", - "Ask AI not available for attachments": "Demande à l'IA non disponible pour les pièces jointes", + "AI Answers not available for attachments": "Réponses IA non disponibles pour les pièces jointes", "No answer available": "Pas de réponse disponible", "Background color": "Couleur de fond", "Highlight color": "Couleur de surbrillance", - "Remove color": "Supprimer la couleur" + "Remove color": "Supprimer la couleur", + "Notifications": "Notifications", + "No notifications": "Aucune notification", + "No unread notifications": "Aucune notification non lue", + "All notifications": "Toutes les notifications", + "Unread only": "Non lues uniquement", + "Mark all as read": "Tout marquer comme lu", + "Mark as read": "Marquer comme lu", + "More options": "Plus d'options", + "mentioned you in a comment": "vous a mentionné dans un commentaire", + "commented on a page": "a commenté une page", + "resolved a comment": "a résolu un commentaire", + "mentioned you on a page": "vous a mentionné sur une page", + "Today": "Aujourd'hui", + "Yesterday": "Hier", + "This week": "Cette semaine", + "Older": "Plus ancien" } diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index 1e848aa6..50e07665 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Inserisci la data corrente", "Draw and sketch excalidraw diagrams": "Disegna e schizza diagrammi excalidraw", "Multiple": "Multiplo", + "Turn into": "Trasforma in", + "Text align": "Allinea testo", "Heading {{level}}": "Intestazione {{level}}", "Toggle title": "Attiva/disattiva titolo", "Write anything. Enter \"/\" for commands": "Scrivi qualcosa. Digita \"/\" per i comandi", @@ -582,13 +584,33 @@ "Ask AI": "Chiedi all'AI", "AI is thinking...": "L'AI sta pensando...", "Ask a question...": "Fai una domanda...", - "AI-powered search (Ask AI)": "Ricerca potenziata dall'AI (Chiedi all'AI)", + "AI Answers": "Risposte AI", + "AI-powered search (AI Answers)": "Ricerca con AI (Risposte AI)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La ricerca AI utilizza embeddings vettoriali per fornire capacità di ricerca semantica nel contenuto della tua area di lavoro.", "Toggle AI search": "Attiva/disattiva ricerca AI", + "Generative AI (Ask AI)": "AI generativa (Chiedi AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Abilita la generazione di contenuti con AI nell'editor. Consente agli utenti di generare, migliorare, tradurre e trasformare il testo.", + "Toggle generative AI": "Attiva/Disattiva AI generativa", "Sources": "Fonti", - "Ask AI not available for attachments": "Chiedere all'AI non è disponibile per gli allegati", + "AI Answers not available for attachments": "Risposte AI non disponibili per gli allegati", "No answer available": "Nessuna risposta disponibile", "Background color": "Colore di sfondo", "Highlight color": "Colore evidenziato", - "Remove color": "Rimuovi colore" + "Remove color": "Rimuovi colore", + "Notifications": "Notifiche", + "No notifications": "Nessuna notifica", + "No unread notifications": "Nessuna notifica non letta", + "All notifications": "Tutte le notifiche", + "Unread only": "Solo non lette", + "Mark all as read": "Segna tutto come letto", + "Mark as read": "Segna come letto", + "More options": "Altre opzioni", + "mentioned you in a comment": "ti ha menzionato in un commento", + "commented on a page": "ha commentato una pagina", + "resolved a comment": "ha risolto un commento", + "mentioned you on a page": "ti ha menzionato in una pagina", + "Today": "Oggi", + "Yesterday": "Ieri", + "This week": "Questa settimana", + "Older": "Più vecchie" } diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index 9402cdc5..ee5d77fc 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -355,6 +355,8 @@ "Insert current date": "現在の日付を挿入します", "Draw and sketch excalidraw diagrams": "Excalidraw 図を挿入します", "Multiple": "複数", + "Turn into": "変換する", + "Text align": "テキストの配置", "Heading {{level}}": "見出し {{level}}", "Toggle title": "タイトルの表示/非表示を切り替える", "Write anything. Enter \"/\" for commands": "文字を入力するか、「/」でコマンドを呼び出します", @@ -582,13 +584,33 @@ "Ask AI": "AIに質問する", "AI is thinking...": "AIが考え中...", "Ask a question...": "質問を入力...", - "AI-powered search (Ask AI)": "AIによる検索(AIに質問)", + "AI Answers": "AI回答", + "AI-powered search (AI Answers)": "AI搭載検索 (AI回答)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI検索はベクター埋め込みを使用してワークスペース全体の意味検索を実現します", "Toggle AI search": "AI検索を切り替え", + "Generative AI (Ask AI)": "生成AI (Ask AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "エディターでAIを活用したコンテンツ生成を有効にします。ユーザーがテキストの生成、改善、翻訳、および変換を行うことができます。", + "Toggle generative AI": "生成AIを切り替える", "Sources": "ソース", - "Ask AI not available for attachments": "添付ファイルにはAI質問は利用できません", + "AI Answers not available for attachments": "添付ファイルにはAI回答を利用できません", "No answer available": "回答がありません", "Background color": "背景色", "Highlight color": "ハイライト色", - "Remove color": "色を削除" + "Remove color": "色を削除", + "Notifications": "通知", + "No notifications": "通知なし", + "No unread notifications": "未読の通知はありません", + "All notifications": "すべての通知", + "Unread only": "未読のみ", + "Mark all as read": "すべてを既読にする", + "Mark as read": "既読にする", + "More options": "その他のオプション", + "mentioned you in a comment": "コメントであなたに言及しました", + "commented on a page": "ページにコメントしました", + "resolved a comment": "コメントを解決しました", + "mentioned you on a page": "ページ上であなたに言及しました", + "Today": "今日", + "Yesterday": "昨日", + "This week": "今週", + "Older": "以前のもの" } diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index d8ce59c9..fb339bad 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -355,6 +355,8 @@ "Insert current date": "현재 날짜 삽입", "Draw and sketch excalidraw diagrams": "Excalidraw diagram 그리기 및 스케치", "Multiple": "복제", + "Turn into": "변경하기", + "Text align": "텍스트 정렬", "Heading {{level}}": "제목 {{level}}", "Toggle title": "제목 토글", "Write anything. Enter \"/\" for commands": "아무거나 입력하세요. 명령어를 사용하려면 \"/\"를 입력하세요", @@ -582,13 +584,33 @@ "Ask AI": "AI에게 묻기", "AI is thinking...": "AI가 생각 중입니다...", "Ask a question...": "질문하세요...", - "AI-powered search (Ask AI)": "AI 지원 검색 (AI에게 묻기)", + "AI Answers": "AI 답변", + "AI-powered search (AI Answers)": "AI 구동 검색 (AI 답변)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI 검색은 벡터 임베딩을 사용하여 작업공간 콘텐츠에 대한 의미 검색 기능을 제공합니다.", "Toggle AI search": "AI 검색 전환", + "Generative AI (Ask AI)": "생성 AI (Ask AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "편집기에서 AI 구동 콘텐츠 생성을 활성화합니다. 사용자가 텍스트를 생성, 개선, 번역 및 변환할 수 있습니다.", + "Toggle generative AI": "생성 AI 토글", "Sources": "출처", - "Ask AI not available for attachments": "AI에게 묻기 기능은 첨부 파일에 대해 사용할 수 없습니다", + "AI Answers not available for attachments": "첨부 파일에 대해 AI 답변을 사용할 수 없습니다", "No answer available": "답변을 제공할 수 없습니다", "Background color": "배경 색", "Highlight color": "강조 색", - "Remove color": "색 제거" + "Remove color": "색 제거", + "Notifications": "알림", + "No notifications": "알림 없음", + "No unread notifications": "읽지 않은 알림 없음", + "All notifications": "모든 알림", + "Unread only": "읽지 않음만", + "Mark all as read": "모두 읽음으로 표시", + "Mark as read": "읽음으로 표시", + "More options": "추가 옵션", + "mentioned you in a comment": "댓글에서 당신을 언급했습니다", + "commented on a page": "페이지에 댓글을 달았습니다", + "resolved a comment": "댓글을 해결했습니다", + "mentioned you on a page": "페이지에서 당신을 언급했습니다", + "Today": "오늘", + "Yesterday": "어제", + "This week": "이번 주", + "Older": "이전" } diff --git a/apps/client/public/locales/nl-NL/translation.json b/apps/client/public/locales/nl-NL/translation.json index 036e8eeb..f8db0c36 100644 --- a/apps/client/public/locales/nl-NL/translation.json +++ b/apps/client/public/locales/nl-NL/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Huidige datum invoeren", "Draw and sketch excalidraw diagrams": "Teken en schets excalidraw diagrammen", "Multiple": "Meerdere", + "Turn into": "Omzetten naar", + "Text align": "Tekstuitlijning", "Heading {{level}}": "Kop {{level}}", "Toggle title": "Schakel titel in/uit", "Write anything. Enter \"/\" for commands": "Schrijf iets. Voer \"/\" in voor commando's", @@ -582,13 +584,33 @@ "Ask AI": "Vraag AI", "AI is thinking...": "AI is aan het nadenken...", "Ask a question...": "Stel een vraag...", - "AI-powered search (Ask AI)": "AI-ondersteunde zoekopdracht (Vraag AI)", + "AI Answers": "AI Antwoorden", + "AI-powered search (AI Answers)": "AI-gestuurde zoekopdracht (AI Antwoorden)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI-zoekopdracht maakt gebruik van vectorembeddings om semantische zoekmogelijkheden te bieden in uw werkruimte-inhoud.", "Toggle AI search": "Schakel AI-zoekopdracht in/uit", + "Generative AI (Ask AI)": "Generatieve AI (Vraag het AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Schakel AI-gestuurde inhoudsgeneratie in de editor in. Hiermee kunnen gebruikers tekst genereren, verbeteren, vertalen en transformeren.", + "Toggle generative AI": "Generatieve AI schakelen", "Sources": "Bronnen", - "Ask AI not available for attachments": "Vraag AI is niet beschikbaar voor bijlages", + "AI Answers not available for attachments": "AI Antwoorden niet beschikbaar voor bijlagen", "No answer available": "Geen antwoord beschikbaar", "Background color": "Achtergrondkleur", "Highlight color": "Markeerkleur", - "Remove color": "Kleur verwijderen" + "Remove color": "Kleur verwijderen", + "Notifications": "Meldingen", + "No notifications": "Geen meldingen", + "No unread notifications": "Geen ongelezen meldingen", + "All notifications": "Alle meldingen", + "Unread only": "Alleen ongelezen", + "Mark all as read": "Markeer alles als gelezen", + "Mark as read": "Markeer als gelezen", + "More options": "Meer opties", + "mentioned you in a comment": "noemde je in een reactie", + "commented on a page": "reageerde op een pagina", + "resolved a comment": "heeft een opmerking opgelost", + "mentioned you on a page": "noemde je op een pagina", + "Today": "Vandaag", + "Yesterday": "Gisteren", + "This week": "Deze week", + "Older": "Ouder" } diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 4bc8a70c..7164d5e4 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Insira a data atual", "Draw and sketch excalidraw diagrams": "Desenhe e esboce diagramas Excalidraw", "Multiple": "Múltiplo", + "Turn into": "Transformar em", + "Text align": "Alinhar texto", "Heading {{level}}": "Título {{level}}", "Toggle title": "Alternar título", "Write anything. Enter \"/\" for commands": "Escreva qualquer coisa. Digite \"/\" para comandos", @@ -582,13 +584,33 @@ "Ask AI": "Pergunte à IA", "AI is thinking...": "IA está pensando...", "Ask a question...": "Faça uma pergunta...", - "AI-powered search (Ask AI)": "Pesquisa com IA (Pergunte à IA)", + "AI Answers": "Respostas de IA", + "AI-powered search (AI Answers)": "Pesquisa com IA (Respostas de IA)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "A pesquisa IA usa vetores de incorporação para fornecer capacidades de pesquisa semântica em todo o conteúdo do seu espaço de trabalho.", "Toggle AI search": "Alternar pesquisa de IA", + "Generative AI (Ask AI)": "IA generativa (Perguntar à IA)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Habilitar geração de conteúdo com IA no editor. Permite aos usuários gerar, melhorar, traduzir e transformar texto.", + "Toggle generative AI": "Alternar IA generativa", "Sources": "Fontes", - "Ask AI not available for attachments": "Perguntar à IA não está disponível para anexos", + "AI Answers not available for attachments": "Respostas de IA não disponíveis para anexos", "No answer available": "Nenhuma resposta disponível", "Background color": "Cor de fundo", "Highlight color": "Cor de destaque", - "Remove color": "Remover cor" + "Remove color": "Remover cor", + "Notifications": "Notificações", + "No notifications": "Sem notificações", + "No unread notifications": "Sem notificações não lidas", + "All notifications": "Todas as notificações", + "Unread only": "Somente não lidas", + "Mark all as read": "Marcar todas como lidas", + "Mark as read": "Marcar como lida", + "More options": "Mais opções", + "mentioned you in a comment": "mencionou você em um comentário", + "commented on a page": "comentou em uma página", + "resolved a comment": "resolveu um comentário", + "mentioned you on a page": "mencionou você em uma página", + "Today": "Hoje", + "Yesterday": "Ontem", + "This week": "Esta semana", + "Older": "Mais antigo" } diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index ba39b1c8..6ec16a1b 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Вставить текущую дату", "Draw and sketch excalidraw diagrams": "Вставить и рисовать диаграммы Excalidraw", "Multiple": "Несколько", + "Turn into": "Преобразовать в", + "Text align": "Выравнивание текста", "Heading {{level}}": "Заголовок {{level}}", "Toggle title": "Переключить заголовок", "Write anything. Enter \"/\" for commands": "Начните писать. Введите \"/\" для списка команд", @@ -582,13 +584,33 @@ "Ask AI": "Спросить ИИ", "AI is thinking...": "ИИ обрабатывает запрос...", "Ask a question...": "Задайте вопрос...", - "AI-powered search (Ask AI)": "Поиск на базе ИИ (Спросить ИИ)", + "AI Answers": "Ответы ИИ", + "AI-powered search (AI Answers)": "Поиск на базе ИИ (Ответы ИИ)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Поиск ИИ использует векторные встраивания для обеспечения семантического поиска по содержимому вашего рабочего пространства.", "Toggle AI search": "Переключить поиск ИИ", + "Generative AI (Ask AI)": "Генеративный ИИ (Спросить ИИ)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Включите создание контента на базе ИИ в редакторе. Позволяет пользователям генерировать, улучшать, переводить и преобразовывать текст.", + "Toggle generative AI": "Переключить генеративный ИИ", "Sources": "Источники", - "Ask AI not available for attachments": "Функция \"Спросить ИИ\" недоступна для вложений", + "AI Answers not available for attachments": "Ответы ИИ недоступны для вложений", "No answer available": "Ответ недоступен", "Background color": "Цвет фона", "Highlight color": "Цвет выделения", - "Remove color": "Удалить цвет" + "Remove color": "Удалить цвет", + "Notifications": "Уведомления", + "No notifications": "Нет уведомлений", + "No unread notifications": "Нет непрочитанных уведомлений", + "All notifications": "Все уведомления", + "Unread only": "Только непрочитанные", + "Mark all as read": "Отметить все как прочитанные", + "Mark as read": "Отметить как прочитанное", + "More options": "Больше возможностей", + "mentioned you in a comment": "упомянул вас в комментарии", + "commented on a page": "прокомментировал на странице", + "resolved a comment": "разрешил комментарий", + "mentioned you on a page": "упомянул вас на странице", + "Today": "Сегодня", + "Yesterday": "Вчера", + "This week": "На этой неделе", + "Older": "Старше" } diff --git a/apps/client/public/locales/uk-UA/translation.json b/apps/client/public/locales/uk-UA/translation.json index 99407057..14b34816 100644 --- a/apps/client/public/locales/uk-UA/translation.json +++ b/apps/client/public/locales/uk-UA/translation.json @@ -355,6 +355,8 @@ "Insert current date": "Вставити поточну дату", "Draw and sketch excalidraw diagrams": "Вставити та малювати діаграми Excalidraw", "Multiple": "Декілька", + "Turn into": "Перетворити", + "Text align": "Вирівнювання тексту", "Heading {{level}}": "Заголовок {{level}}", "Toggle title": "Перемкнути заголовок", "Write anything. Enter \"/\" for commands": "Почніть писати. Введіть \"/\" для списку команд", @@ -582,13 +584,33 @@ "Ask AI": "Запитати ШІ", "AI is thinking...": "ШІ думає...", "Ask a question...": "Задайте питання...", - "AI-powered search (Ask AI)": "Пошук на базі ШІ (Запитати ШІ)", + "AI Answers": "Відповіді ШІ", + "AI-powered search (AI Answers)": "Пошук на базі ШІ (Відповіді ШІ)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Пошук з ШІ використовує векторні вбудовування для надання можливостей семантичного пошуку у вашому робочому вмісті.", "Toggle AI search": "Переключити пошук з ШІ", + "Generative AI (Ask AI)": "Генеративний ШІ (Запитати ШІ)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Увімкнути генерацію контенту за допомогою ШІ в редакторі. Дозволяє користувачам генерувати, покращувати, перекладати та трансформувати текст.", + "Toggle generative AI": "Переключити генеративний ШІ", "Sources": "Джерела", - "Ask AI not available for attachments": "Запитати ШІ недоступно для вкладень", + "AI Answers not available for attachments": "Відповіді ШІ недоступні для вкладень", "No answer available": "Відповідь недоступна", "Background color": "Колір фону", "Highlight color": "Колір підсвічування", - "Remove color": "Видалити колір" + "Remove color": "Видалити колір", + "Notifications": "Сповіщення", + "No notifications": "Немає сповіщень", + "No unread notifications": "Немає непрочитаних сповіщень", + "All notifications": "Усі сповіщення", + "Unread only": "Тільки непрочитані", + "Mark all as read": "Позначити все як прочитане", + "Mark as read": "Позначити як прочитане", + "More options": "Більше опцій", + "mentioned you in a comment": "згадали вас у коментарі", + "commented on a page": "прокоментували на сторінці", + "resolved a comment": "вирішили коментар", + "mentioned you on a page": "згадали вас на сторінці", + "Today": "Сьогодні", + "Yesterday": "Вчора", + "This week": "Цього тижня", + "Older": "Старіші" } diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index 0ea65913..d3877420 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -355,6 +355,8 @@ "Insert current date": "插入当前日期", "Draw and sketch excalidraw diagrams": "绘制 Excalidraw 图表", "Multiple": "多个", + "Turn into": "变成", + "Text align": "文本对齐", "Heading {{level}}": "{{level}} 级标题", "Toggle title": "切换标题", "Write anything. Enter \"/\" for commands": "开始编写内容,输入 \"/\" 以使用指令", @@ -582,13 +584,33 @@ "Ask AI": "询问AI", "AI is thinking...": "AI正在思考...", "Ask a question...": "提问...", - "AI-powered search (Ask AI)": "AI驱动的搜索(询问AI)", + "AI Answers": "AI答案", + "AI-powered search (AI Answers)": "AI驱动的搜索 (AI答案)", "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI搜索使用向量嵌入提供跨工作空间内容的语义搜索功能。", "Toggle AI search": "切换AI搜索", + "Generative AI (Ask AI)": "生成型AI (询问AI)", + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "在编辑器中启用AI驱动的内容生成。允许用户生成、改进、翻译和转换文本。", + "Toggle generative AI": "切换生成型AI", "Sources": "来源", - "Ask AI not available for attachments": "附件不支持询问AI", + "AI Answers not available for attachments": "AI答案不适用于附件", "No answer available": "无可用答案", "Background color": "背景颜色", "Highlight color": "突出显示颜色", - "Remove color": "移除颜色" + "Remove color": "移除颜色", + "Notifications": "通知", + "No notifications": "没有通知", + "No unread notifications": "没有未读通知", + "All notifications": "所有通知", + "Unread only": "仅未读", + "Mark all as read": "标记所有为已读", + "Mark as read": "标记为已读", + "More options": "更多选项", + "mentioned you in a comment": "在评论中提到你", + "commented on a page": "在页面上评论", + "resolved a comment": "解决了一个评论", + "mentioned you on a page": "在页面上提到你", + "Today": "今天", + "Yesterday": "昨天", + "This week": "本周", + "Older": "较早" } diff --git a/apps/client/src/components/layouts/global/app-header.tsx b/apps/client/src/components/layouts/global/app-header.tsx index eb1ca74f..58b76b71 100644 --- a/apps/client/src/components/layouts/global/app-header.tsx +++ b/apps/client/src/components/layouts/global/app-header.tsx @@ -22,6 +22,7 @@ import { searchSpotlight, shareSearchSpotlight, } from "@/features/search/constants.ts"; +import { NotificationPopover } from "@/features/notification/components/notification-popover.tsx"; const links = [{ link: APP_ROUTE.HOME, label: "Home" }]; @@ -97,6 +98,7 @@ export function AppHeader() { + {isCloud() && isTrial && trialDaysLeft !== 0 && ( { + const aiGenerateStreamMutation = useAiGenerateStreamMutation(); + const location = useLocation(); + const isSmBreakpoint = useMediaQuery("(max-width: 48em)"); + const [showAiMenu, setShowAiMenu] = useAtom(showAiMenuAtom); + const containerRef = useRef(null); + const inputRef = useRef(null); + const [prompt, setPrompt] = useState(""); + const [output, setOutput] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(-1); + const [activeCommandSet, setActiveCommandSet] = useState("main"); + const [lastAction, setLastAction] = useState(null); + const [menuPlacement, setMenuPlacement] = useState<{ + top: number; + left: number; + width: number; + }>({ + top: 0, + left: 0, + width: 0, + }); + const currentItems = useMemo(() => { + return commandItems[activeCommandSet].filter((item) => { + return item.name.toLowerCase().includes(prompt.toLowerCase()); + }); + }, [prompt, output, activeCommandSet]); + const updateMenuPlacement = useCallback(() => { + if (!editor || !showAiMenu) return; + + const { view } = editor; + const { to } = editor.state.selection; + const editorRect = view.dom.getBoundingClientRect(); + const cursorCoords = view.coordsAtPos(to); + const topOffset = 8; + const editorPadding = isSmBreakpoint ? 16 : 48; + + setMenuPlacement({ + top: cursorCoords.bottom + topOffset + window.scrollY, + left: editorRect.left + editorPadding + window.scrollX, + width: editorRect.width - editorPadding * 2, + }); + }, [editor, showAiMenu, isSmBreakpoint]); + const resetMenu = useCallback(() => { + setPrompt(""); + setOutput(""); + setActiveCommandSet("main"); + setLastAction(null); + aiGenerateStreamMutation.reset(); + }, [aiGenerateStreamMutation.reset]); + const debouncedUpdateMenuPlacement = useDebouncedCallback( + updateMenuPlacement, + 60, + ); + const handleGenerate = useCallback( + (item?: CommandItem) => { + if (!editor || isLoading) return; + + let command: CommandItem | null = item || null; + + if (!command) { + if (!prompt) return; + + command = { + id: "custom", + name: "Custom", + action: AiAction.CUSTOM, + prompt, + }; + } + + const { from, to } = editor.state.selection; + const slice = editor.state.doc.slice(from, to); + const serializer = DOMSerializer.fromSchema(editor.schema); + const fragment = serializer.serializeFragment(slice.content); + const wrapper = document.createElement("div"); + wrapper.appendChild(fragment); + const content = htmlToMarkdown(wrapper.innerHTML); + + setOutput(""); + setIsLoading(true); + aiGenerateStreamMutation.mutate({ + action: command.action, + prompt: command.prompt, + content, + onChunk: (chunk) => { + setOutput((output) => output + chunk.content); + }, + onComplete: () => { + setIsLoading(false); + setActiveCommandSet("result"); + }, + onError: () => { + setIsLoading(false); + resetMenu(); + }, + }); + setLastAction(command); + }, + [ + editor, + prompt, + isLoading, + aiGenerateStreamMutation.mutateAsync, + resetMenu, + ], + ); + const handleCommand = useCallback( + (item?: CommandItem) => { + setPrompt(""); + + if (!item) { + return handleGenerate(); + } + if (item.id === "back") { + return setActiveCommandSet("main"); + } + if (item.id === "result-replace") { + const chain = editor.chain().focus(); + + if (lastAction.action === AiAction.CONTINUE_WRITING) { + chain.setTextSelection(editor.state.selection.to); + } + + const html = (marked.parse(output) as string).trim(); + // Strip

wrapper for single-paragraph output to preserve inline context + const content = + html.startsWith("

") && + html.endsWith("

") && + html.lastIndexOf("

") === 0 + ? html.slice(3, -4) + : html; + + chain.insertContent(content).run(); + + return setShowAiMenu(false); + } + if (item.id === "result-insert-below") { + editor + .chain() + .focus() + .setTextSelection(editor.state.selection.to) + .insertContent(marked.parse(output)) + .run(); + + return setShowAiMenu(false); + } + if (item.id === "result-copy") { + navigator.clipboard.writeText(output); + + return setShowAiMenu(false); + } + if (item.id === "result-discard") { + setOutput(""); + + return resetMenu(); + } + if (item.id === "result-try-again" && lastAction) { + return handleGenerate(lastAction); + } + if (item.subCommandSet) { + return setActiveCommandSet(item.subCommandSet); + } + + return handleGenerate(item); + }, + [editor, output, lastAction, handleGenerate, resetMenu], + ); + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + const totalItems = currentItems.length; + const cycleSize = totalItems + 1; + + if (event.key === "Escape") { + return setShowAiMenu(false); + } + + if (event.key === "ArrowDown" || event.key === "ArrowUp") { + event.preventDefault(); + + return setSelectedIndex((selectedIndex) => { + const direction = event.key === "ArrowDown" ? 1 : -1; + const newIndex = selectedIndex + direction; + + if (newIndex < -1) return cycleSize - 1; + if (newIndex >= cycleSize) return 0; + + return newIndex; + }); + } + + if (event.key === "Enter") { + event.preventDefault(); + + return handleCommand(currentItems[selectedIndex]); + } + }, + [currentItems, selectedIndex], + ); + + useEffect(() => { + if (!editor) return; + + const handleClose = () => setShowAiMenu(false); + const observer = new ResizeObserver(() => { + debouncedUpdateMenuPlacement(); + }); + + updateMenuPlacement(); + editor.on("focus", handleClose); + editor.on("blur", handleClose); + window.addEventListener("resize", debouncedUpdateMenuPlacement); + window.addEventListener("scroll", debouncedUpdateMenuPlacement, true); + observer.observe(editor.view.dom); + + return () => { + editor.off("focus", handleClose); + editor.off("blur", handleClose); + window.removeEventListener("resize", debouncedUpdateMenuPlacement); + window.removeEventListener("scroll", debouncedUpdateMenuPlacement, true); + observer.disconnect(); + }; + }, [editor, updateMenuPlacement, debouncedUpdateMenuPlacement]); + + useEffect(() => { + setShowAiMenu(false); + }, [location]); + useEffect(() => { + if (showAiMenu) { + resetMenu(); + } + }, [showAiMenu, resetMenu]); + useEffect(() => { + // Focus input when menu opens or command set changes + requestAnimationFrame(() => { + inputRef.current?.focus({ preventScroll: true }); + }); + }, [showAiMenu, isLoading, currentItems]); + useEffect(() => { + if (!currentItems.length) { + setSelectedIndex(-1); + } + setSelectedIndex(prompt || activeCommandSet !== "main" ? 0 : -1); + }, [prompt, activeCommandSet, currentItems]); + + if (!showAiMenu) return null; + + return createPortal( +

+
+ + + setPrompt(e.currentTarget.value)} + rightSection={ + handleGenerate()} + > + + + } + onKeyDown={handleKeyDown} + /> + +
+
, + document.body, + ); +}; + +export { EditorAiMenu }; diff --git a/apps/client/src/ee/ai/components/editor/ai-menu/command-items.ts b/apps/client/src/ee/ai/components/editor/ai-menu/command-items.ts new file mode 100644 index 00000000..71eaa9cb --- /dev/null +++ b/apps/client/src/ee/ai/components/editor/ai-menu/command-items.ts @@ -0,0 +1,219 @@ +import { AiAction } from "@/ee/ai/types/ai.types.ts"; +import { + IconSparkles, + IconArrowsMaximize, + IconArrowsMinimize, + IconWriting, + IconHelp, + IconList, + IconMoodSmile, + IconLanguage, + IconTrash, + IconRefresh, + IconChevronLeft, + IconCheck, + IconArrowDownLeft, + IconCopy, + IconTextPlus, + IconAlignJustified, +} from "@tabler/icons-react"; + +interface CommandItem { + name: string; + id: string; + icon?: typeof IconSparkles; + action?: AiAction; + prompt?: string; + subCommandSet?: CommandSet; +} + +type CommandSet = "main" | "tone" | "translate" | "result"; + +const mainItems: CommandItem[] = [ + { + id: "improve-writing", + name: "Improve writing", + icon: IconSparkles, + action: AiAction.IMPROVE_WRITING, + }, + { + id: "fix-spelling-grammar", + name: "Fix spelling & grammar", + icon: IconCheck, + action: AiAction.FIX_SPELLING_GRAMMAR, + }, + { + id: "make-longer", + name: "Make longer", + icon: IconTextPlus, + action: AiAction.MAKE_LONGER, + }, + { + id: "make-shorter", + name: "Make shorter", + icon: IconAlignJustified, + action: AiAction.MAKE_SHORTER, + }, + { + id: "continue-writing", + name: "Continue writing", + icon: IconWriting, + action: AiAction.CONTINUE_WRITING, + }, + { + id: "explain", + name: "Explain", + icon: IconHelp, + action: AiAction.EXPLAIN, + }, + { + id: "summarize", + name: "Summarize", + icon: IconList, + action: AiAction.SUMMARIZE, + }, + { + id: "change-tone", + name: "Change tone", + icon: IconMoodSmile, + subCommandSet: "tone", + }, + { + id: "translate", + name: "Translate", + icon: IconLanguage, + subCommandSet: "translate", + }, +]; +const toneItems: CommandItem[] = [ + { + id: "back", + name: "Back", + icon: IconChevronLeft, + }, + { + id: "tone-professional", + name: "Professional", + icon: IconMoodSmile, + action: AiAction.CHANGE_TONE, + prompt: "Professional", + }, + { + id: "tone-casual", + name: "Casual", + icon: IconMoodSmile, + action: AiAction.CHANGE_TONE, + prompt: "Casual", + }, + { + id: "tone-friendly", + name: "Friendly", + icon: IconMoodSmile, + action: AiAction.CHANGE_TONE, + prompt: "Friendly", + }, +]; +const translateItems: CommandItem[] = [ + { + id: "back", + name: "Back", + icon: IconChevronLeft, + }, + { + id: "translate-english", + name: "English", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "English", + }, + { + id: "translate-spanish", + name: "Spanish", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Spanish", + }, + { + id: "translate-german", + name: "German", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "German", + }, + { + id: "translate-french", + name: "French", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "French", + }, + { + id: "translate-dutch", + name: "Dutch", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Dutch", + }, + { + id: "translate-portuguese", + name: "Portuguese", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Portuguese", + }, + { + id: "translate-italian", + name: "Italian", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Italian", + }, + { + id: "translate-japanese", + name: "Japanese", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Japanese", + }, + { + id: "translate-korean", + name: "Korean", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Korean", + }, + { + id: "translate-swedish", + name: "Swedish", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Swedish", + }, + { + id: "translate-chinese", + name: "Chinese (Simplified)", + icon: IconLanguage, + action: AiAction.TRANSLATE, + prompt: "Simplified Chinese", + }, +]; +const resultItems: CommandItem[] = [ + { id: "result-replace", name: "Replace", icon: IconCheck }, + { id: "result-insert-below", name: "Insert below", icon: IconArrowDownLeft }, + { id: "result-copy", name: "Copy", icon: IconCopy }, + { id: "result-discard", name: "Discard", icon: IconTrash }, + { + id: "result-try-again", + name: "Try again", + icon: IconRefresh, + }, +]; +const commandItems: Record = { + main: mainItems, + tone: toneItems, + translate: translateItems, + result: resultItems, +}; + +export type { CommandItem, CommandSet }; +export { commandItems }; diff --git a/apps/client/src/ee/ai/components/editor/ai-menu/command-selector.tsx b/apps/client/src/ee/ai/components/editor/ai-menu/command-selector.tsx new file mode 100644 index 00000000..8e66bee0 --- /dev/null +++ b/apps/client/src/ee/ai/components/editor/ai-menu/command-selector.tsx @@ -0,0 +1,72 @@ +import { Loader, Menu, ScrollArea } from "@mantine/core"; +import { IconChevronRight } from "@tabler/icons-react"; +import { ReactNode } from "react"; +import { CommandItem } from "./command-items.ts"; +import classes from "./ai-menu.module.css"; + +interface CommandSelectorProps { + selectedIndex: number; + + isLoading: boolean; + output: string; + currentItems: CommandItem[]; + children: ReactNode; + handleCommand(item: CommandItem): void; +} + +const CommandSelector = ({ + selectedIndex, + children, + isLoading, + output, + currentItems, + handleCommand, +}: CommandSelectorProps) => { + return ( + 0} + middlewares={{ flip: false }} + position="bottom-start" + offset={4} + width={250} + trapFocus={false} + shadow="lg" + > + {children} + + + {currentItems.map((item, index) => { + const isSelected = selectedIndex === index; + const showLoader = + isLoading && output === "" && !item.subCommandSet; + + return ( + + ) : item.icon ? ( + + ) : undefined + } + rightSection={ + item.subCommandSet ? ( + + ) : undefined + } + onClick={() => handleCommand(item)} + disabled={isLoading} + > + {item.name} + + ); + })} + + + + ); +}; + +export { CommandSelector }; diff --git a/apps/client/src/ee/ai/components/editor/ai-menu/result-preview.tsx b/apps/client/src/ee/ai/components/editor/ai-menu/result-preview.tsx new file mode 100644 index 00000000..d34682e3 --- /dev/null +++ b/apps/client/src/ee/ai/components/editor/ai-menu/result-preview.tsx @@ -0,0 +1,32 @@ +import { Loader, Paper, ScrollArea } from "@mantine/core"; +import DOMPurify from "dompurify"; +import { marked } from "marked"; +import { memo } from "react"; +import classes from "./ai-menu.module.css"; + +interface ResultPreviewProps { + output: string; + isLoading: boolean; +} +const ResultPreview = memo(({ output, isLoading }: ResultPreviewProps) => { + if (!output && !isLoading) return; + + const parsedOutput = `${marked.parse(output)}`; + + return ( + + +
+ {parsedOutput && ( +
+ )} + {isLoading && } +
+ + + ); +}); + +export { ResultPreview }; diff --git a/apps/client/src/ee/ai/components/enable-ai-search.tsx b/apps/client/src/ee/ai/components/enable-ai-search.tsx index 53b0a9bd..91242804 100644 --- a/apps/client/src/ee/ai/components/enable-ai-search.tsx +++ b/apps/client/src/ee/ai/components/enable-ai-search.tsx @@ -15,7 +15,7 @@ export default function EnableAiSearch() { <>
- {t("AI-powered search (Ask AI)")} + {t("AI-powered search (AI Answers)")} {t( "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.", diff --git a/apps/client/src/ee/ai/components/enable-generative-ai.tsx b/apps/client/src/ee/ai/components/enable-generative-ai.tsx new file mode 100644 index 00000000..9e09f4f0 --- /dev/null +++ b/apps/client/src/ee/ai/components/enable-generative-ai.tsx @@ -0,0 +1,48 @@ +import { Group, Text, Switch } from "@mantine/core"; +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; +import { notifications } from "@mantine/notifications"; +import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; + +export default function EnableGenerativeAi() { + const { t } = useTranslation(); + const [workspace, setWorkspace] = useAtom(workspaceAtom); + const [checked, setChecked] = useState(workspace?.settings?.ai?.generative); + const hasAccess = useIsCloudEE(); + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + try { + const updatedWorkspace = await updateWorkspace({ generativeAi: value }); + setChecked(value); + setWorkspace(updatedWorkspace); + } catch (err) { + notifications.show({ + message: err?.response?.data?.message, + color: "red", + }); + } + }; + + return ( + +
+ {t("Generative AI (Ask AI)")} + + {t( + "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.", + )} + +
+ + +
+ ); +} diff --git a/apps/client/src/ee/ai/hooks/use-ai-search.ts b/apps/client/src/ee/ai/hooks/use-ai-search.ts index f9c5aa88..03b24424 100644 --- a/apps/client/src/ee/ai/hooks/use-ai-search.ts +++ b/apps/client/src/ee/ai/hooks/use-ai-search.ts @@ -1,6 +1,6 @@ import { useMutation, UseMutationResult } from "@tanstack/react-query"; import { useState, useCallback } from "react"; -import { askAi, IAiSearchResponse } from "@/ee/ai/services/ai-search-service.ts"; +import { aiAnswers, IAiSearchResponse } from "@/ee/ai/services/ai-search-service.ts"; import { IPageSearchParams } from "@/features/search/types/search.types.ts"; // @ts-ignore @@ -26,7 +26,7 @@ export function useAiSearch(): UseAiSearchResult { const { contentType, ...apiParams } = params; - return await askAi(apiParams, (chunk) => { + return await aiAnswers(apiParams, (chunk) => { if (chunk.content) { setStreamingAnswer((prev) => prev + chunk.content); } diff --git a/apps/client/src/ee/ai/pages/ai-settings.tsx b/apps/client/src/ee/ai/pages/ai-settings.tsx index b9ab516d..441f91b9 100644 --- a/apps/client/src/ee/ai/pages/ai-settings.tsx +++ b/apps/client/src/ee/ai/pages/ai-settings.tsx @@ -1,25 +1,25 @@ import { Helmet } from "react-helmet-async"; -import { getAppName, isCloud } from "@/lib/config.ts"; +import { getAppName } from "@/lib/config.ts"; import SettingsTitle from "@/components/settings/settings-title.tsx"; import React from "react"; import useUserRole from "@/hooks/use-user-role.tsx"; import { useTranslation } from "react-i18next"; -import useLicense from "@/ee/hooks/use-license.tsx"; import EnableAiSearch from "@/ee/ai/components/enable-ai-search.tsx"; -import { Alert } from "@mantine/core"; +import EnableGenerativeAi from "@/ee/ai/components/enable-generative-ai.tsx"; +import { Alert, Stack } from "@mantine/core"; import { IconInfoCircle } from "@tabler/icons-react"; +import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; +import { isCloud } from "@/lib/config.ts"; export default function AiSettings() { const { t } = useTranslation(); const { isAdmin } = useUserRole(); - const { hasLicenseKey } = useLicense(); + const hasAccess = useIsCloudEE(); if (!isAdmin) { return null; } - const hasAccess = isCloud() || (!isCloud() && hasLicenseKey); - return ( <> @@ -40,7 +40,10 @@ export default function AiSettings() { )} - + + {!isCloud() && } + + ); } diff --git a/apps/client/src/ee/ai/services/ai-search-service.ts b/apps/client/src/ee/ai/services/ai-search-service.ts index 759a104a..8c2af64a 100644 --- a/apps/client/src/ee/ai/services/ai-search-service.ts +++ b/apps/client/src/ee/ai/services/ai-search-service.ts @@ -15,11 +15,11 @@ export interface IAiSearchResponse { }>; } -export async function askAi( +export async function aiAnswers( params: IPageSearchParams, onChunk?: (chunk: { content?: string; sources?: any[] }) => void, ): Promise { - const response = await fetch("/api/ai/ask", { + const response = await fetch("/api/ai/answers", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/client/src/ee/ai/services/ai-service.ts b/apps/client/src/ee/ai/services/ai-service.ts index f3634d59..88557ff1 100644 --- a/apps/client/src/ee/ai/services/ai-service.ts +++ b/apps/client/src/ee/ai/services/ai-service.ts @@ -43,13 +43,16 @@ export async function generateAiContentStream( } const processStream = async () => { + let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split("\n"); + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + + buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { @@ -66,7 +69,7 @@ export async function generateAiContentStream( onChunk(parsed); } } catch (e) { - // Ignore parse errors for incomplete chunks + // Skip invalid JSON } } } diff --git a/apps/client/src/ee/ai/types/ai.types.ts b/apps/client/src/ee/ai/types/ai.types.ts index a5fbc253..54778563 100644 --- a/apps/client/src/ee/ai/types/ai.types.ts +++ b/apps/client/src/ee/ai/types/ai.types.ts @@ -6,6 +6,7 @@ export enum AiAction { SIMPLIFY = "simplify", CHANGE_TONE = "change_tone", SUMMARIZE = "summarize", + EXPLAIN = "explain", CONTINUE_WRITING = "continue_writing", TRANSLATE = "translate", CUSTOM = "custom", diff --git a/apps/client/src/features/comment/components/comment-dialog.tsx b/apps/client/src/features/comment/components/comment-dialog.tsx index 7a4b55aa..7b0bb7bf 100644 --- a/apps/client/src/features/comment/components/comment-dialog.tsx +++ b/apps/client/src/features/comment/components/comment-dialog.tsx @@ -31,6 +31,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { const [currentUser] = useAtom(currentUserAtom); const [, setAsideState] = useAtom(asideStateAtom); const useClickOutsideRef = useClickOutside(() => { + if (document.querySelector("#mention")) return; handleDialogClose(); }); const createCommentMutation = useCreateCommentMutation(); @@ -105,6 +106,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) { position={{ bottom: 500, right: 50 }} withCloseButton withBorder + data-comment-dialog > diff --git a/apps/client/src/features/comment/components/comment-editor.tsx b/apps/client/src/features/comment/components/comment-editor.tsx index efb0c586..067391f4 100644 --- a/apps/client/src/features/comment/components/comment-editor.tsx +++ b/apps/client/src/features/comment/components/comment-editor.tsx @@ -1,14 +1,15 @@ -import { EditorContent, useEditor } from "@tiptap/react"; +import { EditorContent, ReactNodeViewRenderer, useEditor } from "@tiptap/react"; import { Placeholder } from "@tiptap/extension-placeholder"; -import { Underline } from "@tiptap/extension-underline"; -import { Link } from "@tiptap/extension-link"; import { StarterKit } from "@tiptap/starter-kit"; +import { Mention, LinkExtension } from "@docmost/editor-ext"; import classes from "./comment.module.css"; import { useFocusWithin } from "@mantine/hooks"; import clsx from "clsx"; import { forwardRef, useEffect, useImperativeHandle } from "react"; import { useTranslation } from "react-i18next"; import EmojiCommand from "@/features/editor/extensions/emoji-command"; +import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion"; +import MentionView from "@/features/editor/components/mention/mention-view"; interface CommentEditorProps { defaultContent?: any; @@ -39,13 +40,29 @@ const CommentEditor = forwardRef( StarterKit.configure({ gapcursor: false, dropcursor: false, + link: false, }), Placeholder.configure({ placeholder: placeholder || t("Reply..."), }), - Underline, - Link, + LinkExtension, EmojiCommand, + Mention.configure({ + suggestion: { + allowSpaces: true, + items: () => [], + // @ts-ignore + render: mentionRenderItems, + }, + HTMLAttributes: { + class: "mention", + }, + }).extend({ + addNodeView() { + this.editor.isInitialized = true; + return ReactNodeViewRenderer(MentionView); + }, + }), ], editorProps: { handleDOMEvents: { @@ -60,7 +77,8 @@ const CommentEditor = forwardRef( ].includes(event.key) ) { const emojiCommand = document.querySelector("#emoji-command"); - if (emojiCommand) { + const mentionPopup = document.querySelector("#mention"); + if (emojiCommand || mentionPopup) { return true; } } @@ -108,7 +126,11 @@ const CommentEditor = forwardRef( })); return ( -
+