From 0235aa500701deaecf4db056773fc17f977a9514 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Thu, 26 Jan 2023 20:53:00 +0700 Subject: [PATCH] New Indexer fixes (#2546) Signed-off-by: Andrey Sobolev --- models/attachment/src/index.ts | 1 - models/core/src/core.ts | 9 +- models/core/src/index.ts | 4 +- models/recruit/src/index.ts | 109 ++++- models/recruit/src/plugin.ts | 8 +- packages/core/src/classes.ts | 10 +- packages/core/src/component.ts | 4 +- .../components/IndexedDocumentPreview.svelte | 20 +- .../src/components/SpaceInfo.svelte | 6 +- .../src/components/SpaceSelect.svelte | 10 +- .../src/components/SpacesPopup.svelte | 7 +- .../ui/src/components/icons/Folder.svelte | 3 +- .../src/components/AttachmentPresenter.svelte | 3 + .../components/AttachmentsPresenter.svelte | 2 + .../src/components/CommentsPresenter.svelte | 2 + plugins/recruit-assets/lang/en.json | 7 +- plugins/recruit-assets/lang/ru.json | 7 +- .../src/components/CreateApplication.svelte | 57 ++- .../src/components/MatchVacancy.svelte | 258 +++++++++++ .../src/components/VacancyCard.svelte | 25 +- plugins/recruit-resources/src/index.ts | 5 +- plugins/recruit-resources/src/plugin.ts | 8 +- plugins/recruit/src/index.ts | 13 + plugins/view-assets/lang/en.json | 3 +- plugins/view-assets/lang/ru.json | 3 +- .../src/components/MarkupPreviewPopup.svelte | 36 ++ .../src/components/Table.svelte | 25 +- .../src/components/TableBrowser.svelte | 31 +- .../inference}/SourcePresenter.svelte | 12 +- plugins/view-resources/src/index.ts | 51 ++- plugins/view-resources/src/inference.ts | 33 ++ plugins/view-resources/src/plugin.ts | 3 +- plugins/view-resources/src/utils.ts | 19 + .../src/components/SpecialView.svelte | 18 +- pods/server/src/server.ts | 91 ++-- server-plugins/openai/package.json | 1 + server-plugins/openai/src/openai.ts | 68 +-- server-plugins/openai/src/resources.ts | 433 ++++++++++++------ server/core/src/indexer/content.ts | 2 + server/core/src/indexer/field.ts | 2 + server/core/src/indexer/fulltextPush.ts | 2 + server/core/src/indexer/indexer.ts | 17 +- server/core/src/indexer/summary.ts | 36 +- server/core/src/indexer/types.ts | 17 +- server/core/src/indexer/utils.ts | 56 ++- server/core/src/storage.ts | 3 +- server/elastic/src/adapter.ts | 14 +- server/translate/src/retranslate.ts | 17 +- server/translate/src/types.ts | 2 +- server/ws/src/server.ts | 33 +- 50 files changed, 1250 insertions(+), 356 deletions(-) create mode 100644 plugins/recruit-resources/src/components/MatchVacancy.svelte create mode 100644 plugins/view-resources/src/components/MarkupPreviewPopup.svelte rename plugins/{workbench-resources/src/components/search => view-resources/src/components/inference}/SourcePresenter.svelte (78%) create mode 100644 plugins/view-resources/src/inference.ts diff --git a/models/attachment/src/index.ts b/models/attachment/src/index.ts index b53ef4ba46..11040179f9 100644 --- a/models/attachment/src/index.ts +++ b/models/attachment/src/index.ts @@ -51,7 +51,6 @@ export class TAttachment extends TAttachedDoc implements Attachment { size!: number @Prop(TypeString(), attachment.string.Type) - @Index(IndexKind.FullText) type!: string @Prop(TypeTimestamp(), attachment.string.Date) diff --git a/models/core/src/core.ts b/models/core/src/core.ts index 655f2493fc..7291b69a74 100644 --- a/models/core/src/core.ts +++ b/models/core/src/core.ts @@ -37,6 +37,7 @@ import { FullTextData, FullTextSearchContext, IndexKind, + IndexStageState, Interface, Mixin, Obj, @@ -251,7 +252,13 @@ export class TDocIndexState extends TDoc implements DocIndexState { removed!: boolean // States for diffetent stages - stages!: Record + stages!: Record +} + +@Model(core.class.IndexStageState, core.class.Doc, DOMAIN_DOC_INDEX_STATE) +export class TIndexStageState extends TDoc implements IndexStageState { + stageId!: string + attributes!: Record } @MMixin(core.mixin.FullTextSearchContext, core.class.Class) diff --git a/models/core/src/index.ts b/models/core/src/index.ts index 77aca7991c..87fc5937ee 100644 --- a/models/core/src/index.ts +++ b/models/core/src/index.ts @@ -46,7 +46,8 @@ import { TTypeRelatedDocument, TTypeString, TTypeTimestamp, - TVersion + TVersion, + TIndexStageState } from './core' import { TAccount, TSpace } from './security' import { TUserStatus } from './transient' @@ -99,6 +100,7 @@ export function createModel (builder: Builder): void { TFulltextData, TTypeRelatedDocument, TDocIndexState, + TIndexStageState, TFullTextSearchContext, TConfiguration, TConfigurationElement diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index d3791a6b04..0f028b2f64 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -23,6 +23,7 @@ import { Mixin, Model, Prop, + ReadOnly, TypeBoolean, TypeDate, TypeMarkup, @@ -34,14 +35,22 @@ import attachment from '@hcengineering/model-attachment' import calendar from '@hcengineering/model-calendar' import chunter from '@hcengineering/model-chunter' import contact, { TOrganization, TPerson } from '@hcengineering/model-contact' -import core, { TSpace } from '@hcengineering/model-core' +import core, { TAttachedDoc, TSpace } from '@hcengineering/model-core' import presentation from '@hcengineering/model-presentation' import tags from '@hcengineering/model-tags' -import task, { actionTemplates, TSpaceWithStates, TTask } from '@hcengineering/model-task' +import task, { actionTemplates, DOMAIN_TASK, TSpaceWithStates, TTask } from '@hcengineering/model-task' import view, { actionTemplates as viewTemplates, createAction } from '@hcengineering/model-view' import workbench, { Application, createNavigateAction } from '@hcengineering/model-workbench' -import { IntlString } from '@hcengineering/platform' -import { Applicant, Candidate, Candidates, recruitId, Vacancy, VacancyList } from '@hcengineering/recruit' +import { getEmbeddedLabel, IntlString } from '@hcengineering/platform' +import { + Applicant, + ApplicantMatch, + Candidate, + Candidates, + recruitId, + Vacancy, + VacancyList +} from '@hcengineering/recruit' import setting from '@hcengineering/setting' import { KeyBinding } from '@hcengineering/view' import recruit from './plugin' @@ -104,6 +113,12 @@ export class TCandidate extends TPerson implements Candidate { @Prop(Collection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews) reviews?: number + + @Prop( + Collection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')), + getEmbeddedLabel('Vacancy Matches') + ) + vacancyMatch?: number } @Mixin(recruit.mixin.VacancyList, contact.class.Organization) @@ -136,8 +151,33 @@ export class TApplicant extends TTask implements Applicant { declare assignee: Ref | null } +@Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK) +@UX(recruit.string.Application, recruit.icon.Application, recruit.string.ApplicationShort, 'number') +export class TApplicantMatch extends TAttachedDoc implements ApplicantMatch { + // We need to declare, to provide property with label + @Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Talent) + @Index(IndexKind.Indexed) + declare attachedTo: Ref + + @Prop(TypeBoolean(), getEmbeddedLabel('Complete')) + @ReadOnly() + complete!: boolean + + @Prop(TypeString(), getEmbeddedLabel('Vacancy')) + @ReadOnly() + vacancy!: string + + @Prop(TypeString(), getEmbeddedLabel('Summary')) + @ReadOnly() + summary!: string + + @Prop(TypeMarkup(), getEmbeddedLabel('Response')) + @ReadOnly() + response!: string +} + export function createModel (builder: Builder): void { - builder.createModel(TVacancy, TCandidates, TCandidate, TApplicant, TReview, TOpinion, TVacancyList) + builder.createModel(TVacancy, TCandidates, TCandidate, TApplicant, TReview, TOpinion, TVacancyList, TApplicantMatch) builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, { view: { @@ -363,7 +403,7 @@ export function createModel (builder: Builder): void { } ] }, - recruit.viewlet.StatusTableApplicant + recruit.viewlet.TableApplicant ) builder.createDoc( view.class.Viewlet, @@ -386,7 +426,19 @@ export function createModel (builder: Builder): void { ], hiddenKeys: ['name'] }, - recruit.viewlet.TableApplicant + recruit.viewlet.ApplicantTable + ) + + builder.createDoc( + view.class.Viewlet, + core.space.Model, + { + attachTo: recruit.class.ApplicantMatch, + descriptor: view.viewlet.Table, + config: ['', 'response', 'attachedTo', 'space', 'modifiedOn'], + hiddenKeys: [] + }, + recruit.viewlet.TableApplicantMatch ) const applicantKanbanLookup: Lookup = { @@ -445,6 +497,14 @@ export function createModel (builder: Builder): void { presenter: recruit.component.ApplicationsPresenter }) + builder.mixin(recruit.class.ApplicantMatch, core.class.Class, view.mixin.ObjectPresenter, { + presenter: recruit.component.ApplicationMatchPresenter + }) + + builder.mixin(recruit.class.ApplicantMatch, core.class.Class, view.mixin.CollectionPresenter, { + presenter: recruit.component.ApplicationMatchPresenter + }) + builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectPresenter, { presenter: recruit.component.VacancyPresenter }) @@ -643,6 +703,15 @@ export function createModel (builder: Builder): void { } }) + createAction(builder, { + ...viewTemplates.open, + target: recruit.class.ApplicantMatch, + context: { + mode: ['browser', 'context'], + group: 'create' + } + }) + function createGotoSpecialAction (builder: Builder, id: string, key: KeyBinding, label: IntlString): void { createNavigateAction(builder, key, label, recruit.app.Recruit as Ref, { application: recruitId, @@ -822,6 +891,32 @@ export function createModel (builder: Builder): void { }, recruit.filter.None ) + + // Allow to use fuzzy search for mixins + builder.mixin(recruit.class.Vacancy, core.class.Class, core.mixin.FullTextSearchContext, { + fullTextSummary: true + }) + + createAction(builder, { + label: recruit.string.MatchVacancy, + icon: recruit.icon.Vacancy, + action: view.actionImpl.ShowPopup, + actionProps: { + component: recruit.component.MatchVacancy, + element: 'top', + fillProps: { + _objects: 'objects' + } + }, + input: 'any', + category: recruit.category.Recruit, + keyBinding: [], + target: recruit.mixin.Candidate, + context: { + mode: ['context', 'browser'], + group: 'create' + } + }) } export { recruitOperation } from './migration' diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts index 4cc952f0ed..4f5633bd45 100644 --- a/models/recruit/src/plugin.ts +++ b/models/recruit/src/plugin.ts @@ -88,7 +88,10 @@ export default mergeIds(recruitId, recruit, { NewCandidateHeader: '' as AnyComponent, ApplicantFilter: '' as AnyComponent, VacancyList: '' as AnyComponent, - VacancyTemplateEditor: '' as AnyComponent + VacancyTemplateEditor: '' as AnyComponent, + ApplicationMatchPresenter: '' as AnyComponent, + + MatchVacancy: '' as AnyComponent }, template: { DefaultVacancy: '' as Ref, @@ -103,8 +106,9 @@ export default mergeIds(recruitId, recruit, { viewlet: { TableCandidate: '' as Ref, TableVacancy: '' as Ref, - StatusTableApplicant: '' as Ref, + ApplicantTable: '' as Ref, TableApplicant: '' as Ref, + TableApplicantMatch: '' as Ref, CalendarReview: '' as Ref, TableReview: '' as Ref } diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index 1e55bc162d..656250cd00 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -350,7 +350,7 @@ export interface DocIndexState extends Doc { attachedToClass?: Ref> // States for stages - stages: Record + stages: Record removed: boolean @@ -362,6 +362,14 @@ export interface DocIndexState extends Doc { shortSummary?: Markup | null } +/** + * @public + */ +export interface IndexStageState extends Doc { + stageId: string + attributes: Record +} + /** * @public * diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index eb7bf34a64..58e6fdd4eb 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -41,7 +41,8 @@ import type { Type, UserStatus, Configuration, - ConfigurationElement + ConfigurationElement, + IndexStageState } from './classes' import type { Tx, @@ -102,6 +103,7 @@ export default plugin(coreId, { FulltextData: '' as Ref>, TypeRelatedDocument: '' as Ref>>, DocIndexState: '' as Ref>, + IndexStageState: '' as Ref>, Configuration: '' as Ref> }, diff --git a/packages/presentation/src/components/IndexedDocumentPreview.svelte b/packages/presentation/src/components/IndexedDocumentPreview.svelte index 34f2d69f08..083c284d3a 100644 --- a/packages/presentation/src/components/IndexedDocumentPreview.svelte +++ b/packages/presentation/src/components/IndexedDocumentPreview.svelte @@ -26,7 +26,7 @@ } let search = '' - $: summary = (indexDoc?.attributes as any).summary + $: summary = indexDoc?.fullSummary ?? undefined $: attributes = indexDoc !== undefined @@ -51,10 +51,17 @@ - +
{#if summary} + {#if search.length > 0} + Result: + {#each summary.split('\n').filter((line) => line.toLowerCase().includes(search.toLowerCase())) as line} + {line} + {/each} +
+ {/if} Summary: {#each summary.split('\n') as line} {@const hl = search.length > 0 && line.toLowerCase().includes(search.toLowerCase())} @@ -74,6 +81,13 @@
{#each attr[1] as doc}
1}> + {#if search.length > 0} + Result: + {#each doc.filter((line) => line.toLowerCase().includes(search.toLowerCase())) as line} + {line} + {/each} +
+ {/if} {#each doc as line} {@const hl = search.length > 0 && line.toLowerCase().includes(search.toLowerCase())} {line} @@ -100,7 +114,7 @@ color: black; user-select: text; .highlight { - color: red; + color: blue; } } diff --git a/packages/presentation/src/components/SpaceInfo.svelte b/packages/presentation/src/components/SpaceInfo.svelte index a7910976c4..c6b04daf13 100644 --- a/packages/presentation/src/components/SpaceInfo.svelte +++ b/packages/presentation/src/components/SpaceInfo.svelte @@ -13,18 +13,18 @@ // limitations under the License. -->
-
+
{#if subtitle}
{subtitle}
{/if}
diff --git a/packages/presentation/src/components/SpaceSelect.svelte b/packages/presentation/src/components/SpaceSelect.svelte index d6046ea552..82849d46db 100644 --- a/packages/presentation/src/components/SpaceSelect.svelte +++ b/packages/presentation/src/components/SpaceSelect.svelte @@ -45,13 +45,14 @@ export let create: ObjectCreate | undefined = undefined export let labelDirection: TooltipAlignment | undefined = undefined export let kind: ButtonKind = 'no-border' - export let size: ButtonSize = 'small' + export let size: ButtonSize = 'large' export let justify: 'left' | 'center' = 'center' export let width: string | undefined = undefined export let allowDeselect = false export let component: AnySvelteComponent | undefined = undefined export let componentProps: any | undefined = undefined export let autoSelect = true + export let readonly = false let selected: Space | undefined @@ -75,11 +76,15 @@ $: updateSelected(value) const showSpacesPopup = (ev: MouseEvent) => { + if (readonly) { + return + } showPopup( SpacesPopup, { _class, label, + size, allowDeselect, spaceOptions: { ...(spaceOptions ?? {}), sort: { ...(spaceOptions?.sort ?? {}), modifiedOn: -1 } }, selected: selected?._id, @@ -109,6 +114,7 @@ diff --git a/packages/presentation/src/components/SpacesPopup.svelte b/packages/presentation/src/components/SpacesPopup.svelte index a66e197dda..f2e582392a 100644 --- a/packages/presentation/src/components/SpacesPopup.svelte +++ b/packages/presentation/src/components/SpacesPopup.svelte @@ -14,7 +14,7 @@ --> diff --git a/plugins/attachment-resources/src/components/AttachmentPresenter.svelte b/plugins/attachment-resources/src/components/AttachmentPresenter.svelte index 3ebd476bd0..acab376228 100644 --- a/plugins/attachment-resources/src/components/AttachmentPresenter.svelte +++ b/plugins/attachment-resources/src/components/AttachmentPresenter.svelte @@ -46,6 +46,7 @@
{#if openEmbedded(value.type)} +
{ @@ -71,6 +72,7 @@
{iconLabel(value.name)} {#if removable} +
{ @@ -86,6 +88,7 @@ {/if}
{#if openEmbedded(value.type)} +
{ diff --git a/plugins/attachment-resources/src/components/AttachmentsPresenter.svelte b/plugins/attachment-resources/src/components/AttachmentsPresenter.svelte index 7ed7bb90f5..74f25c79cd 100644 --- a/plugins/attachment-resources/src/components/AttachmentsPresenter.svelte +++ b/plugins/attachment-resources/src/components/AttachmentsPresenter.svelte @@ -26,12 +26,14 @@ {#if value && value > 0} +
{}} class="sm-tool-icon ml-1 mr-1" > diff --git a/plugins/chunter-resources/src/components/CommentsPresenter.svelte b/plugins/chunter-resources/src/components/CommentsPresenter.svelte index 92858eb3d9..8ff7d33817 100644 --- a/plugins/chunter-resources/src/components/CommentsPresenter.svelte +++ b/plugins/chunter-resources/src/components/CommentsPresenter.svelte @@ -26,12 +26,14 @@ {#if value && value > 0} +
{}} class="sm-tool-icon ml-1 mr-1" > diff --git a/plugins/recruit-assets/lang/en.json b/plugins/recruit-assets/lang/en.json index b305619c4e..29c5ad8edf 100644 --- a/plugins/recruit-assets/lang/en.json +++ b/plugins/recruit-assets/lang/en.json @@ -99,7 +99,12 @@ "HasNoActiveApplicant": "No Active", "NoneApplications": "None", "RelatedIssues": "Related issues", - "VacancyList": "Vacancies" + "VacancyList": "Vacancies", + "MatchVacancy": "Match to vacancy", + "VacancyMatching": "Match Talents to vacancy", + "Score": "Score", + "Match": "Match", + "PerformMatch": "Match" }, "status": { "TalentRequired": "Please select talent", diff --git a/plugins/recruit-assets/lang/ru.json b/plugins/recruit-assets/lang/ru.json index b0e808ef8b..e15a6f3a28 100644 --- a/plugins/recruit-assets/lang/ru.json +++ b/plugins/recruit-assets/lang/ru.json @@ -101,7 +101,12 @@ "HasNoActiveApplicant": "Не активные", "NoneApplications": "Отсутствуют", "RelatedIssues": "Связанные задачи", - "VacancyList": "Вакансии" + "VacancyList": "Вакансии", + "MatchVacancy": "Проверить на вакансию", + "VacancyMatching": "Подбор кандидатов на вакансию", + "Score": "Оценка", + "Match": "Совпадение", + "PerformMatch": "Сопоставить" }, "status": { "TalentRequired": "Пожалуйста выберите таланта", diff --git a/plugins/recruit-resources/src/components/CreateApplication.svelte b/plugins/recruit-resources/src/components/CreateApplication.svelte index 5b0fc81191..05a687c621 100644 --- a/plugins/recruit-resources/src/components/CreateApplication.svelte +++ b/plugins/recruit-resources/src/components/CreateApplication.svelte @@ -13,10 +13,23 @@ // limitations under the License. --> @@ -306,6 +335,7 @@ _class={recruit.class.Vacancy} spaceQuery={{ archived: false }} spaceOptions={orgOptions} + readonly={preserveVacancy} label={recruit.string.Vacancy} create={{ component: recruit.component.CreateVacancy, @@ -324,6 +354,27 @@
+ + {#key doc._id} + dispatch('changeContent')} + on:attach={(ev) => { + if (ev.detail.action === 'saved') { + doc.attachments = ev.detail.value + } + }} + /> + {/key} {#key doc} + import contact from '@hcengineering/contact' + import core, { Doc, DocIndexState, FindOptions, Ref } from '@hcengineering/core' + import presentation, { + Card, + createQuery, + getClient, + IndexedDocumentPreview, + MessageViewer, + SpaceSelect + } from '@hcengineering/presentation' + import { Applicant, ApplicantMatch, Candidate, Vacancy } from '@hcengineering/recruit' + import { Button, IconActivity, IconAdd, Label, resizeObserver, showPopup, tooltip } from '@hcengineering/ui' + import Scroller from '@hcengineering/ui/src/components/Scroller.svelte' + import { MarkupPreviewPopup, ObjectPresenter } from '@hcengineering/view-resources' + import { cosinesim } from '@hcengineering/view-resources/src/utils' + import { createEventDispatcher } from 'svelte' + import recruit from '../plugin' + import CreateApplication from './CreateApplication.svelte' + import VacancyCard from './VacancyCard.svelte' + import VacancyOrgPresenter from './VacancyOrgPresenter.svelte' + + export let objects: Candidate[] | Candidate + + $: _objects = Array.isArray(objects) ? objects : [objects] + + const orgOptions: FindOptions = { + lookup: { + company: contact.class.Organization + } + } + let _space: Ref | undefined + let vacancy: Vacancy | undefined + + const vacancyQuery = createQuery() + $: vacancyQuery.query(recruit.class.Vacancy, { _id: _space }, (res) => { + ;[vacancy] = res + }) + + const indexDataQuery = createQuery() + let state: Map, DocIndexState> = new Map() + $: indexDataQuery.query( + core.class.DocIndexState, + { + _id: { + $in: [_space as unknown as Ref, ..._objects.map((it) => it._id as unknown as Ref)] + } + }, + (res) => { + state = new Map(res.map((it) => [it._id, it] ?? [])) + } + ) + $: vacancyState = state.get(_space as unknown as Ref) + + const matchQuery = createQuery() + let matches: Map, ApplicantMatch> = new Map() + + $: matchQuery.query( + recruit.class.ApplicantMatch, + { + attachedTo: { $in: [..._objects.map((it) => it._id)] }, + space: _space + }, + (res) => { + matches = new Map(res.map((it) => [it.attachedTo, it] ?? [])) + } + ) + + const applicationQuery = createQuery() + let applications: Map, Applicant> = new Map() + + $: applicationQuery.query( + recruit.class.Applicant, + { + attachedTo: { $in: [..._objects.map((it) => it._id)] }, + space: _space + }, + (res) => { + applications = new Map(res.map((it) => [it.attachedTo, it] ?? [])) + } + ) + + function getEmbedding (doc: DocIndexState): number[] | undefined { + for (const [k, v] of Object.entries(doc.attributes)) { + if (k.startsWith('openai_embedding_') && doc.attributes[k + '_use'] === true) { + return v + } + } + } + $: vacancyEmbedding = vacancyState && getEmbedding(vacancyState) + const dispatch = createEventDispatcher() + + const client = getClient() + const matching = new Set() + + async function requestMatch (doc: Candidate, docState: DocIndexState): Promise { + try { + matching.add(doc._id) + if (_space === undefined) { + return + } + const oldMatch = matches.get(doc._id) + if (oldMatch) { + await client.remove(oldMatch) + } + await client.addCollection(recruit.class.ApplicantMatch, _space, doc._id, doc._class, 'vacancyMatch', { + complete: false, + vacancy: vacancyState?.fullSummary ?? '', + summary: docState.fullSummary ?? '', + response: '' + }) + } finally { + matching.delete(doc._id) + } + } + async function createApplication (doc: Candidate, match?: ApplicantMatch): Promise { + showPopup( + CreateApplication, + { + space: _space, + candidate: doc._id, + preserveCandidate: true, + preserveVacancy: true, + comment: match?.response ?? '' + }, + 'top' + ) + } + async function showSummary (doc: Candidate): Promise { + showPopup(IndexedDocumentPreview, { objectId: doc._id }, 'top') + } + + + {}} + canSave={true} +> + +
{ + dispatch('changeContent') + }} + > +
+
+ { + _space = evt.detail + }} + component={VacancyOrgPresenter} + componentProps={{ inline: true }} + > + + + + +
+
+ {#if vacancy} + +
+ {#if vacancy.description} + {vacancy.description} + {/if} + {#if vacancyState?.fullSummary} + ')} /> + {/if} +
+
+ {/if} +
+
+ + + + + + + + + + + + + + {#each _objects as doc} + {@const docState = state.get(doc._id)} + {@const docEmbedding = docState && getEmbedding(docState)} + {@const match = matches.get(doc._id)} + {@const appl = applications.get(doc._id)} + + + + + + + {/each} + +
#
+
+ + {#if appl} +
+ +
+ {/if} +
+
+ {#if docEmbedding && vacancyEmbedding} + {Math.round(cosinesim(docEmbedding, vacancyEmbedding) * 100)} + {/if} + + {#if match?.complete} +
+ +
+ {/if} +
+ {#if docState} +
+
+
+
+
diff --git a/plugins/recruit-resources/src/components/VacancyCard.svelte b/plugins/recruit-resources/src/components/VacancyCard.svelte index 6b29e46f41..6a36299919 100644 --- a/plugins/recruit-resources/src/components/VacancyCard.svelte +++ b/plugins/recruit-resources/src/components/VacancyCard.svelte @@ -79,6 +79,7 @@
{/if} {#if vacancy} +
- {#if inline} -
- - - {vacancy.name} - -
- {:else} - {vacancy.name} - {/if} +
+ {#if inline} +
+ + + {vacancy.name} + +
+ {:else} + {vacancy.name} + {/if} +
{#if company} {company.name} {/if} {#if !inline || vacancy.description} -
{vacancy.description ?? ''}
+
{vacancy.description ?? ''}
{/if}