Merge pull request #1607 from ManishMadan2882/main

Perfecting Settings/Documents
This commit is contained in:
Alex
2025-01-28 10:08:32 +00:00
committed by GitHub
11 changed files with 405 additions and 389 deletions

View File

@@ -1478,90 +1478,17 @@ class GetFeedbackAnalytics(Resource):
)
except Exception as err:
return make_response(jsonify({"success": False, "error": str(err)}), 400)
end_date = datetime.datetime.now(datetime.timezone.utc)
if filter_option == "last_hour":
start_date = end_date - datetime.timedelta(hours=1)
group_format = "%Y-%m-%d %H:%M:00"
group_stage_1 = {
"$group": {
"_id": {
"minute": {
"$dateToString": {
"format": group_format,
"date": "$timestamp",
}
},
"feedback": "$feedback",
},
"count": {"$sum": 1},
}
}
group_stage_2 = {
"$group": {
"_id": "$_id.minute",
"likes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "LIKE"]},
"$count",
0,
]
}
},
"dislikes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "DISLIKE"]},
"$count",
0,
]
}
},
}
}
date_field = {"$dateToString": {"format": group_format, "date": "$date"}}
elif filter_option == "last_24_hour":
start_date = end_date - datetime.timedelta(hours=24)
group_format = "%Y-%m-%d %H:00"
group_stage_1 = {
"$group": {
"_id": {
"hour": {
"$dateToString": {
"format": group_format,
"date": "$timestamp",
}
},
"feedback": "$feedback",
},
"count": {"$sum": 1},
}
}
group_stage_2 = {
"$group": {
"_id": "$_id.hour",
"likes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "LIKE"]},
"$count",
0,
]
}
},
"dislikes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "DISLIKE"]},
"$count",
0,
]
}
},
}
}
date_field = {"$dateToString": {"format": group_format, "date": "$date"}}
else:
if filter_option in ["last_7_days", "last_15_days", "last_30_days"]:
filter_days = (
@@ -1579,61 +1506,59 @@ class GetFeedbackAnalytics(Resource):
hour=23, minute=59, second=59, microsecond=999999
)
group_format = "%Y-%m-%d"
group_stage_1 = {
"$group": {
"_id": {
"day": {
"$dateToString": {
"format": group_format,
"date": "$timestamp",
}
},
"feedback": "$feedback",
},
"count": {"$sum": 1},
}
}
group_stage_2 = {
"$group": {
"_id": "$_id.day",
"likes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "LIKE"]},
"$count",
0,
]
}
},
"dislikes": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "DISLIKE"]},
"$count",
0,
]
}
},
}
}
date_field = {"$dateToString": {"format": group_format, "date": "$date"}}
try:
match_stage = {
"$match": {
"timestamp": {"$gte": start_date, "$lte": end_date},
"date": {"$gte": start_date, "$lte": end_date},
"queries": {"$exists": True, "$ne": []},
}
}
if api_key:
match_stage["$match"]["api_key"] = api_key
feedback_data = feedback_collection.aggregate(
[
match_stage,
group_stage_1,
group_stage_2,
{"$sort": {"_id": 1}},
]
)
# Unwind the queries array to process each query separately
pipeline = [
match_stage,
{"$unwind": "$queries"},
{"$match": {"queries.feedback": {"$exists": True}}},
{
"$group": {
"_id": {
"time": date_field,
"feedback": "$queries.feedback"
},
"count": {"$sum": 1}
}
},
{
"$group": {
"_id": "$_id.time",
"positive": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "LIKE"]},
"$count",
0
]
}
},
"negative": {
"$sum": {
"$cond": [
{"$eq": ["$_id.feedback", "DISLIKE"]},
"$count",
0
]
}
}
}
},
{"$sort": {"_id": 1}}
]
feedback_data = conversations_collection.aggregate(pipeline)
if filter_option == "last_hour":
intervals = generate_minute_range(start_date, end_date)
@@ -1648,8 +1573,8 @@ class GetFeedbackAnalytics(Resource):
for entry in feedback_data:
daily_feedback[entry["_id"]] = {
"positive": entry["likes"],
"negative": entry["dislikes"],
"positive": entry["positive"],
"negative": entry["negative"]
}
except Exception as err:
@@ -2105,4 +2030,4 @@ class DeleteTool(Resource):
except Exception as err:
return {"success": False, "error": str(err)}, 400
return {"success": True}, 200
return {"success": True}, 200

