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 IConfiguration _configuration;
|
||||
|
||||
private const string FileMetadataSuffix = ".metadata.json";
|
||||
private const string FolderMarkerSuffix = ".folder";
|
||||
private const string IndexFileName = "index.json";
|
||||
|
||||
// Protected system folders that cannot be deleted, renamed, or moved
|
||||
private static readonly HashSet<string> ProtectedFolders = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
BlobContainerNames.Avatar,
|
||||
BlobContainerNames.Import,
|
||||
BlobContainerNames.Note,
|
||||
BlobContainerNames.Intranet
|
||||
// BlobContainerNames.Avatar,
|
||||
// BlobContainerNames.Import,
|
||||
// BlobContainerNames.Note,
|
||||
// BlobContainerNames.Intranet
|
||||
};
|
||||
|
||||
public FileManagementAppService(
|
||||
|
|
@ -119,16 +118,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
|||
var rootFolder = pathParts[0];
|
||||
var isProtected = ProtectedFolders.Contains(rootFolder);
|
||||
|
||||
Logger.LogInformation($"IsProtectedFolder - Path: '{path}', RootFolder: '{rootFolder}', IsProtected: {isProtected}");
|
||||
Logger.LogInformation($"Protected folders: {string.Join(", ", ProtectedFolders)}");
|
||||
|
||||
return isProtected;
|
||||
}
|
||||
|
||||
private void ValidateNotProtectedFolder(string id, string operation)
|
||||
{
|
||||
var decodedPath = DecodeIdAsPath(id);
|
||||
Logger.LogInformation($"ValidateNotProtectedFolder - ID: {id}, DecodedPath: {decodedPath}, Operation: {operation}");
|
||||
|
||||
if (IsProtectedFolder(decodedPath))
|
||||
{
|
||||
|
|
@ -136,8 +131,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
|||
Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}");
|
||||
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)
|
||||
|
|
@ -1124,7 +1117,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
|||
|
||||
// Decode the folderId to get the actual path
|
||||
var decodedPath = DecodeIdAsPath(folderId);
|
||||
Logger.LogInformation($"GetFolderPath - FolderId: {folderId}, DecodedPath: {decodedPath}");
|
||||
|
||||
// Split path into parts and build breadcrumb
|
||||
var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
|
@ -1136,8 +1128,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
|||
var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1));
|
||||
currentEncodedPath = EncodePathAsId(pathUpToCurrent);
|
||||
|
||||
Logger.LogInformation($"PathItem {i}: Name='{pathParts[i]}', Id='{currentEncodedPath}', PathUpToCurrent='{pathUpToCurrent}'");
|
||||
|
||||
pathItems.Add(new PathItemDto
|
||||
{
|
||||
Id = currentEncodedPath,
|
||||
|
|
@ -1145,7 +1135,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
|||
});
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Returning {pathItems.Count} breadcrumb items");
|
||||
return new FolderPathDto { Path = pathItems };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -822,16 +822,6 @@
|
|||
"RequiredPermissionName": "App.Menus.Manager",
|
||||
"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",
|
||||
"Code": "App.DeveloperKit",
|
||||
|
|
@ -1084,11 +1074,21 @@
|
|||
"RequiredPermissionName": "App.Reports.ReportTemplates",
|
||||
"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",
|
||||
"Code": "App.Forum",
|
||||
"DisplayName": "App.Forum",
|
||||
"Order": 5,
|
||||
"Order": 6,
|
||||
"Url": "/admin/forum",
|
||||
"Icon": "FcLink",
|
||||
"RequiredPermissionName": "App.ForumManagement.Publish",
|
||||
|
|
|
|||
|
|
@ -2233,51 +2233,6 @@
|
|||
"MultiTenancySide": 2,
|
||||
"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",
|
||||
"Name": "App.ForumManagement",
|
||||
|
|
@ -3484,6 +3439,42 @@
|
|||
"MultiTenancySide": 3,
|
||||
"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",
|
||||
"Name": "App.ForumManagement.Publish",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Tooltip from '@/components/ui/Tooltip'
|
|||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
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'
|
||||
|
||||
const AiAssistant = () => {
|
||||
|
|
|
|||
|
|
@ -21,4 +21,6 @@ export type MessageContent = string | BaseContent
|
|||
export interface Message {
|
||||
role: 'user' | 'assistant'
|
||||
content: MessageContent
|
||||
/** ISO string */
|
||||
createdAt?: string
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
||||
import { useStoreState } from '@/store'
|
||||
import {
|
||||
FaFolder,
|
||||
FaCloudUploadAlt,
|
||||
|
|
@ -43,6 +44,10 @@ import { APP_NAME } from '@/constants/app.constant'
|
|||
const FileManager = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
|
||||
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
|
||||
const isHostContext = !authTenantId
|
||||
|
||||
// State
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [items, setItems] = useState<FileItemType[]>([])
|
||||
|
|
@ -71,7 +76,7 @@ const FileManager = () => {
|
|||
const [tenants, setTenants] = useState<TenantDto[]>([])
|
||||
const [tenantsLoading, setTenantsLoading] = useState(false)
|
||||
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
|
||||
const pendingTenantChange = useRef(false)
|
||||
|
|
@ -96,8 +101,20 @@ const FileManager = () => {
|
|||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
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
|
||||
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)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch items:', error)
|
||||
|
|
@ -742,6 +745,7 @@ const FileManager = () => {
|
|||
{/* Tenant Selector Row */}
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBuilding className="text-gray-500 flex-shrink-0" />
|
||||
{isHostContext ? (
|
||||
<Select
|
||||
size="xs"
|
||||
isLoading={tenantsLoading}
|
||||
|
|
@ -765,6 +769,14 @@ const FileManager = () => {
|
|||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<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>
|
||||
|
||||
{/* File Operations */}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { AiDto } from '@/proxy/ai/models'
|
|||
import { Container } from '@/components/shared'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// Types
|
||||
type ChatType = 'chat' | 'query' | 'analyze'
|
||||
|
|
@ -27,6 +28,7 @@ type MessageContent = string | BaseContent
|
|||
interface Message {
|
||||
role: 'user' | 'assistant'
|
||||
content: MessageContent
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
const isContentObject = (content: MessageContent): content is BaseContent =>
|
||||
|
|
@ -102,13 +104,17 @@ const Assistant = () => {
|
|||
if (!input.trim()) return
|
||||
|
||||
const userMessage = input.trim()
|
||||
const userMessageCreatedAt = new Date().toISOString()
|
||||
setInput('')
|
||||
setLoading(true)
|
||||
|
||||
// 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 {
|
||||
const selectedBotItem = bot.find((item) => item.id === selectedBot)
|
||||
|
|
@ -140,17 +146,24 @@ const Assistant = () => {
|
|||
const formattedAnswer =
|
||||
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 {
|
||||
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) => [
|
||||
...prev,
|
||||
{
|
||||
role: 'assistant',
|
||||
content: errorMessage,
|
||||
createdAt: assistantMessageCreatedAt,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
|
@ -312,7 +325,16 @@ const Assistant = () => {
|
|||
<div
|
||||
className={`max-w-[80%] rounded-lg p-2 ${msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-white text-gray-800'}`}
|
||||
>
|
||||
<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>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in a new issue