AiBotAppService güncellemesi
This commit is contained in:
parent
ab955483eb
commit
9186792a98
12 changed files with 189 additions and 119 deletions
|
|
@ -8,5 +8,6 @@ public class AiBotDto : FullAuditedEntityDto<Guid>
|
|||
public string Name { get; set; }
|
||||
public string ApiUrl { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Tenants { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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\"";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PagedResultDto<AiBotDto>> 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<AiBotDto>(totalCount, dtos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<EditingFormDto>()
|
||||
{
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ namespace Sozsoft.Platform.Entities;
|
|||
public class AiBot : Entity<Guid>
|
||||
{
|
||||
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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Sozsoft.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20260623105928_Initial")]
|
||||
[Migration("20260623164647_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -699,6 +699,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Tenants")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
|
|
@ -921,9 +921,10 @@ namespace Sozsoft.Platform.Migrations
|
|||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Description = table.Column<string>(type: "text", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
ApiUrl = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false)
|
||||
Description = table.Column<string>(type: "text", nullable: true),
|
||||
Tenants = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
|
@ -696,6 +696,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Tenants")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
|
|
|
|||
|
|
@ -4,17 +4,19 @@ export interface AiBotDto extends FullAuditedEntityDto<string> {
|
|||
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
|
||||
}
|
||||
|
|
@ -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 = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{error ? (
|
||||
{error && (
|
||||
<div className="text-red-700 dark:text-red-200 bg-red-100 dark:bg-red-900/50 border border-red-300 dark:border-red-800 rounded p-2">
|
||||
⚠️ <strong>Hata:</strong> {error}
|
||||
</div>
|
||||
) : typeof answer === 'string' ? (
|
||||
renderFormattedText(answer)
|
||||
) : Array.isArray(answer) ? (
|
||||
answer.length === 0 ? (
|
||||
)}
|
||||
|
||||
{!error &&
|
||||
(typeof answer === 'string' ? (
|
||||
renderFormattedText(answer)
|
||||
) : Array.isArray(answer) ? (
|
||||
answer.length === 0 ? (
|
||||
<div className="text-gray-500 italic">Sonuç bulunamadı.</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto max-h-[400px] overflow-y-auto border border-gray-200 dark:border-gray-700 rounded">
|
||||
<table className="table-auto w-full text-sm">
|
||||
<thead className="bg-gray-100 dark:bg-gray-800 sticky top-0">
|
||||
<tr>
|
||||
{Object.keys(answer[0]).map((col) => (
|
||||
<th
|
||||
key={col}
|
||||
className="border border-gray-200 dark:border-gray-700 px-2 py-1 text-left"
|
||||
>
|
||||
{col}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{answer.map((row, rowIndex) => (
|
||||
<tr key={rowIndex} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
{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 (
|
||||
<td
|
||||
key={colIndex}
|
||||
className="border border-gray-200 dark:border-gray-700 px-2 py-1"
|
||||
>
|
||||
{display}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
) : typeof answer === 'object' && answer !== null && (answer as any).message ? (
|
||||
renderFormattedText(String((answer as any).message))
|
||||
) : answer === null || answer === undefined ? (
|
||||
<div className="text-gray-500 italic">Sonuç bulunamadı.</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto max-h-[400px] overflow-y-auto border border-gray-200 dark:border-gray-700 rounded">
|
||||
<table className="table-auto w-full text-sm">
|
||||
<thead className="bg-gray-100 dark:bg-gray-800 sticky top-0">
|
||||
<tr>
|
||||
{Object.keys(answer[0]).map((col) => (
|
||||
<th
|
||||
key={col}
|
||||
className="border border-gray-200 dark:border-gray-700 px-2 py-1 text-left"
|
||||
>
|
||||
{col}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{answer.map((row, rowIndex) => (
|
||||
<tr key={rowIndex} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
{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 (
|
||||
<td
|
||||
key={colIndex}
|
||||
className="border border-gray-200 dark:border-gray-700 px-2 py-1"
|
||||
>
|
||||
{display}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
) : typeof answer === 'object' && answer !== null && (answer as any).message ? (
|
||||
renderFormattedText(String((answer as any).message))
|
||||
) : (
|
||||
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700">
|
||||
{JSON.stringify(answer, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700">
|
||||
{JSON.stringify(answer, null, 2)}
|
||||
</pre>
|
||||
))}
|
||||
|
||||
{chart && (
|
||||
<div className="mt-4">
|
||||
|
|
|
|||
Loading…
Reference in a new issue