View File

@@ -53,6 +53,7 @@
"default": "Default"
},
"documents": {
"title": "This table contains all the documents that are available to you and those you have uploaded",
"label": "Documents",
"name": "Document Name",
"date": "Vector Date",
@@ -70,7 +71,8 @@
"weekly": "Weekly",
"monthly": "Monthly"
},
"actions": "Actions"
"actions": "Actions",
"deleteWarning": "Are you sure you want to delete \"{{name}}\"?"
},
"apiKeys": {
"label": "Chatbots",
@@ -78,7 +80,8 @@
"key": "API Key",
"sourceDoc": "Source Document",
"createNew": "Create New",
"noData": "No existing Chatbots"
"noData": "No existing Chatbots",
"deleteConfirmation": "Are you sure you want to delete the API key '{{name}}'?"
},
"analytics": {
"label": "Analytics",

View File

@@ -53,6 +53,7 @@
"default": "Predeterminado"
},
"documents": {
"title": "Esta tabla contiene todos los documentos que están disponibles para ti y los que has subido",
"label": "Documentos",
"name": "Nombre del Documento",
"date": "Fecha de Vector",
@@ -70,7 +71,8 @@
"weekly": "Semanal",
"monthly": "Mensual"
},
"actions": "Acciones"
"actions": "Acciones",
"deleteWarning": "¿Estás seguro de que deseas eliminar \"{{name}}\"?"
},
"apiKeys": {
"label": "Chatbots",
@@ -78,7 +80,8 @@
"key": "Clave de API",
"sourceDoc": "Documento Fuente",
"createNew": "Crear Nuevo",
"noData": "No hay chatbots existentes"
"noData": "No hay chatbots existentes",
"deleteConfirmation": "¿Estás seguro de que quieres eliminar la clave API '{{name}}'?"
},
"analytics": {
"label": "Analítica",

View File

@@ -70,15 +70,17 @@
"weekly": "毎週",
"monthly": "毎月"
},
"actions": "アクション"
"actions": "アクション",
"deleteWarning": "\"{{name}}\"を削除してもよろしいですか?"
},
"apiKeys": {
"label": "APIキー",
"label": "チャットボット",
"name": "名前",
"key": "APIキー",
"sourceDoc": "ソースドキュメント",
"createNew": "新規作成",
"noData": "既存のAPIキーがありません"
"noData": "既存のチャットボットはありません",
"deleteConfirmation": "APIキー '{{name}}' を削除してもよろしいですか?"
},
"analytics": {
"label": "分析",

View File

@@ -53,6 +53,7 @@
"default": "По умолчанию"
},
"documents": {
"title": "Эта таблица содержит все документы, которые доступны вам и те, которые вы загрузили",
"label": "Документы",
"name": "Название документа",
"date": "Дата вектора",
@@ -70,7 +71,8 @@
"weekly": "Еженедельно",
"monthly": "Ежемесячно"
},
"actions": "Действия"
"actions": "Действия",
"deleteWarning": "Вы уверены, что хотите удалить \"{{name}}\"?"
},
"apiKeys": {
"label": "API ключи",
@@ -78,7 +80,8 @@
"key": "API ключ",
"sourceDoc": "Источник документа",
"createNew": "Создать новый",
"noData": "Нет существующих API ключей"
"noData": "Нет существующих чатботов",
"deleteConfirmation": "Вы уверены, что хотите удалить API ключ '{{name}}'?"
},
"analytics": {
"label": "Аналитика",

View File

@@ -53,6 +53,7 @@
"default": "預設"
},
"documents": {
"title": "此表格包含所有可供您使用的文件以及您上傳的文件",
"label": "文件",
"name": "文件名稱",
"date": "向量日期",
@@ -70,15 +71,17 @@
"weekly": "每週",
"monthly": "每月"
},
"actions": "操作"
"actions": "操作",
"deleteWarning": "您確定要刪除 \"{{name}}\" 嗎?"
},
"apiKeys": {
"label": "聊天機器人",
"name": "名稱",
"key": "API 金鑰",
"sourceDoc": "來源文件",
"createNew": "新增",
"noData": "沒有現有的聊天機器人"
"createNew": "建立新的",
"noData": "沒有現有的聊天機器人",
"deleteConfirmation": "您確定要刪除 API 金鑰 '{{name}}' 嗎?"
},
"analytics": {
"label": "分析",

View File

@@ -53,7 +53,8 @@
"default": "默认"
},
"documents": {
"label": "文件",
"title": "此表格包含所有可供您使用的文档以及您上传的文档",
"label": "文档",
"name": "文件名称",
"date": "向量日期",
"type": "类型",
@@ -70,7 +71,8 @@
"weekly": "每周",
"monthly": "每月"
},
"actions": "操作"
"actions": "操作",
"deleteWarning": "您确定要删除 \"{{name}}\" 吗?"
},
"apiKeys": {
"label": "聊天机器人",
@@ -78,7 +80,8 @@
"key": "API 密钥",
"sourceDoc": "源文档",
"createNew": "创建新的",
"noData": "没有现有的聊天机器人"
"noData": "没有现有的聊天机器人",
"deleteConfirmation": "您确定要删除 API 密钥 '{{name}}' 吗?"
},
"analytics": {
"label": "分析",
@@ -111,7 +114,7 @@
"searchPlaceholder": "搜索...",
"addTool": "添加工具",
"noToolsFound": "未找到工具",
"selectToolSetup": "选择要设置的工具" ,
"selectToolSetup": "选择要设置的工具",
"settingsIconAlt": "设置图标",
"configureToolAria": "配置 {toolName}",
"toggleToolAria": "切换 {toolName}"

View File

@@ -5,6 +5,7 @@ import userService from '../api/services/userService';
import Trash from '../assets/trash.svg';
import CreateAPIKeyModal from '../modals/CreateAPIKeyModal';
import SaveAPIKeyModal from '../modals/SaveAPIKeyModal';
import ConfirmationModal from '../modals/ConfirmationModal';
import { APIKeyData } from './types';
import SkeletonLoader from '../components/SkeletonLoader';
@@ -15,6 +16,10 @@ export default function APIKeys() {
const [newKey, setNewKey] = React.useState('');
const [apiKeys, setApiKeys] = React.useState<APIKeyData[]>([]);
const [loading, setLoading] = useState(true);
const [keyToDelete, setKeyToDelete] = useState<{
id: string;
name: string;
} | null>(null);
const handleFetchKeys = async () => {
setLoading(true);
@@ -44,6 +49,7 @@ export default function APIKeys() {
.then((data) => {
data.success === true &&
setApiKeys((previous) => previous.filter((elem) => elem.id !== id));
setKeyToDelete(null);
})
.catch((error) => {
console.error(error);
@@ -104,6 +110,18 @@ export default function APIKeys() {
close={() => setSaveKeyModal(false)}
/>
)}
{keyToDelete && (
<ConfirmationModal
message={t('settings.apiKeys.deleteConfirmation', {
name: keyToDelete.name,
})}
modalState="ACTIVE"
setModalState={() => setKeyToDelete(null)}
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={() => handleDeleteKey(keyToDelete.id)}
handleCancel={() => setKeyToDelete(null)}
/>
)}
<div className="mt-[27px] w-full">
<div className="w-full overflow-x-auto">
{loading ? (
@@ -157,7 +175,12 @@ export default function APIKeys() {
alt={`Delete ${element.name}`}
className="h-4 w-4 cursor-pointer hover:opacity-50"
id={`img-${index}`}
onClick={() => handleDeleteKey(element.id)}
onClick={() =>
setKeyToDelete({
id: element.id,
name: element.name,
})
}
/>
</td>
</tr>

View File

@@ -15,7 +15,8 @@ import { Doc, DocumentsProps, ActiveState } from '../models/misc'; // Ensure Act
import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi';
import { setSourceDocs } from '../preferences/preferenceSlice';
import { setPaginatedDocuments } from '../preferences/preferenceSlice';
import { truncate } from '../utils/stringUtils';
import { formatDate } from '../utils/dateTimeUtils';
import ConfirmationModal from '../modals/ConfirmationModal';
// Utility function to format numbers
const formatTokens = (tokens: number): string => {
@@ -85,6 +86,7 @@ const Documents: React.FC<DocumentsProps> = ({
setSortField(newSortField);
setSortOrder(newSortOrder);
}
setLoading(true);
getDocsWithPagination(
newSortField,
@@ -110,7 +112,6 @@ const Documents: React.FC<DocumentsProps> = ({
userService
.manageSync({ source_id: doc.id, sync_frequency })
.then(() => {
// First, fetch the updated source docs
return getDocs();
})
.then((data) => {
@@ -134,177 +135,184 @@ const Documents: React.FC<DocumentsProps> = ({
});
};
useEffect(() => {
if (modalState === 'INACTIVE') {
refreshDocs(sortField, currentPage, rowsPerPage);
const [documentToDelete, setDocumentToDelete] = useState<{
index: number;
document: Doc;
} | null>(null);
const [deleteModalState, setDeleteModalState] =
useState<ActiveState>('INACTIVE');
const handleDeleteConfirmation = (index: number, document: Doc) => {
setDocumentToDelete({ index, document });
setDeleteModalState('ACTIVE');
};
const handleConfirmedDelete = () => {
if (documentToDelete) {
handleDeleteDocument(documentToDelete.index, documentToDelete.document);
setDeleteModalState('INACTIVE');
setDocumentToDelete(null);
}
}, [modalState]);
};
useEffect(() => {
// undefine to prevent reset the sort order
refreshDocs(undefined, 1, rowsPerPage);
}, [searchTerm]);
return (
<div className="mt-8">
<div className="flex flex-col relative">
<div className="z-10 w-full overflow-x-auto">
<div className="my-3 flex justify-between items-center">
<div className="p-1">
<label htmlFor="document-search-input" className="sr-only">
{t('settings.documents.searchPlaceholder')}
</label>
<Input
maxLength={256}
placeholder={t('settings.documents.searchPlaceholder')}
name="Document-search-input"
type="text"
id="document-search-input"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
/>
</div>
<button
className="rounded-full w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
title={t('settings.documents.addNew')}
onClick={() => {
setIsOnboarding(false);
setModalState('ACTIVE');
}}
>
{t('settings.documents.addNew')}
</button>
</div>
{loading ? (
<SkeletonLoader count={1} />
) : (
<div className="flex flex-col">
<div className="flex-grow">
<div className="dark:border-silver/40 border-silver rounded-md border overflow-auto">
<table className="min-w-full divide-y divide-silver dark:divide-silver/40 text-xs sm:text-sm ">
<thead>
<tr className="text-nowrap">
<th className="px-5 py-3 text-start font-medium text-gray-700 dark:text-gray-50 uppercase w-96">
{t('settings.documents.name')}
</th>
<th className="px-5 py-3 text-start font-medium text-gray-700 dark:text-gray-50 uppercase">
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th
scope="col"
className="px-5 py-2 text-center font-medium text-gray-700 dark:text-gray-50 uppercase"
>
<div className="flex justify-center items-center">
{t('settings.documents.tokenUsage')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
{/*}
<th className="px-5 py-2 text-start text-sm font-medium text-gray-700 dark:text-gray-50 uppercase">
<div className="flex justify-center items-center">
{t('settings.documents.type')}
</div>
</th>
*/}
<th
scope="col"
className="px-6 py-2 text-start font-medium text-gray-700 dark:text-gray-50 uppercase sr-only"
aria-label={t('settings.documents.actions')}
>
{t('settings.documents.actions')}
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
{!currentDocuments?.length && (
<tr>
<td
colSpan={4}
className="!py-4 text-gray-800 dark:text-neutral-200 text-center"
>
{t('settings.documents.noData')}
</td>
</tr>
)}
{Array.isArray(currentDocuments) &&
currentDocuments.map((document, index) => (
<tr key={index} className="text-nowrap font-normal">
<td
title={document.name}
className="px-6 py-4 whitespace-nowrap text-left font-medium text-gray-800 dark:text-neutral-200"
>
{truncate(document.name, 50)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center font-medium text-gray-800 dark:text-neutral-200">
{document.date}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center font-medium text-gray-800 dark:text-neutral-200">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
{/*}
<td className="px-6 py-4 whitespace-nowrap text-center text-sm font-medium text-gray-800 dark:text-neutral-200">
{document.type === 'remote'
? 'Pre-loaded'
: 'Private'}
</td>
*/}
<td className="px-6 py-4 whitespace-nowrap text-left text-sm font-medium flex">
<div className="min-w-[150px] flex flex-row items-center ml-auto gap-10">
{document.type !== 'remote' && (
<img
src={Trash}
alt={t('convTile.delete')}
className="h-4 w-4 cursor-pointer opacity-60 hover:opacity-100"
id={`img-${index}`}
onClick={(event) => {
event.stopPropagation();
handleDeleteDocument(index, document);
}}
/>
)}
{document.syncFrequency && (
<div className="ml-2">
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
</div>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
<div className="flex flex-col mt-8">
<div className="flex flex-col relative flex-grow">
<div className="mb-6">
<h2 className="text-base font-medium text-sonic-silver">
{t('settings.documents.title')}
</h2>
</div>
{/* outside scrollable area */}
<div className="my-3 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
<div className="w-full sm:w-auto">
<label htmlFor="document-search-input" className="sr-only">
{t('settings.documents.searchPlaceholder')}
</label>
<Input
maxLength={256}
placeholder={t('settings.documents.searchPlaceholder')}
name="Document-search-input"
type="text"
id="document-search-input"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
/>
</div>
<button
className="rounded-full w-full sm:w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
title={t('settings.documents.addNew')}
onClick={() => {
setIsOnboarding(false);
setModalState('ACTIVE');
}}
>
{t('settings.documents.addNew')}
</button>
</div>
{loading ? (
<SkeletonLoader count={1} />
) : (
<div className="flex flex-col flex-grow">
{' '}
{/* Removed overflow-auto */}
<div className="border rounded-md border-gray-300 dark:border-silver/40 overflow-hidden">
<table className="w-full min-w-[640px] table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[45%]">
{t('settings.documents.name')}
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[20%]">
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[25%]">
<div className="flex justify-center items-center">
<span className="hidden sm:inline">
{t('settings.documents.tokenUsage')}
</span>
<span className="sm:hidden">
{t('settings.documents.tokenUsage')}
</span>
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[10%]">
<span className="sr-only">
{t('settings.documents.actions')}
</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
{!currentDocuments?.length ? (
<tr>
<td
colSpan={4}
className="py-4 text-center text-gray-700 dark:text-[#E0E0E] bg-transparent"
>
{t('settings.documents.noData')}
</td>
</tr>
) : (
currentDocuments.map((document, index) => (
<tr key={index} className="group transition-colors">
<td
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[45%] truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
title={document.name}
>
{document.name}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[20%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.date ? formatDate(document.date) : ''}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[25%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
<td className="py-4 px-4 text-right w-[10%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
<div className="flex items-center justify-end gap-3">
{!document.syncFrequency && (
<div className="w-8"></div>
)}
{document.syncFrequency && (
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
)}
<button
onClick={(event) => {
event.stopPropagation();
handleDeleteConfirmation(index, document);
}}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
>
<img
src={Trash}
alt={t('convTile.delete')}
className="h-4 w-4 opacity-60 hover:opacity-100"
/>
</button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
)}
</div>
<div className="mt-auto pt-4">
<Pagination
currentPage={currentPage}
totalPages={totalPages}
@@ -319,23 +327,36 @@ const Documents: React.FC<DocumentsProps> = ({
refreshDocs(undefined, 1, rows);
}}
/>
{/* Conditionally render the Upload modal based on modalState */}
{modalState === 'ACTIVE' && (
<div className="fixed top-0 left-0 w-screen h-screen z-50 flex items-center justify-center bg-transparent">
<div className="w-full h-full bg-transparent flex flex-col items-center justify-center p-8">
{/* Your Upload component */}
<Upload
receivedFile={[]}
setModalState={setModalState}
isOnboarding={isOnboarding}
renderTab={null}
close={() => setModalState('INACTIVE')}
/>
</div>
</div>
)}
</div>
{modalState === 'ACTIVE' && (
<Upload
receivedFile={[]}
setModalState={setModalState}
isOnboarding={isOnboarding}
renderTab={null}
close={() => setModalState('INACTIVE')}
onSuccessfulUpload={() =>
refreshDocs(undefined, currentPage, rowsPerPage)
}
/>
)}
{deleteModalState === 'ACTIVE' && documentToDelete && (
<ConfirmationModal
message={t('settings.documents.deleteWarning', {
name: documentToDelete.document.name,
})}
modalState={deleteModalState}
setModalState={setDeleteModalState}
handleSubmit={handleConfirmedDelete}
handleCancel={() => {
setDeleteModalState('INACTIVE');
setDocumentToDelete(null);
}}
submitLabel={t('convTile.delete')}
/>
)}
</div>
);
};

View File

@@ -23,12 +23,14 @@ function Upload({
isOnboarding,
renderTab = null,
close,
onSuccessfulUpload = () => undefined,
}: {
receivedFile: File[];
setModalState: (state: ActiveState) => void;
isOnboarding: boolean;
renderTab: string | null;
close: () => void;
onSuccessfulUpload?: () => void;
}) {
const [docName, setDocName] = useState(receivedFile[0]?.name);
const [urlName, setUrlName] = useState('');
@@ -218,6 +220,7 @@ function Upload({
setfiles([]);
setProgress(undefined);
setModalState('INACTIVE');
onSuccessfulUpload?.();
}
} else if (data.status == 'PROGRESS') {
setProgress(
@@ -424,20 +427,26 @@ function Upload({
<p className="mb-0 text-xs italic text-gray-4000">
{t('modals.uploadDoc.info')}
</p>
<div className="mt-0">
<div className="mt-0 max-w-full">
<p className="mb-[14px] font-medium text-eerie-black dark:text-light-gray">
{t('modals.uploadDoc.uploadedFiles')}
</p>
{files.map((file) => (
<p key={file.name} className="text-gray-6000">
{file.name}
</p>
))}
{files.length === 0 && (
<p className="text-gray-6000 dark:text-light-gray">
{t('none')}
</p>
)}
<div className="max-w-full overflow-hidden">
{files.map((file) => (
<p
key={file.name}
className="text-gray-6000 truncate overflow-hidden text-ellipsis"
title={file.name}
>
{file.name}
</p>
))}
{files.length === 0 && (
<p className="text-gray-6000 dark:text-light-gray">
{t('none')}
</p>
)}
</div>
</div>
</>
)}
@@ -597,47 +606,49 @@ function Upload({
{t('modals.uploadDoc.back')}
</button>
)}
<button
onClick={() => {
if (activeTab === 'file') {
uploadFile();
} else {
uploadRemote();
{activeTab && (
<button
onClick={() => {
if (activeTab === 'file') {
uploadFile();
} else {
uploadRemote();
}
}}
disabled={
(activeTab === 'file' && (!files.length || !docName)) ||
(activeTab === 'remote' &&
((urlType.label !== 'Reddit' &&
urlType.label !== 'GitHub' &&
(!url || !urlName)) ||
(urlType.label === 'GitHub' && !repoUrl) ||
(urlType.label === 'Reddit' &&
(!redditData.client_id ||
!redditData.client_secret ||
!redditData.user_agent ||
!redditData.search_queries ||
!redditData.number_posts))))
}
}}
disabled={
(activeTab === 'file' && (!files.length || !docName)) ||
(activeTab === 'remote' &&
((urlType.label !== 'Reddit' &&
urlType.label !== 'GitHub' &&
(!url || !urlName)) ||
(urlType.label === 'GitHub' && !repoUrl) ||
(urlType.label === 'Reddit' &&
(!redditData.client_id ||
!redditData.client_secret ||
!redditData.user_agent ||
!redditData.search_queries ||
!redditData.number_posts))))
}
className={`rounded-3xl px-4 py-2 font-medium ${
(activeTab === 'file' && (!files.length || !docName)) ||
(activeTab === 'remote' &&
((urlType.label !== 'Reddit' &&
urlType.label !== 'GitHub' &&
(!url || !urlName)) ||
(urlType.label === 'GitHub' && !repoUrl) ||
(urlType.label === 'Reddit' &&
(!redditData.client_id ||
!redditData.client_secret ||
!redditData.user_agent ||
!redditData.search_queries ||
!redditData.number_posts))))
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
}`}
>
{t('modals.uploadDoc.train')}
</button>
className={`rounded-3xl px-4 py-2 font-medium ${
(activeTab === 'file' && (!files.length || !docName)) ||
(activeTab === 'remote' &&
((urlType.label !== 'Reddit' &&
urlType.label !== 'GitHub' &&
(!url || !urlName)) ||
(urlType.label === 'GitHub' && !repoUrl) ||
(urlType.label === 'Reddit' &&
(!redditData.client_id ||
!redditData.client_secret ||
!redditData.user_agent ||
!redditData.search_queries ||
!redditData.number_posts))))
? 'cursor-not-allowed bg-gray-300 text-gray-500'
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
}`}
>
{t('modals.uploadDoc.train')}
</button>
)}
</div>
</div>
);

View File

@@ -1,20 +1,39 @@
export function formatDate(dateString: string): string {
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateString)) {
const dateTime = new Date(dateString);
return dateTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(dateString)) {
const dateTime = new Date(dateString);
return dateTime.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
} else if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
} else {
if (isNaN(date.getTime())) {
throw new Error('Invalid date');
}
const userLocale = navigator.language || 'en-US';
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const weekday = date.toLocaleDateString(userLocale, {
weekday: 'short',
timeZone: userTimezone,
});
const monthDay = date.toLocaleDateString(userLocale, {
day: '2-digit',
month: 'short',
year: 'numeric',
timeZone: userTimezone,
});
const time = date
.toLocaleTimeString(userLocale, {
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true,
timeZone: userTimezone,
})
.replace(/am|pm/i, (match) => match.toUpperCase());
return `${weekday}, ${monthDay} ${time}`;
} catch (error) {
console.error('Error formatting date:', error);
return dateString;
}
}