diff --git a/api/src/Sozsoft.Platform.Application.Contracts/AiBots/AiBotDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/AiBots/AiBotDto.cs index 9367377..3130d3d 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/AiBots/AiBotDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/AiBots/AiBotDto.cs @@ -8,5 +8,6 @@ public class AiBotDto : FullAuditedEntityDto public string Name { get; set; } public string ApiUrl { get; set; } public string Description { get; set; } + public string Tenants { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs index f0f7e9a..cd41818 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs @@ -42,24 +42,31 @@ public static class LookupQueryValues $"ORDER BY \"{DisplayExpr}\";"; } - public static string TechnicalSkillsValues = - $"SELECT " + - $"\"Name\" AS \"Key\", " + - $"\"Name\" AS \"Name\" " + - $"FROM \"{FullNameTable(TableNameEnum.Skill)}\" " + - $"WHERE \"SkillTypeId\" IN (SELECT \"Id\" FROM \"{FullNameTable(TableNameEnum.SkillType)}\" WHERE \"Name\"='Technical Skills' ) " + - $"ORDER BY \"Name\""; - - public static string TenantValues = - $"SELECT * FROM (" + - $"SELECT NULL AS \"Key\", 'Host' AS \"Name\" " + - $"UNION ALL " + - $"SELECT " + - $"\"AbpTenants\".\"Id\" AS \"Key\", " + - $"\"AbpTenants\".\"Name\" AS \"Name\" " + - $"FROM \"AbpTenants\"" + - $") AS \"List\" " + - $"ORDER BY \"Name\""; + public static string TenantValues(bool alwaysName = false) + { + if (alwaysName) + { + return $"SELECT * FROM (" + + $"SELECT " + + $"\"AbpTenants\".\"Name\" AS \"Key\", " + + $"\"AbpTenants\".\"Name\" AS \"Name\" " + + $"FROM \"AbpTenants\"" + + $") AS \"List\" " + + $"ORDER BY \"Name\""; + } + else + { + return $"SELECT * FROM (" + + $"SELECT NULL AS \"Key\", 'Host' AS \"Name\" " + + $"UNION ALL " + + $"SELECT " + + $"\"AbpTenants\".\"Id\" AS \"Key\", " + + $"\"AbpTenants\".\"Name\" AS \"Name\" " + + $"FROM \"AbpTenants\"" + + $") AS \"List\" " + + $"ORDER BY \"Name\""; + } + } public static string LanguageKeyValues = $"SELECT " + @@ -231,4 +238,13 @@ public static class LookupQueryValues $"FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.SurveyQuestion))}\" " + $"WHERE \"IsDeleted\" = 'false' " + $"ORDER BY \"QuestionText\";"; + + public static string TechnicalSkillsValues = + $"SELECT " + + $"\"Name\" AS \"Key\", " + + $"\"Name\" AS \"Name\" " + + $"FROM \"{FullNameTable(TableNameEnum.Skill)}\" " + + $"WHERE \"SkillTypeId\" IN (SELECT \"Id\" FROM \"{FullNameTable(TableNameEnum.SkillType)}\" WHERE \"Name\"='Technical Skills' ) " + + $"ORDER BY \"Name\""; + } diff --git a/api/src/Sozsoft.Platform.Application/AiBots/AiBotAppService.cs b/api/src/Sozsoft.Platform.Application/AiBots/AiBotAppService.cs index fd0393e..a50feb6 100644 --- a/api/src/Sozsoft.Platform.Application/AiBots/AiBotAppService.cs +++ b/api/src/Sozsoft.Platform.Application/AiBots/AiBotAppService.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; +using System.Threading.Tasks; +using System.Linq; namespace Sozsoft.Platform.AiBots; @@ -18,5 +20,29 @@ public class AiBotAppService : CrudAppService< { } + + public override async Task> GetListAsync(PagedAndSortedResultRequestDto input) + { + await CheckGetListPolicyAsync(); + + var query = await CreateFilteredQueryAsync(input); + + if (CurrentTenant.IsAvailable && !string.IsNullOrWhiteSpace(CurrentTenant.Name)) + { + var tenantName = CurrentTenant.Name; + query = query.Where(aiBot => + string.IsNullOrWhiteSpace(aiBot.Tenants) || + aiBot.Tenants.Contains($"\"{tenantName}\"")); + } + + var totalCount = await AsyncExecuter.CountAsync(query); + query = ApplySorting(query, input); + query = ApplyPaging(query, input); + + var entities = await AsyncExecuter.ToListAsync(query); + var dtos = await MapToGetListOutputDtosAsync(entities); + + return new PagedResultDto(totalCount, dtos); + } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 1bfc56b..ad99dcf 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -15116,6 +15116,12 @@ "en": "Is Tenant", "tr": "Tenant" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.Tenants", + "en": "Tenants", + "tr": "Tenantlar" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.IsTransfer", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index 8dcc6d5..97a738e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -679,7 +679,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency DataSourceType = UiLookupDataSourceTypeEnum.Query, DisplayExpr = "Name", ValueExpr = "Key", - LookupQuery = LookupQueryValues.TenantValues, + LookupQuery = LookupQueryValues.TenantValues(), }), ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(listForm.Name), @@ -1239,14 +1239,15 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] { new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey } }), - EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 750, true, true, true, true, false), + EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 650, true, true, true, true, false), EditingFormJson = JsonSerializer.Serialize(new List() { new() { Order=1, ColCount=2, ColSpan=1, ItemType="group", Items = [ new EditingFormItemDto { Order=1, DataField="Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order=2, DataField="IsActive", ColSpan=1, EditorType2=EditorTypes.dxCheckBox }, new EditingFormItemDto { Order=3, DataField="ApiUrl", ColSpan=2, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=4, DataField="Description", ColSpan=2, EditorType2=EditorTypes.dxHtmlEditor, EditorOptions=EditorOptionValues.HtmlEditorOptions }, + new EditingFormItemDto { Order=4, DataField="Tenants", ColSpan=2, EditorType2=EditorTypes.dxTagBox }, + new EditingFormItemDto { Order=5, DataField="Description", ColSpan=2, EditorType2=EditorTypes.dxHtmlEditor, EditorOptions=EditorOptionValues.HtmlEditorOptions }, ]} }), FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] @@ -1337,6 +1338,28 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency PermissionJson = DefaultFieldPermissionJson(listForm.Name), PivotSettingsJson = DefaultPivotSettingsJson }, + new ListFormField + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Tenants", + CaptionName = "App.Listform.ListformField.Tenants", + Width = 0, + ListOrderNo = 6, + Visible = true, + IsActive = true, + AllowSearch = true, + LookupJson = JsonSerializer.Serialize(new LookupDto { + DataSourceType = UiLookupDataSourceTypeEnum.Query, + DisplayExpr = "Name", + ValueExpr = "Key", + LookupQuery = LookupQueryValues.TenantValues(true), + }), + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, ]); #endregion } diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Host/AiBot.cs b/api/src/Sozsoft.Platform.Domain/Entities/Host/AiBot.cs index 13f0250..6249de6 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Host/AiBot.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Host/AiBot.cs @@ -6,7 +6,8 @@ namespace Sozsoft.Platform.Entities; public class AiBot : Entity { public string Name { get; set; } - public string Description { get; set; } - public string ApiUrl { get; set; } public bool IsActive { get; set; } + public string ApiUrl { get; set; } + public string Description { get; set; } + public string Tenants { get; set; } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 98f0831..b0ec2bd 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -194,6 +194,7 @@ public class PlatformDbContext : b.Property(a => a.Name).IsRequired().HasMaxLength(128); b.Property(a => a.ApiUrl).HasMaxLength(256); b.Property(a => a.Description).HasColumnType("text"); + b.Property(a => a.Tenants).HasColumnType("text"); b.HasIndex(x => new { x.Name }).IsUnique().HasFilter(null); }); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.Designer.cs index ec574e6..7b158db 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260623105928_Initial")] + [Migration("20260623164647_Initial")] partial class Initial { /// @@ -699,6 +699,9 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Tenants") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("Name") diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.cs index 4ff82df..871283a 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623105928_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260623164647_Initial.cs @@ -921,9 +921,10 @@ namespace Sozsoft.Platform.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - Description = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "bit", nullable: false), ApiUrl = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - IsActive = table.Column(type: "bit", nullable: false) + Description = table.Column(type: "text", nullable: true), + Tenants = table.Column(type: "text", nullable: true) }, constraints: table => { diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 18f3992..f3d53fd 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -696,6 +696,9 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Tenants") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("Name") diff --git a/ui/src/proxy/ai/models.ts b/ui/src/proxy/ai/models.ts index 37268cc..387d8dd 100644 --- a/ui/src/proxy/ai/models.ts +++ b/ui/src/proxy/ai/models.ts @@ -4,17 +4,19 @@ export interface AiBotDto extends FullAuditedEntityDto { name: string apiUrl?: string description?: string + tenants?: string } +// Types export type ChatType = 'chat' | 'query' | 'analyze' export interface BaseContent { type: ChatType question: string - sql: string | null - answer: string | any[] + sql?: string | null + answer?: string | any[] | null chart?: string - error?: string + error?: string | null } export type MessageContent = string | BaseContent @@ -22,6 +24,13 @@ export type MessageContent = string | BaseContent export interface Message { role: 'user' | 'assistant' content: MessageContent - /** ISO string */ createdAt?: string +} + +export interface Conversation { + id: string + title: string + messages: Message[] + createdAt: string + updatedAt: string } \ No newline at end of file diff --git a/ui/src/views/ai/Assistant.tsx b/ui/src/views/ai/Assistant.tsx index 3c3f35f..0474621 100644 --- a/ui/src/views/ai/Assistant.tsx +++ b/ui/src/views/ai/Assistant.tsx @@ -4,41 +4,13 @@ import { useStoreState } from '@/store' import { Avatar, Button, Drawer, Dropdown, ScrollBar } from '@/components/ui' import { useLocalization } from '@/utils/hooks/useLocalization' import { aiService } from '@/services/ai.service' -import { AiBotDto } from '@/proxy/ai/models' +import { AiBotDto, BaseContent, Conversation, Message, MessageContent } 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' import { AI_ASSISTANT } from '@/constants/permission.constant' -// Types -type ChatType = 'chat' | 'query' | 'analyze' - -interface BaseContent { - type: ChatType - question: string - sql: string | null - answer: string | any[] - chart?: string - error?: string -} - -type MessageContent = string | BaseContent - -interface Message { - role: 'user' | 'assistant' - content: MessageContent - createdAt?: string -} - -interface Conversation { - id: string - title: string - messages: Message[] - createdAt: string - updatedAt: string -} - const CONVERSATIONS_STORAGE_KEY = 'AiConversations' const ACTIVE_CONVERSATION_STORAGE_KEY = 'AiActiveConversationId' @@ -330,7 +302,6 @@ const Assistant = () => { question: userMessage, sessionId: conversationId, tenantName: tenant.tenantName ?? null, - // description: selectedBotItem?.description?.replace(/\r?\n/g, '\\n'), }), }) @@ -338,8 +309,12 @@ const Assistant = () => { const raw = Array.isArray(data) ? data[0] : data const mapped: BaseContent = { - ...raw, - result: raw.result || raw.answer || raw.error || 'Sonuç bulunamadı.', + type: raw?.type || 'chat', + question: raw?.question || userMessage, + sql: raw?.sql ?? null, + answer: raw?.answer ?? null, + chart: raw?.chart, + error: raw?.error ?? null, } // 2️⃣ Cevabı store'a ekle @@ -407,68 +382,73 @@ const Assistant = () => { )} - {error ? ( + {error && (
⚠️ Hata: {error}
- ) : typeof answer === 'string' ? ( - renderFormattedText(answer) - ) : Array.isArray(answer) ? ( - answer.length === 0 ? ( + )} + + {!error && + (typeof answer === 'string' ? ( + renderFormattedText(answer) + ) : Array.isArray(answer) ? ( + answer.length === 0 ? ( +
Sonuç bulunamadı.
+ ) : ( +
+ + + + {Object.keys(answer[0]).map((col) => ( + + ))} + + + + {answer.map((row, rowIndex) => ( + + {Object.keys(row).map((col, colIndex) => { + const val = row[col] + const display = + val === null || val === undefined + ? '—' + : typeof val === 'boolean' + ? val + ? 'Evet' + : 'Hayır' + : typeof val === 'string' && val.endsWith('T00:00:00.000Z') + ? new Date(val).toLocaleDateString('tr-TR') + : String(val) + + return ( + + ) + })} + + ))} + +
+ {col} +
+ {display} +
+
+ ) + ) : typeof answer === 'object' && answer !== null && (answer as any).message ? ( + renderFormattedText(String((answer as any).message)) + ) : answer === null || answer === undefined ? (
Sonuç bulunamadı.
) : ( -
- - - - {Object.keys(answer[0]).map((col) => ( - - ))} - - - - {answer.map((row, rowIndex) => ( - - {Object.keys(row).map((col, colIndex) => { - const val = row[col] - const display = - val === null || val === undefined - ? '—' - : typeof val === 'boolean' - ? val - ? 'Evet' - : 'Hayır' - : typeof val === 'string' && val.endsWith('T00:00:00.000Z') - ? new Date(val).toLocaleDateString('tr-TR') - : String(val) - - return ( - - ) - })} - - ))} - -
- {col} -
- {display} -
-
- ) - ) : typeof answer === 'object' && answer !== null && (answer as any).message ? ( - renderFormattedText(String((answer as any).message)) - ) : ( -
-              {JSON.stringify(answer, null, 2)}
-            
- )} +
+                {JSON.stringify(answer, null, 2)}
+              
+ ))} {chart && (