From ce689eacbc259efe341abcdce915f7a8ad3d2835 Mon Sep 17 00:00:00 2001 From: Dylan Tientcheu Date: Wed, 14 Jan 2026 11:27:50 +0100 Subject: [PATCH] fix: add sp with translations --- docs/.vitepress/config.ts | 7 +- docs/es/config.ts | 78 ++++-- docs/fa/config.ts | 78 ++++-- docs/ja/config.ts | 78 ++++-- docs/ko/config.ts | 78 ++++-- docs/pt/config.ts | 78 ++++-- docs/ru/config.ts | 78 ++++-- docs/zh/config.ts | 76 ++++-- .../components/VPAlgoliaSearchBox.vue | 14 +- .../components/VPNavBarAskAiButton.vue | 5 +- .../components/VPNavBarSearch.vue | 19 +- .../components/VPNavBarSearchButton.vue | 83 ++++--- src/client/theme-default/support/docsearch.ts | 81 +++++- types/docsearch.d.ts | 233 ++---------------- 14 files changed, 611 insertions(+), 375 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 615fea4a8..341866cb9 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -118,13 +118,10 @@ export default defineConfig({ appId: '8J64VVRP8K', apiKey: '52f578a92b88ad6abde815aae2b0ad7c', indexName: 'vitepress', + mode: 'modal', askAi: { assistantId: 'YaVSonfX5bS8', - sidePanel: { - button: { - variant: 'inline' - } - } + sidePanel: true } } }, diff --git a/docs/es/config.ts b/docs/es/config.ts index 934c1dcf7..2d9b838d0 100644 --- a/docs/es/config.ts +++ b/docs/es/config.ts @@ -180,8 +180,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'Las respuestas son generadas por IA y pueden contener errores. Verifica las respuestas.', + relatedSourcesText: 'Fuentes relacionadas', + thinkingText: 'Pensando...', + copyButtonText: 'Copiar', + copyButtonCopiedText: '¡Copiado!', + copyButtonTitle: 'Copiar', + likeButtonTitle: 'Me gusta', + dislikeButtonTitle: 'No me gusta', + thanksForFeedbackText: '¡Gracias por tu opinión!', + preToolCallText: 'Buscando...', + duringToolCallText: 'Buscando ', + afterToolCallText: 'Búsqueda de' + } + return { placeholder: 'Buscar documentos', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'Asistente de IA', + conversationHistoryTitle: 'Historial de conversaciones', + newConversationText: 'Nueva conversación', + viewConversationHistoryText: 'Ver historial' + }, + promptForm: { + promptPlaceholderText: 'Pregunta algo...', + promptAnsweringText: 'Respondiendo...', + promptAskAnotherQuestionText: 'Hacer otra pregunta...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: 'Tu pregunta', + promptAriaLabelText: 'Campo de entrada de pregunta' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: 'Razonando...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: 'Respuesta detenida', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'Asistente de IA', + introductionText: '¿En qué puedo ayudarte hoy?' + }, + logo: { + poweredByText: 'Desarrollado por' + } + } + } + } + }, translations: { button: { buttonText: 'Buscar', @@ -226,22 +287,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'Preguntar a la IA: ' }, - askAiScreen: { - disclaimerText: - 'Las respuestas son generadas por IA y pueden contener errores. Verifica las respuestas.', - relatedSourcesText: 'Fuentes relacionadas', - thinkingText: 'Pensando...', - copyButtonText: 'Copiar', - copyButtonCopiedText: '¡Copiado!', - copyButtonTitle: 'Copiar', - likeButtonTitle: 'Me gusta', - dislikeButtonTitle: 'No me gusta', - thanksForFeedbackText: '¡Gracias por tu opinión!', - preToolCallText: 'Buscando...', - duringToolCallText: 'Buscando ', - afterToolCallText: 'Búsqueda de', - aggregatedToolCallText: 'Búsqueda de' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: 'Seleccionar', submitQuestionText: 'Enviar pregunta', diff --git a/docs/fa/config.ts b/docs/fa/config.ts index 97a005f04..030fc5995 100644 --- a/docs/fa/config.ts +++ b/docs/fa/config.ts @@ -181,8 +181,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'پاسخ‌ها توسط هوش مصنوعی تولید می‌شوند و ممکن است خطا داشته باشند. لطفاً بررسی کنید.', + relatedSourcesText: 'منابع مرتبط', + thinkingText: 'در حال پردازش...', + copyButtonText: 'کپی', + copyButtonCopiedText: 'کپی شد!', + copyButtonTitle: 'کپی', + likeButtonTitle: 'پسندیدم', + dislikeButtonTitle: 'نپسندیدم', + thanksForFeedbackText: 'از بازخورد شما سپاسگزاریم!', + preToolCallText: 'در حال جستجو...', + duringToolCallText: 'در حال جستجو برای ', + afterToolCallText: 'جستجو انجام شد' + } + return { placeholder: 'جستجوی مستندات', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'دستیار هوش مصنوعی', + conversationHistoryTitle: 'تاریخچه گفتگوها', + newConversationText: 'گفتگوی جدید', + viewConversationHistoryText: 'مشاهده تاریخچه' + }, + promptForm: { + promptPlaceholderText: 'سوالی بپرسید...', + promptAnsweringText: 'در حال پاسخ...', + promptAskAnotherQuestionText: 'سوال دیگری بپرسید...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: 'سوال شما', + promptAriaLabelText: 'فیلد ورودی سوال' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: 'در حال استدلال...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: 'پاسخ متوقف شد', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'دستیار هوش مصنوعی', + introductionText: 'امروز چگونه می‌توانم کمکتان کنم؟' + }, + logo: { + poweredByText: 'توسعه یافته توسط' + } + } + } + } + }, translations: { button: { buttonText: 'جستجو', @@ -224,22 +285,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'از هوش مصنوعی بپرسید: ' }, - askAiScreen: { - disclaimerText: - 'پاسخ‌ها توسط هوش مصنوعی تولید می‌شوند و ممکن است خطا داشته باشند. لطفاً بررسی کنید.', - relatedSourcesText: 'منابع مرتبط', - thinkingText: 'در حال پردازش...', - copyButtonText: 'کپی', - copyButtonCopiedText: 'کپی شد!', - copyButtonTitle: 'کپی', - likeButtonTitle: 'پسندیدم', - dislikeButtonTitle: 'نپسندیدم', - thanksForFeedbackText: 'از بازخورد شما سپاسگزاریم!', - preToolCallText: 'در حال جستجو...', - duringToolCallText: 'در حال جستجو برای ', - afterToolCallText: 'جستجو انجام شد', - aggregatedToolCallText: 'جستجو انجام شد' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: 'انتخاب', submitQuestionText: 'ارسال پرسش', diff --git a/docs/ja/config.ts b/docs/ja/config.ts index 4f6ef26db..4623e8492 100644 --- a/docs/ja/config.ts +++ b/docs/ja/config.ts @@ -148,8 +148,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'AI が生成した回答には誤りが含まれる可能性があります。必ずご確認ください。', + relatedSourcesText: '関連ソース', + thinkingText: '考え中...', + copyButtonText: 'コピー', + copyButtonCopiedText: 'コピーしました!', + copyButtonTitle: 'コピー', + likeButtonTitle: 'いいね', + dislikeButtonTitle: 'よくない', + thanksForFeedbackText: 'フィードバックありがとうございます!', + preToolCallText: '検索中...', + duringToolCallText: '検索中 ', + afterToolCallText: '検索完了' + } + return { placeholder: 'ドキュメントを検索', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'AI アシスタント', + conversationHistoryTitle: '会話履歴', + newConversationText: '新しい会話', + viewConversationHistoryText: '履歴を表示' + }, + promptForm: { + promptPlaceholderText: '質問してください...', + promptAnsweringText: '回答中...', + promptAskAnotherQuestionText: '別の質問をする...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: 'あなたの質問', + promptAriaLabelText: '質問入力フィールド' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: '推論中...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: '応答が停止されました', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'AI アシスタント', + introductionText: '今日は何をお手伝いできますか?' + }, + logo: { + poweredByText: '提供' + } + } + } + } + }, translations: { button: { buttonText: '検索', @@ -191,22 +252,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'AI に質問: ' }, - askAiScreen: { - disclaimerText: - 'AI が生成した回答には誤りが含まれる可能性があります。必ずご確認ください。', - relatedSourcesText: '関連ソース', - thinkingText: '考え中...', - copyButtonText: 'コピー', - copyButtonCopiedText: 'コピーしました!', - copyButtonTitle: 'コピー', - likeButtonTitle: 'いいね', - dislikeButtonTitle: 'よくない', - thanksForFeedbackText: 'フィードバックありがとうございます!', - preToolCallText: '検索中...', - duringToolCallText: '検索中 ', - afterToolCallText: '検索完了', - aggregatedToolCallText: '検索完了' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: '選択', submitQuestionText: '質問を送信', diff --git a/docs/ko/config.ts b/docs/ko/config.ts index b2fbecf39..f7d111f44 100644 --- a/docs/ko/config.ts +++ b/docs/ko/config.ts @@ -222,8 +222,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'AI가 생성한 답변으로 오류가 있을 수 있습니다. 반드시 확인하세요.', + relatedSourcesText: '관련 소스', + thinkingText: '생각 중...', + copyButtonText: '복사', + copyButtonCopiedText: '복사됨!', + copyButtonTitle: '복사', + likeButtonTitle: '좋아요', + dislikeButtonTitle: '싫어요', + thanksForFeedbackText: '피드백 감사합니다!', + preToolCallText: '검색 중...', + duringToolCallText: '검색 중 ', + afterToolCallText: '검색 완료' + } + return { placeholder: '문서 검색', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'AI 어시스턴트', + conversationHistoryTitle: '대화 기록', + newConversationText: '새 대화', + viewConversationHistoryText: '기록 보기' + }, + promptForm: { + promptPlaceholderText: '질문하세요...', + promptAnsweringText: '답변 작성 중...', + promptAskAnotherQuestionText: '다른 질문하기...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: '질문', + promptAriaLabelText: '질문 입력 필드' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: '추론 중...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: '답변 중지됨', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'AI 어시스턴트', + introductionText: '오늘 무엇을 도와드릴까요?' + }, + logo: { + poweredByText: '제공' + } + } + } + } + }, translations: { button: { buttonText: '검색', @@ -265,22 +326,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'AI에게 물어보기: ' }, - askAiScreen: { - disclaimerText: - 'AI가 생성한 답변으로 오류가 있을 수 있습니다. 반드시 확인하세요.', - relatedSourcesText: '관련 소스', - thinkingText: '생각 중...', - copyButtonText: '복사', - copyButtonCopiedText: '복사됨!', - copyButtonTitle: '복사', - likeButtonTitle: '좋아요', - dislikeButtonTitle: '싫어요', - thanksForFeedbackText: '피드백 감사합니다!', - preToolCallText: '검색 중...', - duringToolCallText: '검색 중 ', - afterToolCallText: '검색 완료', - aggregatedToolCallText: '검색 완료' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: '선택', submitQuestionText: '질문 보내기', diff --git a/docs/pt/config.ts b/docs/pt/config.ts index ae71473d4..57b664683 100644 --- a/docs/pt/config.ts +++ b/docs/pt/config.ts @@ -177,8 +177,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'As respostas são geradas por IA e podem conter erros. Verifique as respostas.', + relatedSourcesText: 'Fontes relacionadas', + thinkingText: 'Pensando...', + copyButtonText: 'Copiar', + copyButtonCopiedText: 'Copiado!', + copyButtonTitle: 'Copiar', + likeButtonTitle: 'Curtir', + dislikeButtonTitle: 'Não curtir', + thanksForFeedbackText: 'Obrigado pelo feedback!', + preToolCallText: 'Pesquisando...', + duringToolCallText: 'Pesquisando ', + afterToolCallText: 'Pesquisa concluída' + } + return { placeholder: 'Pesquisar documentos', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'Assistente de IA', + conversationHistoryTitle: 'Histórico de conversas', + newConversationText: 'Nova conversa', + viewConversationHistoryText: 'Ver histórico' + }, + promptForm: { + promptPlaceholderText: 'Pergunte algo...', + promptAnsweringText: 'Respondendo...', + promptAskAnotherQuestionText: 'Fazer outra pergunta...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: 'Sua pergunta', + promptAriaLabelText: 'Campo de entrada de pergunta' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: 'Raciocinando...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: 'Resposta interrompida', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'Assistente de IA', + introductionText: 'Como posso ajudá-lo hoje?' + }, + logo: { + poweredByText: 'Desenvolvido por' + } + } + } + } + }, translations: { button: { buttonText: 'Pesquisar', @@ -222,22 +283,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'Pergunte à IA: ' }, - askAiScreen: { - disclaimerText: - 'As respostas são geradas por IA e podem conter erros. Verifique as respostas.', - relatedSourcesText: 'Fontes relacionadas', - thinkingText: 'Pensando...', - copyButtonText: 'Copiar', - copyButtonCopiedText: 'Copiado!', - copyButtonTitle: 'Copiar', - likeButtonTitle: 'Curtir', - dislikeButtonTitle: 'Não curtir', - thanksForFeedbackText: 'Obrigado pelo feedback!', - preToolCallText: 'Pesquisando...', - duringToolCallText: 'Pesquisando ', - afterToolCallText: 'Pesquisa concluída', - aggregatedToolCallText: 'Pesquisa concluída' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: 'Selecionar', submitQuestionText: 'Enviar pergunta', diff --git a/docs/ru/config.ts b/docs/ru/config.ts index e196d193e..be646dead 100644 --- a/docs/ru/config.ts +++ b/docs/ru/config.ts @@ -177,8 +177,69 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: + 'Ответы генерируются ИИ и могут содержать ошибки. Проверяйте информацию.', + relatedSourcesText: 'Связанные источники', + thinkingText: 'Думаю...', + copyButtonText: 'Копировать', + copyButtonCopiedText: 'Скопировано!', + copyButtonTitle: 'Копировать', + likeButtonTitle: 'Нравится', + dislikeButtonTitle: 'Не нравится', + thanksForFeedbackText: 'Спасибо за отзыв!', + preToolCallText: 'Поиск...', + duringToolCallText: 'Поиск ', + afterToolCallText: 'Поиск завершён' + } + return { placeholder: 'Поиск в документации', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'ИИ-ассистент', + conversationHistoryTitle: 'История диалогов', + newConversationText: 'Новый диалог', + viewConversationHistoryText: 'Просмотр истории' + }, + promptForm: { + promptPlaceholderText: 'Задайте вопрос...', + promptAnsweringText: 'Формируется ответ...', + promptAskAnotherQuestionText: 'Задать ещё вопрос...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: 'Ваш вопрос', + promptAriaLabelText: 'Поле ввода вопроса' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: 'Рассуждение...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: 'Ответ остановлен', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'ИИ-ассистент', + introductionText: 'Чем могу помочь сегодня?' + }, + logo: { + poweredByText: 'Разработано' + } + } + } + } + }, translations: { button: { buttonText: 'Поиск', @@ -222,22 +283,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: 'Задайте вопрос ИИ: ' }, - askAiScreen: { - disclaimerText: - 'Ответы генерируются ИИ и могут содержать ошибки. Проверяйте информацию.', - relatedSourcesText: 'Связанные источники', - thinkingText: 'Думаю...', - copyButtonText: 'Копировать', - copyButtonCopiedText: 'Скопировано!', - copyButtonTitle: 'Копировать', - likeButtonTitle: 'Нравится', - dislikeButtonTitle: 'Не нравится', - thanksForFeedbackText: 'Спасибо за отзыв!', - preToolCallText: 'Поиск...', - duringToolCallText: 'Поиск ', - afterToolCallText: 'Поиск завершён', - aggregatedToolCallText: 'Поиск завершён' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: 'выбрать', submitQuestionText: 'Отправить вопрос', diff --git a/docs/zh/config.ts b/docs/zh/config.ts index f6bbdf021..d48be1ce7 100644 --- a/docs/zh/config.ts +++ b/docs/zh/config.ts @@ -170,8 +170,68 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { } function searchOptions(): Partial { + const modalAskAiTranslations = { + disclaimerText: '答案由 AI 生成,可能不准确,请自行验证。', + relatedSourcesText: '相关来源', + thinkingText: '思考中...', + copyButtonText: '复制', + copyButtonCopiedText: '已复制!', + copyButtonTitle: '复制', + likeButtonTitle: '赞', + dislikeButtonTitle: '踩', + thanksForFeedbackText: '感谢你的反馈!', + preToolCallText: '搜索中...', + duringToolCallText: '搜索 ', + afterToolCallText: '已搜索' + } + return { placeholder: '搜索文档', + askAi: { + sidePanel: { + panel: { + translations: { + header: { + title: 'AI 助手', + conversationHistoryTitle: '对话历史', + newConversationText: '新对话', + viewConversationHistoryText: '查看历史' + }, + promptForm: { + promptPlaceholderText: '提问...', + promptAnsweringText: '回答中...', + promptAskAnotherQuestionText: '继续提问...', + promptDisclaimerText: modalAskAiTranslations.disclaimerText, + promptLabelText: '你的问题', + promptAriaLabelText: '问题输入框' + }, + conversationScreen: { + conversationDisclaimer: modalAskAiTranslations.disclaimerText, + reasoningText: '推理中...', + thinkingText: modalAskAiTranslations.thinkingText, + relatedSourcesText: modalAskAiTranslations.relatedSourcesText, + stoppedStreamingText: '已停止', + copyButtonText: modalAskAiTranslations.copyButtonText, + copyButtonCopiedText: modalAskAiTranslations.copyButtonCopiedText, + likeButtonTitle: modalAskAiTranslations.likeButtonTitle, + dislikeButtonTitle: modalAskAiTranslations.dislikeButtonTitle, + thanksForFeedbackText: + modalAskAiTranslations.thanksForFeedbackText, + preToolCallText: modalAskAiTranslations.preToolCallText, + searchingText: modalAskAiTranslations.duringToolCallText, + toolCallResultText: modalAskAiTranslations.afterToolCallText + }, + newConversationScreen: { + titleText: 'AI 助手', + introductionText: '今天我能为你做些什么?' + }, + logo: { + poweredByText: '由' + } + } + } + } + }, translations: { button: { buttonText: '搜索文档', @@ -213,21 +273,7 @@ function searchOptions(): Partial { resultsScreen: { askAiPlaceholder: '向 AI 提问: ' }, - askAiScreen: { - disclaimerText: '答案由 AI 生成,可能不准确,请自行验证。', - relatedSourcesText: '相关来源', - thinkingText: '思考中...', - copyButtonText: '复制', - copyButtonCopiedText: '已复制!', - copyButtonTitle: '复制', - likeButtonTitle: '赞', - dislikeButtonTitle: '踩', - thanksForFeedbackText: '感谢你的反馈!', - preToolCallText: '搜索中...', - duringToolCallText: '搜索 ', - afterToolCallText: '已搜索', - aggregatedToolCallText: '已搜索' - }, + askAiScreen: modalAskAiTranslations, footer: { selectText: '选择', submitQuestionText: '提交问题', diff --git a/src/client/theme-default/components/VPAlgoliaSearchBox.vue b/src/client/theme-default/components/VPAlgoliaSearchBox.vue index 72d1c0a18..bc06fbd47 100644 --- a/src/client/theme-default/components/VPAlgoliaSearchBox.vue +++ b/src/client/theme-default/components/VPAlgoliaSearchBox.vue @@ -8,8 +8,10 @@ import { useData } from '../composables/data' import { buildAskAiConfig, mergeLangFacetFilters, + resolveMode, validateCredentials } from '../support/docsearch' +import type { DocSearchAskAi } from '../../../../types/docsearch' const props = defineProps<{ algolia: DefaultTheme.AlgoliaSearchOptions @@ -100,7 +102,7 @@ async function update() { ...options.searchParameters, facetFilters }, - askAi: askAi as DocSearchProps["askAi"] + askAi: askAi as DocSearchAskAi }) } @@ -108,12 +110,14 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { // Always tear down previous instances first (e.g. on locale changes) cleanup?.() + const { useSidePanel, mode } = resolveMode(userOptions) const askAi = userOptions.askAi const sidePanelConfig = askAi && typeof askAi === 'object' ? askAi.sidePanel : undefined - if (askAi && typeof askAi === 'object' && sidePanelConfig) { + if (useSidePanel && askAi && typeof askAi === 'object' && sidePanelConfig) { const { keyboardShortcuts, ...restConfig } = sidePanelConfig !== true ? sidePanelConfig : {} as SidepanelProps sidepanelInstance = sidepanel({ + ...restConfig, container: '#docsearch-sidepanel', indexName: askAi.indexName ?? userOptions.indexName, appId: askAi.appId ?? userOptions.appId, @@ -125,7 +129,6 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { setTimeout(() => { sidepanelInstance?.open() }, 0) } }, - ...restConfig, } as SidepanelProps) } @@ -145,8 +148,8 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { }) }, - // When sidepanel is enabled, intercept Ask AI events to open it instead (hybrid mode) - ...(sidepanelInstance && { + // When sidepanel is enabled (and not in modal mode), intercept Ask AI events to open it instead (hybrid mode) + ...(useSidePanel && sidepanelInstance && mode !== 'modal' && { interceptAskAiEvent: (initialMessage: { query: string; messageId?: string; suggestedQuestionId?: string }) => { docsearchInstance?.close() setTimeout(() => sidepanelInstance?.open(initialMessage), 0) @@ -186,4 +189,5 @@ function getRelativePath(url: string) { diff --git a/src/client/theme-default/components/VPNavBarAskAiButton.vue b/src/client/theme-default/components/VPNavBarAskAiButton.vue index af49db0ae..d9853fb87 100644 --- a/src/client/theme-default/components/VPNavBarAskAiButton.vue +++ b/src/client/theme-default/components/VPNavBarAskAiButton.vue @@ -1,6 +1,6 @@ diff --git a/src/client/theme-default/components/VPNavBarSearchButton.vue b/src/client/theme-default/components/VPNavBarSearchButton.vue index d8b81053e..c9955f6ba 100644 --- a/src/client/theme-default/components/VPNavBarSearchButton.vue +++ b/src/client/theme-default/components/VPNavBarSearchButton.vue @@ -14,17 +14,13 @@ const translate = createSearchTranslate(defaultTranslations) @@ -76,7 +72,7 @@ const translate = createSearchTranslate(defaultTranslations) fill: currentColor; } -.DocSearch-SearchBar + .DocSearch-Footer { +.DocSearch-SearchBar+.DocSearch-Footer { border-top-color: transparent; } @@ -86,37 +82,38 @@ const translate = createSearchTranslate(defaultTranslations) } .DocSearch-Button { + display: none; +} + +.VPDocSearch-Button { --docsearch-muted-color: var(--docsearch-text-color); --docsearch-searchbox-background: transparent; + padding: 4px 8px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + height: 36px; width: auto; - padding: 0px 8px; border: none; border-radius: 8px; } +.VPDocSearch-Button-Container { + display: inline-flex; + height: 100%; + gap: 6px; + align-items: center; +} + .DocSearch-Search-Icon { color: inherit !important; width: 20px; height: 20px; } -@media (min-width: 768px) { - .DocSearch-Button { - background-color: var(--vp-c-bg-alt); - color: var(--docsearch-secondary-text-color); - } - .DocSearch-Search-Icon { - width: 15px; - height: 15px; - } - - .DocSearch-Button-Placeholder { - font-size: 13px; - } -} - -.DocSearch-Button-Keys { +.VPDocSearch-Button-Keys { min-width: auto; margin: 0; padding: 4px 6px; @@ -126,19 +123,45 @@ const translate = createSearchTranslate(defaultTranslations) font-size: 12px; line-height: 1; color: var(--docsearch-key-color); + display: none; } -.DocSearch-Button-Keys > * { +.VPDocSearch-Button-Placeholder { display: none; } -.DocSearch-Button-Keys:after { +.VPDocSearch-Button-Keys>* { + display: none; +} + +.VPDocSearch-Button-Keys:after { /*rtl:ignore*/ direction: ltr; content: 'Ctrl K'; } -.mac .DocSearch-Button-Keys:after { +.mac .VPDocSearch-Button-Keys:after { content: '\2318 K'; } + +@media (min-width: 768px) { + .VPDocSearch-Button { + background-color: var(--vp-c-bg-alt); + color: var(--docsearch-secondary-text-color); + } + + .DocSearch-Search-Icon { + width: 15px; + height: 15px; + } + + .VPDocSearch-Button-Placeholder { + font-size: 13px; + display: block; + } + + .VPDocSearch-Button-Keys { + display: block; + } +} diff --git a/src/client/theme-default/support/docsearch.ts b/src/client/theme-default/support/docsearch.ts index eb5f265d5..2bcfdaa86 100644 --- a/src/client/theme-default/support/docsearch.ts +++ b/src/client/theme-default/support/docsearch.ts @@ -1,6 +1,6 @@ import type { DefaultTheme } from 'vitepress/theme' -export type FacetFilter = string | string[] +export type FacetFilter = string | string[] | FacetFilter[] export interface ValidatedCredentials { valid: boolean @@ -9,6 +9,76 @@ export interface ValidatedCredentials { indexName?: string } +export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal' + +export interface ResolvedMode { + mode: DocSearchMode + showKeywordSearch: boolean + useSidePanel: boolean +} + +/** + * Resolves the effective mode based on config and available features. + * + * - 'auto': infer hybrid vs sidePanel-only from provided config + * - 'sidePanel': force sidePanel-only even if keyword search is configured + * - 'hybrid': force hybrid (error if keyword search is not configured) + * - 'modal': force modal even if sidePanel is configured + */ +export function resolveMode( + options: Pick< + DefaultTheme.AlgoliaSearchOptions, + 'appId' | 'apiKey' | 'indexName' | 'askAi' | 'mode' + > +): ResolvedMode { + const mode = options.mode ?? 'auto' + const hasKeyword = hasKeywordSearch(options) + const askAi = options.askAi + const hasSidePanelConfig = Boolean( + askAi && typeof askAi === 'object' && askAi.sidePanel + ) + + switch (mode) { + case 'sidePanel': + // Force sidePanel-only - hide keyword search + return { + mode, + showKeywordSearch: false, + useSidePanel: true + } + + case 'hybrid': + // Force hybrid - keyword search must be configured + if (!hasKeyword) { + console.error( + '[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).' + ) + } + return { + mode, + showKeywordSearch: hasKeyword, + useSidePanel: true + } + + case 'modal': + // Force modal - don't use sidepanel for askai, even if configured + return { + mode, + showKeywordSearch: hasKeyword, + useSidePanel: true + } + + case 'auto': + default: + // Auto-detect based on config + return { + mode: 'auto', + showKeywordSearch: hasKeyword, + useSidePanel: hasSidePanelConfig + } + } +} + export function hasKeywordSearch( options: Pick< DefaultTheme.AlgoliaSearchOptions, @@ -44,7 +114,9 @@ export function mergeLangFacetFilters( .map((filter) => { if (Array.isArray(filter)) { // Handle nested arrays (OR conditions) - return filter.filter((f) => !f.startsWith('lang:')) + return filter.filter( + (f) => typeof f === 'string' && !f.startsWith('lang:') + ) } return filter }) @@ -101,7 +173,10 @@ export function buildAskAiConfig( const askAiFacetFiltersSource = askAiSearchParameters?.facetFilters ?? options.searchParameters?.facetFilters - const askAiFacetFilters = mergeLangFacetFilters(askAiFacetFiltersSource, lang) + const askAiFacetFilters = mergeLangFacetFilters( + askAiFacetFiltersSource as FacetFilter | FacetFilter[] | undefined, + lang + ) const mergedAskAiSearchParameters = { ...askAiSearchParameters, diff --git a/types/docsearch.d.ts b/types/docsearch.d.ts index c2c110483..f9a1125bc 100644 --- a/types/docsearch.d.ts +++ b/types/docsearch.d.ts @@ -1,3 +1,20 @@ +import { type DocSearchProps as DocSearchPropsJS } from '@docsearch/js' +import { type SidepanelProps as SidepanelPropsBase } from '@docsearch/sidepanel-js' + +/** + * Sidepanel translation configuration (for locale configs). + */ +export type SidepanelTranslations = NonNullable< + NonNullable['translations'] +> & { + panel?: NonNullable['translations']> +} + +/** + * Partial sidepanel props for locale configs where auth fields are inherited from the main config. + */ +export type SidepanelProps = Partial> + export interface DocSearchProps { /** * Keyword search (optional when using Ask AI side panel only). @@ -6,11 +23,11 @@ export interface DocSearchProps { apiKey?: string indexName?: string placeholder?: string - searchParameters?: SearchOptions + searchParameters?: DocSearchPropsJS['searchParameters'] disableUserPersonalization?: boolean initialQuery?: string insights?: boolean - translations?: DocSearchTranslations + translations?: DocSearchPropsJS['translations'] askAi?: DocSearchAskAi | string /** * Ask AI side panel integration mode. @@ -19,173 +36,9 @@ export interface DocSearchProps { * - 'auto': infer hybrid vs sidePanel-only from provided config * - 'sidePanel': force sidePanel-only even if keyword search is configured * - 'hybrid': force hybrid (error if keyword search is not configured) + * - 'modal': force modal even if sidePanel is configured (ask ai in modal stays in modal) */ - mode?: 'auto' | 'sidePanel' | 'hybrid' -} - -export interface SearchOptions { - query?: string - similarQuery?: string - facetFilters?: string | string[] - optionalFilters?: string | string[] - numericFilters?: string | string[] - tagFilters?: string | string[] - sumOrFiltersScores?: boolean - filters?: string - page?: number - hitsPerPage?: number - offset?: number - length?: number - attributesToHighlight?: string[] - attributesToSnippet?: string[] - attributesToRetrieve?: string[] - highlightPreTag?: string - highlightPostTag?: string - snippetEllipsisText?: string - restrictHighlightAndSnippetArrays?: boolean - facets?: string[] - maxValuesPerFacet?: number - facetingAfterDistinct?: boolean - minWordSizefor1Typo?: number - minWordSizefor2Typos?: number - allowTyposOnNumericTokens?: boolean - disableTypoToleranceOnAttributes?: string[] - queryType?: 'prefixLast' | 'prefixAll' | 'prefixNone' - removeWordsIfNoResults?: 'none' | 'lastWords' | 'firstWords' | 'allOptional' - advancedSyntax?: boolean - advancedSyntaxFeatures?: ('exactPhrase' | 'excludeWords')[] - optionalWords?: string | string[] - disableExactOnAttributes?: string[] - exactOnSingleWordQuery?: 'attribute' | 'none' | 'word' - alternativesAsExact?: ( - | 'ignorePlurals' - | 'singleWordSynonym' - | 'multiWordsSynonym' - )[] - enableRules?: boolean - ruleContexts?: string[] - distinct?: boolean | number - analytics?: boolean - analyticsTags?: string[] - synonyms?: boolean - replaceSynonymsInHighlight?: boolean - minProximity?: number - responseFields?: string[] - maxFacetHits?: number - percentileComputation?: boolean - clickAnalytics?: boolean - personalizationImpact?: number - enablePersonalization?: boolean - restrictSearchableAttributes?: string[] - sortFacetValuesBy?: 'count' | 'alpha' - typoTolerance?: boolean | 'min' | 'strict' - aroundLatLng?: string - aroundLatLngViaIP?: boolean - aroundRadius?: number | 'all' - aroundPrecision?: number | { from: number; value: number }[] - minimumAroundRadius?: number - insideBoundingBox?: number[][] - insidePolygon?: number[][] - ignorePlurals?: boolean | string[] - removeStopWords?: boolean | string[] - naturalLanguages?: string[] - getRankingInfo?: boolean - userToken?: string - enableABTest?: boolean - decompoundQuery?: boolean - relevancyStrictness?: number -} - -export interface DocSearchTranslations { - button?: ButtonTranslations - modal?: ModalTranslations -} - -export interface ButtonTranslations { - buttonText?: string - buttonAriaLabel?: string -} - -export interface ModalTranslations extends ScreenStateTranslations { - searchBox?: SearchBoxTranslations - footer?: FooterTranslations -} - -export interface ScreenStateTranslations { - errorScreen?: ErrorScreenTranslations - startScreen?: StartScreenTranslations - resultsScreen?: ResultsScreenTranslations - noResultsScreen?: NoResultsScreenTranslations - askAiScreen?: AskAiScreenTranslations -} - -export interface SearchBoxTranslations { - clearButtonTitle?: string - clearButtonAriaLabel?: string - closeButtonText?: string - closeButtonAriaLabel?: string - placeholderText?: string - placeholderTextAskAi?: string - searchInputLabel?: string - placeholderTextAskAiStreaming?: string - backToKeywordSearchButtonText?: string - backToKeywordSearchButtonAriaLabel?: string -} - -export interface FooterTranslations { - selectText?: string - submitQuestionText?: string - selectKeyAriaLabel?: string - navigateText?: string - navigateUpKeyAriaLabel?: string - backToSearchText?: string - navigateDownKeyAriaLabel?: string - closeText?: string - closeKeyAriaLabel?: string - poweredByText?: string -} - -export interface ErrorScreenTranslations { - titleText?: string - helpText?: string -} - -export interface StartScreenTranslations { - recentSearchesTitle?: string - noRecentSearchesText?: string - saveRecentSearchButtonTitle?: string - removeRecentSearchButtonTitle?: string - favoriteSearchesTitle?: string - removeFavoriteSearchButtonTitle?: string - recentConversationsTitle?: string - removeRecentConversationButtonTitle?: string -} - -export interface ResultsScreenTranslations { - askAiPlaceholder?: string -} - -export interface NoResultsScreenTranslations { - noResultsText?: string - suggestedQueryText?: string - reportMissingResultsText?: string - reportMissingResultsLinkText?: string -} - -export interface AskAiScreenTranslations { - disclaimerText?: string - relatedSourcesText?: string - thinkingText?: string - copyButtonText?: string - copyButtonCopiedText?: string - copyButtonTitle?: string - likeButtonTitle?: string - dislikeButtonTitle?: string - thanksForFeedbackText?: string - preToolCallText?: string - duringToolCallText?: string - afterToolCallText?: string - aggregatedToolCallText?: string + mode?: 'auto' | 'sidePanel' | 'hybrid' | 'modal' } export interface DocSearchAskAi { @@ -206,13 +59,14 @@ export interface DocSearchAskAi { appId?: string /** * The assistant ID to use for the ask AI feature. + * Optional in locale configs where it's inherited from the main config. */ - assistantId: string | null + assistantId?: string | null /** * The search parameters to use for the ask AI feature. */ searchParameters?: Pick< - SearchOptions, + DocSearchPropsJS['searchParameters'], | 'facetFilters' | 'filters' | 'attributesToRetrieve' @@ -226,42 +80,5 @@ export interface DocSearchAskAi { /** * Ask AI side panel configuration. */ - sidePanel?: boolean | SidePanelConfig -} - -/** - * Sidepanel configuration (flat, mirrors @docsearch/sidepanel-js API). - * @see https://docsearch.algolia.com/docs/sidepanel/api-reference - */ -export interface SidePanelConfig { - /** - * Default: 'floating' - */ - variant?: 'floating' | 'inline' - /** - * Default: 'right' - */ - side?: 'right' | 'left' - /** - * Default: '360px' - */ - width?: number | string - /** - * Default: '580px' - */ - expandedWidth?: number | string - /** - * Enables displaying suggested questions on new conversation screen. - */ - suggestedQuestions?: boolean - /** - * Default: {'Ctrl/Cmd+I': true} - */ - keyboardShortcuts?: { - 'Ctrl/Cmd+I'?: boolean - } - /** - * Default: 'light' - */ - theme?: 'light' | 'dark' + sidePanel?: boolean | SidepanelProps }