File Management ve AI Asistant güncellemeleri
This commit is contained in:
parent
17df35102d
commit
62f38a27a5
7 changed files with 133 additions and 117 deletions
|
|
@ -25,17 +25,16 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
private readonly BlobManager _blobContainer;
|
private readonly BlobManager _blobContainer;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
private const string FileMetadataSuffix = ".metadata.json";
|
|
||||||
private const string FolderMarkerSuffix = ".folder";
|
private const string FolderMarkerSuffix = ".folder";
|
||||||
private const string IndexFileName = "index.json";
|
private const string IndexFileName = "index.json";
|
||||||
|
|
||||||
// Protected system folders that cannot be deleted, renamed, or moved
|
// Protected system folders that cannot be deleted, renamed, or moved
|
||||||
private static readonly HashSet<string> ProtectedFolders = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> ProtectedFolders = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
BlobContainerNames.Avatar,
|
// BlobContainerNames.Avatar,
|
||||||
BlobContainerNames.Import,
|
// BlobContainerNames.Import,
|
||||||
BlobContainerNames.Note,
|
// BlobContainerNames.Note,
|
||||||
BlobContainerNames.Intranet
|
// BlobContainerNames.Intranet
|
||||||
};
|
};
|
||||||
|
|
||||||
public FileManagementAppService(
|
public FileManagementAppService(
|
||||||
|
|
@ -119,16 +118,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var rootFolder = pathParts[0];
|
var rootFolder = pathParts[0];
|
||||||
var isProtected = ProtectedFolders.Contains(rootFolder);
|
var isProtected = ProtectedFolders.Contains(rootFolder);
|
||||||
|
|
||||||
Logger.LogInformation($"IsProtectedFolder - Path: '{path}', RootFolder: '{rootFolder}', IsProtected: {isProtected}");
|
|
||||||
Logger.LogInformation($"Protected folders: {string.Join(", ", ProtectedFolders)}");
|
|
||||||
|
|
||||||
return isProtected;
|
return isProtected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateNotProtectedFolder(string id, string operation)
|
private void ValidateNotProtectedFolder(string id, string operation)
|
||||||
{
|
{
|
||||||
var decodedPath = DecodeIdAsPath(id);
|
var decodedPath = DecodeIdAsPath(id);
|
||||||
Logger.LogInformation($"ValidateNotProtectedFolder - ID: {id}, DecodedPath: {decodedPath}, Operation: {operation}");
|
|
||||||
|
|
||||||
if (IsProtectedFolder(decodedPath))
|
if (IsProtectedFolder(decodedPath))
|
||||||
{
|
{
|
||||||
|
|
@ -136,8 +131,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}");
|
Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}");
|
||||||
throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected.");
|
throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation($"Folder {decodedPath} is not protected, allowing {operation}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<FileMetadata>> GetFolderIndexAsync(string? parentId, string tenantId)
|
private async Task<List<FileMetadata>> GetFolderIndexAsync(string? parentId, string tenantId)
|
||||||
|
|
@ -1124,7 +1117,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
|
|
||||||
// Decode the folderId to get the actual path
|
// Decode the folderId to get the actual path
|
||||||
var decodedPath = DecodeIdAsPath(folderId);
|
var decodedPath = DecodeIdAsPath(folderId);
|
||||||
Logger.LogInformation($"GetFolderPath - FolderId: {folderId}, DecodedPath: {decodedPath}");
|
|
||||||
|
|
||||||
// Split path into parts and build breadcrumb
|
// Split path into parts and build breadcrumb
|
||||||
var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
@ -1136,8 +1128,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1));
|
var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1));
|
||||||
currentEncodedPath = EncodePathAsId(pathUpToCurrent);
|
currentEncodedPath = EncodePathAsId(pathUpToCurrent);
|
||||||
|
|
||||||
Logger.LogInformation($"PathItem {i}: Name='{pathParts[i]}', Id='{currentEncodedPath}', PathUpToCurrent='{pathUpToCurrent}'");
|
|
||||||
|
|
||||||
pathItems.Add(new PathItemDto
|
pathItems.Add(new PathItemDto
|
||||||
{
|
{
|
||||||
Id = currentEncodedPath,
|
Id = currentEncodedPath,
|
||||||
|
|
@ -1145,7 +1135,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation($"Returning {pathItems.Count} breadcrumb items");
|
|
||||||
return new FolderPathDto { Path = pathItems };
|
return new FolderPathDto { Path = pathItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -822,16 +822,6 @@
|
||||||
"RequiredPermissionName": "App.Menus.Manager",
|
"RequiredPermissionName": "App.Menus.Manager",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Saas",
|
|
||||||
"Code": "App.Files",
|
|
||||||
"DisplayName": "App.Files",
|
|
||||||
"Order": 14,
|
|
||||||
"Url": "/admin/files",
|
|
||||||
"Icon": "FcFolder",
|
|
||||||
"RequiredPermissionName": "App.Files",
|
|
||||||
"IsDisabled": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.DeveloperKit",
|
"Code": "App.DeveloperKit",
|
||||||
|
|
@ -1084,11 +1074,21 @@
|
||||||
"RequiredPermissionName": "App.Reports.ReportTemplates",
|
"RequiredPermissionName": "App.Reports.ReportTemplates",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Administration",
|
||||||
|
"Code": "App.Files",
|
||||||
|
"DisplayName": "App.Files",
|
||||||
|
"Order": 5,
|
||||||
|
"Url": "/admin/files",
|
||||||
|
"Icon": "FcFolder",
|
||||||
|
"RequiredPermissionName": "App.Files",
|
||||||
|
"IsDisabled": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Administration",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "App.Forum",
|
"Code": "App.Forum",
|
||||||
"DisplayName": "App.Forum",
|
"DisplayName": "App.Forum",
|
||||||
"Order": 5,
|
"Order": 6,
|
||||||
"Url": "/admin/forum",
|
"Url": "/admin/forum",
|
||||||
"Icon": "FcLink",
|
"Icon": "FcLink",
|
||||||
"RequiredPermissionName": "App.ForumManagement.Publish",
|
"RequiredPermissionName": "App.ForumManagement.Publish",
|
||||||
|
|
|
||||||
|
|
@ -2233,51 +2233,6 @@
|
||||||
"MultiTenancySide": 2,
|
"MultiTenancySide": 2,
|
||||||
"MenuGroup": "Erp|Kurs"
|
"MenuGroup": "Erp|Kurs"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Files",
|
|
||||||
"ParentName": null,
|
|
||||||
"DisplayName": "App.Files",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"MenuGroup": "Erp|Kurs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Files.Create",
|
|
||||||
"ParentName": "App.Files",
|
|
||||||
"DisplayName": "Create",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"MenuGroup": "Erp|Kurs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Files.Update",
|
|
||||||
"ParentName": "App.Files",
|
|
||||||
"DisplayName": "Update",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"MenuGroup": "Erp|Kurs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Files.Delete",
|
|
||||||
"ParentName": "App.Files",
|
|
||||||
"DisplayName": "Delete",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"MenuGroup": "Erp|Kurs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Files.Widget",
|
|
||||||
"ParentName": "App.Files",
|
|
||||||
"DisplayName": "Widget",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"MenuGroup": "Erp|Kurs"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"GroupName": "App.Saas",
|
"GroupName": "App.Saas",
|
||||||
"Name": "App.ForumManagement",
|
"Name": "App.ForumManagement",
|
||||||
|
|
@ -3484,6 +3439,42 @@
|
||||||
"MultiTenancySide": 3,
|
"MultiTenancySide": 3,
|
||||||
"MenuGroup": "Erp|Kurs"
|
"MenuGroup": "Erp|Kurs"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Administration",
|
||||||
|
"Name": "App.Files",
|
||||||
|
"ParentName": null,
|
||||||
|
"DisplayName": "App.Files",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"MenuGroup": "Erp|Kurs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Administration",
|
||||||
|
"Name": "App.Files.Create",
|
||||||
|
"ParentName": "App.Files",
|
||||||
|
"DisplayName": "Create",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"MenuGroup": "Erp|Kurs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Administration",
|
||||||
|
"Name": "App.Files.Update",
|
||||||
|
"ParentName": "App.Files",
|
||||||
|
"DisplayName": "Update",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"MenuGroup": "Erp|Kurs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Administration",
|
||||||
|
"Name": "App.Files.Delete",
|
||||||
|
"ParentName": "App.Files",
|
||||||
|
"DisplayName": "Delete",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"MenuGroup": "Erp|Kurs"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"GroupName": "App.Administration",
|
"GroupName": "App.Administration",
|
||||||
"Name": "App.ForumManagement.Publish",
|
"Name": "App.ForumManagement.Publish",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import Tooltip from '@/components/ui/Tooltip'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
import { usePermission } from '@/utils/hooks/usePermission'
|
||||||
import { FcAssistant, FcHeadset } from 'react-icons/fc'
|
import { FcHeadset } from 'react-icons/fc'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const AiAssistant = () => {
|
const AiAssistant = () => {
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,6 @@ export type MessageContent = string | BaseContent
|
||||||
export interface Message {
|
export interface Message {
|
||||||
role: 'user' | 'assistant'
|
role: 'user' | 'assistant'
|
||||||
content: MessageContent
|
content: MessageContent
|
||||||
|
/** ISO string */
|
||||||
|
createdAt?: string
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
import {
|
import {
|
||||||
FaFolder,
|
FaFolder,
|
||||||
FaCloudUploadAlt,
|
FaCloudUploadAlt,
|
||||||
|
|
@ -43,6 +44,10 @@ import { APP_NAME } from '@/constants/app.constant'
|
||||||
const FileManager = () => {
|
const FileManager = () => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
|
||||||
|
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
|
||||||
|
const isHostContext = !authTenantId
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [items, setItems] = useState<FileItemType[]>([])
|
const [items, setItems] = useState<FileItemType[]>([])
|
||||||
|
|
@ -71,7 +76,7 @@ const FileManager = () => {
|
||||||
const [tenants, setTenants] = useState<TenantDto[]>([])
|
const [tenants, setTenants] = useState<TenantDto[]>([])
|
||||||
const [tenantsLoading, setTenantsLoading] = useState(false)
|
const [tenantsLoading, setTenantsLoading] = useState(false)
|
||||||
const [selectedTenant, setSelectedTenant] = useState<{ id: string; name: string } | undefined>(
|
const [selectedTenant, setSelectedTenant] = useState<{ id: string; name: string } | undefined>(
|
||||||
undefined,
|
authTenantId ? { id: authTenantId, name: authTenantName || '' } : undefined,
|
||||||
)
|
)
|
||||||
// Tracks mid-flight tenant change so the fetch effect doesn't fire with a stale folderId
|
// Tracks mid-flight tenant change so the fetch effect doesn't fire with a stale folderId
|
||||||
const pendingTenantChange = useRef(false)
|
const pendingTenantChange = useRef(false)
|
||||||
|
|
@ -96,8 +101,20 @@ const FileManager = () => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTenants()
|
if (isHostContext) {
|
||||||
}, [fetchTenants])
|
fetchTenants()
|
||||||
|
}
|
||||||
|
}, [fetchTenants, isHostContext])
|
||||||
|
|
||||||
|
// If user is in a tenant context, lock selection to that tenant.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authTenantId) return
|
||||||
|
|
||||||
|
setSelectedTenant((prev) => {
|
||||||
|
if (prev?.id === authTenantId) return prev
|
||||||
|
return { id: authTenantId, name: authTenantName || prev?.name || '' }
|
||||||
|
})
|
||||||
|
}, [authTenantId, authTenantName])
|
||||||
|
|
||||||
// Reset navigation when tenant changes
|
// Reset navigation when tenant changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -124,20 +141,6 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log('Fetched items:', protectedItems)
|
|
||||||
// console.log(
|
|
||||||
// 'Protected folders check:',
|
|
||||||
// protectedItems.filter((item) => item.isReadOnly),
|
|
||||||
// )
|
|
||||||
// console.log(
|
|
||||||
// 'Folders with childCount:',
|
|
||||||
// protectedItems.filter((item) => item.type === 'folder').map(item => ({
|
|
||||||
// name: item.name,
|
|
||||||
// childCount: item.childCount,
|
|
||||||
// hasChildCount: 'childCount' in item,
|
|
||||||
// type: typeof item.childCount
|
|
||||||
// }))
|
|
||||||
// )
|
|
||||||
setItems(protectedItems)
|
setItems(protectedItems)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch items:', error)
|
console.error('Failed to fetch items:', error)
|
||||||
|
|
@ -742,29 +745,38 @@ const FileManager = () => {
|
||||||
{/* Tenant Selector Row */}
|
{/* Tenant Selector Row */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaBuilding className="text-gray-500 flex-shrink-0" />
|
<FaBuilding className="text-gray-500 flex-shrink-0" />
|
||||||
<Select
|
{isHostContext ? (
|
||||||
size="xs"
|
<Select
|
||||||
isLoading={tenantsLoading}
|
size="xs"
|
||||||
options={[
|
isLoading={tenantsLoading}
|
||||||
{ value: '', label: 'Host' },
|
options={[
|
||||||
...tenants.map((t) => ({ value: t.id ?? '', label: t.name ?? '' })),
|
{ value: '', label: 'Host' },
|
||||||
]}
|
...tenants.map((t) => ({ value: t.id ?? '', label: t.name ?? '' })),
|
||||||
value={{
|
]}
|
||||||
value: selectedTenant ? selectedTenant.id : '',
|
value={{
|
||||||
label: selectedTenant ? selectedTenant.name : 'Host',
|
value: selectedTenant ? selectedTenant.id : '',
|
||||||
}}
|
label: selectedTenant ? selectedTenant.name : 'Host',
|
||||||
onChange={(option) => {
|
}}
|
||||||
if (option && 'value' in option) {
|
onChange={(option) => {
|
||||||
const val = option.value as string
|
if (option && 'value' in option) {
|
||||||
if (!val) {
|
const val = option.value as string
|
||||||
setSelectedTenant(undefined)
|
if (!val) {
|
||||||
} else {
|
setSelectedTenant(undefined)
|
||||||
const found = tenants.find((t) => t.id === val)
|
} else {
|
||||||
if (found) setSelectedTenant({ id: found.id!, name: found.name ?? '' })
|
const found = tenants.find((t) => t.id === val)
|
||||||
|
if (found) setSelectedTenant({ id: found.id!, name: found.name ?? '' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<div
|
||||||
|
className="text-sm font-medium text-gray-700 dark:text-gray-200 truncate max-w-[220px]"
|
||||||
|
title={authTenantName || selectedTenant?.name || ''}
|
||||||
|
>
|
||||||
|
{authTenantName || selectedTenant?.name || ''}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File Operations */}
|
{/* File Operations */}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { AiDto } from '@/proxy/ai/models'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
type ChatType = 'chat' | 'query' | 'analyze'
|
type ChatType = 'chat' | 'query' | 'analyze'
|
||||||
|
|
@ -27,6 +28,7 @@ type MessageContent = string | BaseContent
|
||||||
interface Message {
|
interface Message {
|
||||||
role: 'user' | 'assistant'
|
role: 'user' | 'assistant'
|
||||||
content: MessageContent
|
content: MessageContent
|
||||||
|
createdAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const isContentObject = (content: MessageContent): content is BaseContent =>
|
const isContentObject = (content: MessageContent): content is BaseContent =>
|
||||||
|
|
@ -102,13 +104,17 @@ const Assistant = () => {
|
||||||
if (!input.trim()) return
|
if (!input.trim()) return
|
||||||
|
|
||||||
const userMessage = input.trim()
|
const userMessage = input.trim()
|
||||||
|
const userMessageCreatedAt = new Date().toISOString()
|
||||||
setInput('')
|
setInput('')
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
// 1️⃣ Soruyu store'a ekle
|
// 1️⃣ Soruyu store'a ekle
|
||||||
addAiPost({ role: 'user', content: userMessage })
|
addAiPost({ role: 'user', content: userMessage, createdAt: userMessageCreatedAt })
|
||||||
|
|
||||||
setMessages((prev) => [...prev, { role: 'user', content: userMessage }])
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ role: 'user', content: userMessage, createdAt: userMessageCreatedAt },
|
||||||
|
])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const selectedBotItem = bot.find((item) => item.id === selectedBot)
|
const selectedBotItem = bot.find((item) => item.id === selectedBot)
|
||||||
|
|
@ -140,17 +146,24 @@ const Assistant = () => {
|
||||||
const formattedAnswer =
|
const formattedAnswer =
|
||||||
typeof mapped.answer === 'string' ? mapped.answer : JSON.stringify(mapped.answer)
|
typeof mapped.answer === 'string' ? mapped.answer : JSON.stringify(mapped.answer)
|
||||||
|
|
||||||
addAiPost({ role: 'assistant', content: mapped }) // mapped bir BaseContent
|
const assistantMessageCreatedAt = new Date().toISOString()
|
||||||
|
|
||||||
setMessages((prev) => [...prev, { role: 'assistant', content: mapped }])
|
addAiPost({ role: 'assistant', content: mapped, createdAt: assistantMessageCreatedAt }) // mapped bir BaseContent
|
||||||
|
|
||||||
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ role: 'assistant', content: mapped, createdAt: assistantMessageCreatedAt },
|
||||||
|
])
|
||||||
} catch {
|
} catch {
|
||||||
const errorMessage = 'Üzgünüm, bir hata oluştu. Lütfen tekrar deneyin.'
|
const errorMessage = 'Üzgünüm, bir hata oluştu. Lütfen tekrar deneyin.'
|
||||||
addAiPost({ role: 'assistant', content: errorMessage })
|
const assistantMessageCreatedAt = new Date().toISOString()
|
||||||
|
addAiPost({ role: 'assistant', content: errorMessage, createdAt: assistantMessageCreatedAt })
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: errorMessage,
|
content: errorMessage,
|
||||||
|
createdAt: assistantMessageCreatedAt,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +325,16 @@ const Assistant = () => {
|
||||||
<div
|
<div
|
||||||
className={`max-w-[80%] rounded-lg p-2 ${msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-white text-gray-800'}`}
|
className={`max-w-[80%] rounded-lg p-2 ${msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-white text-gray-800'}`}
|
||||||
>
|
>
|
||||||
{renderMessageContent(msg)}
|
<div className="flex flex-col gap-1">
|
||||||
|
{renderMessageContent(msg)}
|
||||||
|
{msg.createdAt && (
|
||||||
|
<div
|
||||||
|
className={`text-[11px] leading-none ${msg.role === 'user' ? 'text-white/80 text-right' : 'text-gray-500 text-right'}`}
|
||||||
|
>
|
||||||
|
{dayjs(msg.createdAt).format('DD.MM.YYYY HH:mm')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue