Skip to content

Commit

Permalink
feat: multiple copilots support
Browse files Browse the repository at this point in the history
  • Loading branch information
meta-d committed Dec 28, 2024
1 parent 8fd46c6 commit 0605af3
Show file tree
Hide file tree
Showing 23 changed files with 358 additions and 394 deletions.
39 changes: 22 additions & 17 deletions apps/cloud/src/app/@core/services/copilot-server.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { OrganizationBaseCrudService } from '@metad/cloud/state'
import { NGXLogger } from 'ngx-logger'

import { effect, inject, Injectable, signal } from '@angular/core'
import { inject, Injectable } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { OrganizationBaseCrudService } from '@metad/cloud/state'
import { toParams } from '@metad/core'
import {
BehaviorSubject,
Observable,
shareReplay,
switchMap
} from 'rxjs'
import { NGXLogger } from 'ngx-logger'
import { BehaviorSubject, Observable, shareReplay, switchMap } from 'rxjs'
import { API_COPILOT } from '../constants/app.constants'
import { ICopilotWithProvider, ICopilot as IServerCopilot, AiModelTypeEnum, ParameterRule, IAiProviderEntity, ICopilot, AiProviderRole } from '../types'

import {
AiModelTypeEnum,
AiProviderRole,
IAiProviderEntity,
ICopilot,
ICopilotWithProvider,
ParameterRule
} from '../types'

@Injectable({ providedIn: 'root' })
export class CopilotServerService extends OrganizationBaseCrudService<ICopilot> {
Expand All @@ -21,11 +21,16 @@ export class CopilotServerService extends OrganizationBaseCrudService<ICopilot>
readonly refresh$ = new BehaviorSubject(false)

private readonly modelsByType = new Map<AiModelTypeEnum, Observable<ICopilotWithProvider[]>>()
private readonly aiProviders$ = this.httpClient.get<IAiProviderEntity[]>(API_COPILOT + `/providers`).pipe(shareReplay(1))
private readonly aiProviders$ = this.httpClient
.get<IAiProviderEntity[]>(API_COPILOT + `/providers`)
.pipe(shareReplay(1))

/**
* All available copilots (enabled or tenant free quota)
*/
private readonly copilots$ = this.refresh$.pipe(
switchMap(() => this.selectOrganizationId()),
switchMap(() => this.httpClient.get<ICopilot[]>(API_COPILOT)),
switchMap(() => this.httpClient.get<ICopilot[]>(this.apiBaseUrl + `/availables`)),
shareReplay(1)
)

Expand Down Expand Up @@ -81,8 +86,8 @@ export class CopilotServerService extends OrganizationBaseCrudService<ICopilot>
}

export function injectAiProviders() {
const service = inject(CopilotServerService)
return toSignal(service.getAiProviders())
const service = inject(CopilotServerService)
return toSignal(service.getAiProviders())
}

export function injectCopilotServer() {
Expand All @@ -92,4 +97,4 @@ export function injectCopilotServer() {
export function injectCopilots() {
const server = injectCopilotServer()
return toSignal(server.getCopilots())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<input
class="block px-3 w-full h-9 bg-gray-100 text-sm rounded-lg border border-transparent appearance-none outline-none caret-primary-600 hover:border-[rgba(0,0,0,0.08)] hover:bg-gray-50 focus:bg-white focus:border-gray-300 focus:shadow-xs placeholder:text-sm placeholder:text-gray-400 false false"
[placeholder]="credential.placeholder | i18n"
type="text"
type="password"
[formControlName]="credential.variable"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import { MaterialModule } from 'apps/cloud/src/app/@shared/material.module'
import { NGXLogger } from 'ngx-logger'
import { combineLatest, combineLatestWith, filter, map, switchMap, withLatestFrom } from 'rxjs'
import { SemanticModelService } from '../../model.service'
import { createVariableSchema } from '../../schema/variable.schema'
import {
CdkDragDropContainers,
MODEL_TYPE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<mat-toolbar class="pac-nav-toolbar pac-model__shell-bar flex items-center" displayDensity="compact">
<div class="pac-nav-toolbar pac-model__shell-bar flex items-center px-1" displayDensity="compact">
@if (!modelSideMenuOpened()) {
<button mat-icon-button class="text-neutral-600" (click)="openSideMenu()">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24" class="icon-xl-heavy">
Expand All @@ -10,8 +10,11 @@
<div class="flex items-center px-4">
@if (cube()) {
<span class="text-lg">{{ cube().caption || cube().name}}</span>
<button mat-icon-button (click)="openCubeDesigner()">
<mat-icon fontSet="material-icons-outlined">settings</mat-icon>
<button mat-icon-button [matTooltip]="'PAC.MODEL.OpenCubeDesigner' | translate: {Default: 'Open cube designer'}"
matTooltipPosition="above"
(click)="toggleCubeDesigner()"
>
<mat-icon fontSet="material-icons-outlined">settings</mat-icon>
</button>
}
</div>
Expand Down Expand Up @@ -43,7 +46,7 @@
}
</span>
</nav>
</mat-toolbar>
</div>

<mat-drawer-container class="flex-1" autosize [hasBackdrop]="false">
<mat-drawer #designerDrawer class="ngm-story__designer-drawer mat-elevation-z"
Expand Down Expand Up @@ -91,7 +94,9 @@
</mat-drawer-content>
</mat-drawer-container>

<div *ngIf="!cube()" class="pac-result absolute top-0 left-0 w-full h-full z-10 flex flex-col justify-center items-center bg-white/10 backdrop-blur-md">
@if (!cube()) {
<div class="pac-result absolute top-0 left-0 w-full h-full z-10 flex flex-col justify-center items-center bg-white/10 backdrop-blur-md">
<span class="bug font-notoColorEmoji">🐞</span>
<span class="description">{{ 'PAC.MODEL.ENTITY.CubeNotFound' | translate: {Default: 'Cube not found!'} }}</span>
</div>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
}
}

.mat-mdc-tab-header {
--mdc-secondary-navigation-tab-container-height: 34px;
}

.pac-model-entity__workspace {
@apply flex flex-col;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,22 +142,24 @@ export class ModelEntityComponent implements OnInit {
}

onDesignerDrawerChange(opened: boolean) {
// this.detailsOpen.set(opened)
this.settingsService.setEditable(opened)
}

openCubeDesigner() {
this.entityService.setSelectedProperty(null)
this.settingsService.setEditable(true)
this.detailsOpen.set(true)
toggleCubeDesigner() {
if (this.detailsOpen() && !this.entityService.selectedProperty()) {
this.detailsOpen.set(false)
} else {
this.entityService.setSelectedProperty(null)
this.settingsService.setEditable(true)
this.detailsOpen.set(true)
}
}

openSub(event) {
this.router.navigate([event + '/'], { relativeTo: this.route })
}

propertySelectedChange(selected: string) {
// this.entityService.setSelectedProperty(selected)
this.detailsOpen.set(true)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<mat-toolbar class="pac__toolbar pac-model__shell-bar flex justify-between items-center">
<div class="pac__toolbar pac-model__shell-bar flex justify-between items-center px-2 py-1">
<div class="flex justify-start items-center" cdkMenuBar>
@if (isMobile()) {
<button cdkMenuItem class="pac-model__page-trigger flex-1"
Expand Down Expand Up @@ -55,7 +55,7 @@
</button> -->
</div>

</mat-toolbar>
</div>

<mat-drawer-container class="flex-1" autosize>
<mat-drawer #drawer class="ngm-story__designer-drawer ngm-sidenav-container-bg-transparent mat-elevation-z"
Expand Down
50 changes: 22 additions & 28 deletions apps/cloud/src/app/features/services/copilot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from 'rxjs'
import { ICopilot as IServerCopilot } from '../../@core/types'
import { AgentService } from '../../@core/services/agent.service'
import { Store, XpertService } from '../../@core'
import { CopilotServerService, Store, XpertService } from '../../@core'


const baseUrl = environment.API_BASE_URL
Expand All @@ -35,18 +35,12 @@ export class PACCopilotService extends NgmCopilotService {
readonly xpertService = inject(XpertService)
readonly router = inject(Router)
readonly #agentService = inject(AgentService)
readonly copilotServer = inject(CopilotServerService)

readonly refresh$ = new BehaviorSubject(false)

// Init copilot config
private _userSub = this.#store.user$
.pipe(
map((user) => user?.id),
startWith(null),
distinctUntilChanged(),
filter(Boolean),
switchMap(() => this.#store.selectOrganizationId()),
switchMap(() => this.httpClient.get<ICopilot[]>(API_PREFIX + '/copilot')),
private _userSub = this.copilotServer.getCopilots().pipe(
takeUntilDestroyed()
)
.subscribe((items) => {
Expand Down Expand Up @@ -161,25 +155,25 @@ export class PACCopilotService extends NgmCopilotService {
return `Bearer ${this.#store.token}`
}

async upsertItems(items: Partial<IServerCopilot[]>) {
items = await Promise.all(
items.map((item) =>
firstValueFrom(this.httpClient.post<ICopilot>(API_PREFIX + '/copilot', item.id ? item : omit(item, 'id')))
)
)

this.copilots.update((copilots) => {
items.forEach((item) => {
if (item.id) {
copilots = copilots.filter((_) => _.id !== item.id)
}
copilots.push(item as ICopilot)
})
return copilots
})

this.refresh$.next(true)
}
// async upsertItems(items: Partial<IServerCopilot[]>) {
// items = await Promise.all(
// items.map((item) =>
// firstValueFrom(this.httpClient.post<ICopilot>(API_PREFIX + '/copilot', item.id ? item : omit(item, 'id')))
// )
// )

// this.copilots.update((copilots) => {
// items.forEach((item) => {
// if (item.id) {
// copilots = copilots.filter((_) => _.id !== item.id)
// }
// copilots.push(item as ICopilot)
// })
// return copilots
// })

// this.refresh$.next(true)
// }

enableCopilot(): void {
this.router.navigate(['settings', 'copilot'])
Expand Down
101 changes: 64 additions & 37 deletions apps/cloud/src/app/features/setting/copilot/basic/basic.component.html
Original file line number Diff line number Diff line change
@@ -1,61 +1,88 @@
@if (quotaCopilots()?.length) {
<div class="text-lg px-2 py-1">
<div class="text-lg px-2 py-2">
{{ 'PAC.Copilot.FreeQuota' | translate: {Default: 'Free Quota'} }}
</div>

@for (copilot of quotaCopilots(); track copilot.id) {
<copilot-provider @disappear1 readonly [providerId]="copilot.modelProvider.id" [usage]="copilot.usage" class="shadow-sm mb-2 rounded-xl border-[0.5px] border-black/5"/>
<copilot-provider @disappear1 readonly class="shadow-sm mb-2 rounded-xl border-[0.5px] border-black/5"
[providerId]="copilot.modelProvider.id"
[usage]="copilot.usage"
/>
}
}

@if (quotaCopilots()?.length) {
<div class="text-lg px-2 py-1">
<div class="text-lg px-2 py-4">
{{ 'PAC.Copilot.CustomCopilots' | translate: {Default: 'Custom Copilots'} }}
</div>
}

<mat-accordion >
<mat-expansion-panel hideToggle class="mat-elevation-z" [disabled]="!primary()?.enabled" [expanded]="!!primary()?.enabled">
<mat-expansion-panel #expansion hideToggle class="mat-elevation-z" [disabled]="!primary()?.enabled" [expanded]="!!primary()?.enabled">
<mat-expansion-panel-header>
<mat-panel-title>{{'PAC.KEY_WORDS.Copilot' | translate: {Default: 'Copilot'} }}({{'PAC.KEY_WORDS.Primary' | translate: {Default: 'Primary'} }})</mat-panel-title>
<mat-panel-title>{{'PAC.KEY_WORDS.Primary' | translate: {Default: 'Primary'} }}</mat-panel-title>
<mat-panel-description class="flex-1">
<mat-slide-toggle labelPosition="before" ngm-density small [matTooltip]="'PAC.Copilot.EnableCopilot' | translate: {Default: 'Enable Copilot'}"
<mat-slide-toggle labelPosition="before" ngm-density small
[matTooltip]="'PAC.Copilot.EnableCopilot' | translate: {Default: 'Enable Copilot'}"
matTooltipPosition="above"
[checked]="primary()?.enabled"
(change)="onToggle(eAiProviderRole.Primary, primary()?.enabled)"
(click)="$event.stopPropagation();">
</mat-slide-toggle>
</mat-panel-description>
</mat-expansion-panel-header>

<pac-copilot-form [role]="eAiProviderRole.Primary"></pac-copilot-form>
</mat-expansion-panel>

<mat-expansion-panel hideToggle class="mat-elevation-z" [disabled]="!secondary()?.enabled" [expanded]="!!secondary()?.enabled">
<mat-expansion-panel-header>
<mat-panel-title>{{'PAC.KEY_WORDS.Copilot' | translate: {Default: 'Copilot'} }}({{'PAC.KEY_WORDS.Secondary' | translate: {Default: 'Secondary'} }})</mat-panel-title>
<mat-panel-description class="flex-1">
<mat-slide-toggle labelPosition="before" ngm-density small [matTooltip]="'PAC.Copilot.EnableCopilot' | translate: {Default: 'Enable Copilot'}"
[checked]="secondary()?.enabled"
(change)="onToggle(eAiProviderRole.Secondary, secondary()?.enabled)"
(change)="onToggle(null, eAiProviderRole.Primary, primary()?.enabled, expansion)"
(click)="$event.stopPropagation();">
</mat-slide-toggle>
</mat-panel-description>
</mat-expansion-panel-header>

<pac-copilot-form [role]="eAiProviderRole.Secondary"></pac-copilot-form>
<pac-copilot-form [copilot]="primary()" />
</mat-expansion-panel>

<mat-expansion-panel hideToggle class="mat-elevation-z" [disabled]="!embedding()?.enabled" [expanded]="!!embedding()?.enabled">
<mat-expansion-panel-header>
<mat-panel-title>{{'PAC.KEY_WORDS.Copilot' | translate: {Default: 'Copilot'} }}({{'PAC.KEY_WORDS.Embedding' | translate: {Default: 'Embedding'} }})</mat-panel-title>
<mat-panel-description class="flex-1">
<mat-slide-toggle labelPosition="before" ngm-density small [matTooltip]="'PAC.Copilot.EnableCopilot' | translate: {Default: 'Enable Copilot'}"
[checked]="embedding()?.enabled"
(change)="onToggle(eAiProviderRole.Embedding, embedding()?.enabled)"
(click)="$event.stopPropagation();">
</mat-slide-toggle>
</mat-panel-description>
</mat-expansion-panel-header>

<pac-copilot-form [role]="eAiProviderRole.Embedding" embedding></pac-copilot-form>
</mat-expansion-panel>
@for (copilot of copilots(); track copilot.id) {
<mat-expansion-panel #expansion hideToggle class="mat-elevation-z" [disabled]="!copilot.enabled">
<mat-expansion-panel-header>
<mat-panel-title>
{{'PAC.KEY_WORDS.' + (copilot.role | capitalize) | translate: { Default: (copilot.role | capitalize) } }}
</mat-panel-title>
<mat-panel-description class="flex-1">
<mat-slide-toggle labelPosition="before" ngm-density small
[matTooltip]="'PAC.Copilot.EnableCopilot' | translate: {Default: 'Enable Copilot'}"
matTooltipPosition="above"
[checked]="copilot.enabled"
(change)="onToggle(copilot, null, copilot.enabled, expansion)"
(click)="$event.stopPropagation();">
</mat-slide-toggle>
@if (!copilot.enabled) {
<button type="button" class="btn pressable danger rounded-full w-6 h-6 p-0 justify-center ml-2"
[matTooltip]="'PAC.ACTIONS.Delete' | translate: {Default: 'Delete'}"
matTooltipPosition="above"
(click)="deleteCopilot(copilot)">
<i class="ri-close-line"></i>
</button>
}
</mat-panel-description>
</mat-expansion-panel-header>

<pac-copilot-form [copilot]="copilot" />
</mat-expansion-panel>
}
</mat-accordion>

<div class="flex justify-end mt-2">
<button type="button" class="btn btn-primary btn-large pressable"
[cdkMenuTriggerFor]="addMenu"
>
<i class="ri-apps-2-add-line"></i> {{ 'PAC.ACTIONS.Add' | translate: {Default: 'Add'} }}
</button>
</div>

<ng-template #addMenu>
<div cdkMenu class="cdk-menu__large w-[200px] divide-y-2 p-2">
@for (provider of providers(); track provider.value) {
<div cdkMenuItem (click)="addProvider(provider.value)">
{{ 'PAC.KEY_WORDS.' + provider.label | translate: {Default: provider.label } }}
</div>
}
</div>
</ng-template>

@if (loading()) {
<ngm-spin class="absolute top-0 left-0 w-full h-full" />
}
Loading

0 comments on commit 0605af3

Please sign in to comment.