From 621577e736f92cfd8501659372c7a6421ffcbbbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Jun 2025 08:05:39 +0000 Subject: [PATCH 1/5] Initial plan From 994ac4f223922a75eb0faa3b8b042bf7cd3ba91c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Jun 2025 08:13:41 +0000 Subject: [PATCH 2/5] Initial plan for AI endpoint response modal Co-authored-by: maximgorbatyuk <13348685+maximgorbatyuk@users.noreply.github.com> --- karma.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index 78088e17..c5f826b2 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -2,7 +2,7 @@ // https://karma-runner.github.io/1.0/config/configuration-file.html const process = require('process'); -process.env.CHROME_BIN = require('puppeteer').executablePath(); +process.env.CHROME_BIN = process.env.CHROME_BIN || '/usr/bin/google-chrome'; module.exports = function (config) { config.set({ From 77860507e04389ac5ece859a7121eec5cefd19e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Jun 2025 08:18:01 +0000 Subject: [PATCH 3/5] Add AI analysis endpoint and modal for companies admin page Co-authored-by: maximgorbatyuk <13348685+maximgorbatyuk@users.noreply.github.com> --- src/app/models/open-ai.model.ts | 11 ++++ .../companies-admin-page.component.html | 39 ++++++++++++ .../companies-admin-page.component.spec.ts | 61 +++++++++++++++++++ .../companies-admin-page.component.ts | 50 +++++++++++++++ src/app/services/companies.service.ts | 7 +++ 5 files changed, 168 insertions(+) diff --git a/src/app/models/open-ai.model.ts b/src/app/models/open-ai.model.ts index fd4d37a3..6946214f 100644 --- a/src/app/models/open-ai.model.ts +++ b/src/app/models/open-ai.model.ts @@ -34,3 +34,14 @@ export interface OpenAiAnalysis { bearer: string; report: OpenAiReport; } + +export interface OpenAiChatResult { + content: string; + role: string; + finishReason: string; + usage: { + promptTokens: number; + completionTokens: number; + totalTokens: number; + }; +} diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html index 5a377b9b..6c3ce1e9 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html @@ -59,6 +59,7 @@ Slug Создана Обновлена + Действия @@ -77,6 +78,15 @@ {{ item.slug }} {{ item.createdAt | date: "yyyy-MM-dd HH:mm" }} {{ item.updatedAt | date: "yyyy-MM-dd HH:mm" }} + + + @@ -150,3 +160,32 @@ + + +
+
+ +
+
+ + +
+
+
diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts index 0d8d10f9..41b3bcc0 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts @@ -9,10 +9,13 @@ import { import { CompaniesAdminPageComponent } from "./companies-admin-page.component"; import { CompaniesService } from "@services/companies.service"; +import { of } from "rxjs"; +import { Company } from "@models/companies.model"; describe("CompaniesAdminPageComponent", () => { let component: CompaniesAdminPageComponent; let fixture: ComponentFixture; + let companiesService: CompaniesService; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -26,6 +29,7 @@ describe("CompaniesAdminPageComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(CompaniesAdminPageComponent); component = fixture.componentInstance; + companiesService = TestBed.inject(CompaniesService); fixture.detectChanges(); }); @@ -63,4 +67,61 @@ describe("CompaniesAdminPageComponent", () => { expect(component.search).toHaveBeenCalled(); }); + + it("should show AI analysis modal when showAiAnalysis is called", () => { + const mockCompany: Company = { + id: "1", + name: "Test Company", + slug: "test-company", + } as Company; + + const mockAiResult = { + content: "Test analysis", + role: "assistant", + finishReason: "stop", + usage: { + promptTokens: 10, + completionTokens: 20, + totalTokens: 30, + }, + }; + + spyOn(companiesService, "getOpenAiAnalysis").and.returnValue( + of(mockAiResult), + ); + + component.showAiAnalysis(mockCompany); + + expect(companiesService.getOpenAiAnalysis).toHaveBeenCalledWith( + "test-company", + ); + expect(component.aiAnalysisData).toEqual(mockAiResult); + expect(component.aiAnalysisJsonContent).toContain("Test analysis"); + }); + + it("should close AI analysis modal", () => { + component.aiAnalysisData = {} as any; + component.aiAnalysisJsonContent = "test content"; + + component.onAiAnalysisModalClose(); + + expect(component.aiAnalysisData).toBeNull(); + expect(component.aiAnalysisJsonContent).toBe(""); + }); + + it("should copy AI analysis content", () => { + const mockInputElement = { + select: jasmine.createSpy("select"), + setSelectionRange: jasmine.createSpy("setSelectionRange"), + }; + + spyOn(document, "execCommand"); + + component.copyAiAnalysis(mockInputElement); + + expect(mockInputElement.select).toHaveBeenCalled(); + expect(document.execCommand).toHaveBeenCalledWith("copy"); + expect(mockInputElement.setSelectionRange).toHaveBeenCalledWith(0, 0); + expect(component.copyBtnTitle).toBe("Скопировано"); + }); }); diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.ts b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.ts index 13b52730..02fc53bf 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.ts +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.ts @@ -13,6 +13,7 @@ import { EditCompanyForm } from "../shared/edit-company-form"; import { AlertService } from "@shared/components/alert/services/alert.service"; import { DialogMessage } from "@shared/components/dialogs/models/dialog-message"; import { ConfirmMsg } from "@shared/components/dialogs/models/confirm-msg"; +import { OpenAiChatResult } from "@models/open-ai.model"; @Component({ templateUrl: "./companies-admin-page.component.html", @@ -31,6 +32,19 @@ export class CompaniesAdminPageComponent implements OnInit, OnDestroy { itemToEdit: Company | null = null; confirmDeletionMessage: DialogMessage | null = null; + // AI Analysis modal properties + aiAnalysisData: OpenAiChatResult | null = null; + aiAnalysisJsonContent: string = ""; + + // Copy button properties (similar to interview markdown modal) + private readonly copyBtnDefaultTitle = "Копировать"; + private readonly copyBtnDefaultIcon = "bi bi-clipboard2-check"; + private readonly copiedBtnTitle = "Скопировано"; + private readonly copiedBtnIcon = "bi bi-check2"; + + copyBtnTitle = this.copyBtnDefaultTitle; + copyBtnIcon = this.copyBtnDefaultIcon; + get searchButtonShouldBeEnabled(): boolean { return ( this.searchQuery != null && @@ -161,4 +175,40 @@ export class CompaniesAdminPageComponent implements OnInit, OnDestroy { ), ); } + + showAiAnalysis(company: Company): void { + this.service + .getOpenAiAnalysis(company.slug) + .pipe(untilDestroyed(this)) + .subscribe({ + next: (result) => { + this.aiAnalysisData = result; + this.aiAnalysisJsonContent = JSON.stringify(result, null, 2); + }, + error: (error) => { + this.alert.error("Ошибка при получении AI анализа: " + error.message); + }, + }); + } + + onAiAnalysisModalClose(): void { + this.aiAnalysisData = null; + this.aiAnalysisJsonContent = ""; + this.copyBtnTitle = this.copyBtnDefaultTitle; + this.copyBtnIcon = this.copyBtnDefaultIcon; + } + + copyAiAnalysis(inputElement: any): void { + inputElement.select(); + document.execCommand("copy"); + inputElement.setSelectionRange(0, 0); + + this.copyBtnTitle = this.copiedBtnTitle; + this.copyBtnIcon = this.copiedBtnIcon; + + setTimeout(() => { + this.copyBtnTitle = this.copyBtnDefaultTitle; + this.copyBtnIcon = this.copyBtnDefaultIcon; + }, 1000); + } } diff --git a/src/app/services/companies.service.ts b/src/app/services/companies.service.ts index b6607b52..94521730 100644 --- a/src/app/services/companies.service.ts +++ b/src/app/services/companies.service.ts @@ -9,6 +9,7 @@ import { import { PageParams } from "@models/page-params"; import { ConvertObjectToHttpParams } from "@shared/value-objects/convert-object-to-http"; import { PaginatedList } from "@models/paginated-list"; +import { OpenAiChatResult } from "@models/open-ai.model"; export interface CompaniesSearchParams extends PageParams { searchQuery: string | null; @@ -146,4 +147,10 @@ export class CompaniesService { {}, ); } + + getOpenAiAnalysis(companyIdentifier: string): Observable { + return this.api.get( + this.apiUrl + companyIdentifier + "/open-ai-analysis", + ); + } } From 0304465694c4726854578eda5ff7761e88466b96 Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Sun, 29 Jun 2025 13:55:14 +0500 Subject: [PATCH 4/5] Fixes --- karma.conf.js | 2 +- src/app/models/open-ai.model.ts | 17 ++++++++-------- .../companies-admin-page.component.spec.ts | 20 ++++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index c5f826b2..78088e17 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -2,7 +2,7 @@ // https://karma-runner.github.io/1.0/config/configuration-file.html const process = require('process'); -process.env.CHROME_BIN = process.env.CHROME_BIN || '/usr/bin/google-chrome'; +process.env.CHROME_BIN = require('puppeteer').executablePath(); module.exports = function (config) { config.set({ diff --git a/src/app/models/open-ai.model.ts b/src/app/models/open-ai.model.ts index 6946214f..2f45c0f3 100644 --- a/src/app/models/open-ai.model.ts +++ b/src/app/models/open-ai.model.ts @@ -35,13 +35,14 @@ export interface OpenAiAnalysis { report: OpenAiReport; } +export interface OpenAiChatChoise { + message: { + role: string; + content: string; + } +} + export interface OpenAiChatResult { - content: string; - role: string; - finishReason: string; - usage: { - promptTokens: number; - completionTokens: number; - totalTokens: number; - }; + isSuccess: boolean; + choises: Array; } diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts index 41b3bcc0..9a721a5e 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.spec.ts @@ -11,6 +11,7 @@ import { CompaniesAdminPageComponent } from "./companies-admin-page.component"; import { CompaniesService } from "@services/companies.service"; import { of } from "rxjs"; import { Company } from "@models/companies.model"; +import { OpenAiChatResult } from "@models/open-ai.model"; describe("CompaniesAdminPageComponent", () => { let component: CompaniesAdminPageComponent; @@ -75,15 +76,16 @@ describe("CompaniesAdminPageComponent", () => { slug: "test-company", } as Company; - const mockAiResult = { - content: "Test analysis", - role: "assistant", - finishReason: "stop", - usage: { - promptTokens: 10, - completionTokens: 20, - totalTokens: 30, - }, + const mockAiResult: OpenAiChatResult = { + isSuccess: true, + choises: [ + { + message: { + role: "assistant", + content: "Test analysis", + }, + }, + ], }; spyOn(companiesService, "getOpenAiAnalysis").and.returnValue( From dfe171f5f8c2b5f401bf5a947312e551b0786069 Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Sun, 29 Jun 2025 14:16:04 +0500 Subject: [PATCH 5/5] Fixed model --- src/app/models/open-ai.model.ts | 2 +- .../companies-admin-page.component.html | 14 ++++----- .../companies-admin-page.component.ts | 4 +-- .../telegram-bot.component.spec.ts | 30 +++++++++++-------- .../telegram-bot/telegram-bot.component.ts | 2 +- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/app/models/open-ai.model.ts b/src/app/models/open-ai.model.ts index 2f45c0f3..98752610 100644 --- a/src/app/models/open-ai.model.ts +++ b/src/app/models/open-ai.model.ts @@ -39,7 +39,7 @@ export interface OpenAiChatChoise { message: { role: string; content: string; - } + }; } export interface OpenAiChatResult { diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html index 6c3ce1e9..5ac9212f 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.html @@ -169,15 +169,15 @@ >
- +
-