Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Composer/packages/client/src/recoilModel/Recognizers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const Recognizer = React.memo((props: { projectId: string }) => {
useEffect(() => {
let recognizers: RecognizerFile[] = [];
dialogs
.filter((dialog) => !dialog.isFormDialog)
.filter((dialog) => isCrossTrainedRecognizerSet(dialog) || isLuisRecognizer(dialog))
.forEach((dialog) => {
const filtedLus = luFiles.filter((item) => item.id.startsWith(dialog.id));
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/client/src/recoilModel/atoms/botState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const emptyDialog: DialogInfo = {
triggers: [],
intentTriggers: [],
skills: [],
isFormDialog: false,
};
type dialogStateParams = { projectId: string; dialogId: string };
export const dialogState = atomFamily<DialogInfo, dialogStateParams>({
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const stubDialog = (): DialogInfo => ({
triggers: [],
intentTriggers: [],
skills: [],
isFormDialog: false,
});

export function useShell(source: EventSource, projectId: string): Shell {
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/lib/indexers/src/dialogIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ function parse(id: string, content: any) {
const luFile = typeof content.recognizer === 'string' ? content.recognizer : '';
const qnaFile = typeof content.recognizer === 'string' ? content.recognizer : '';
const lgFile = typeof content.generator === 'string' ? content.generator : '';
const isFormDialog = has(content, '$Generator'); // mark as form generated dialog;
Comment thread
zhixzhan marked this conversation as resolved.
Outdated
const diagnostics: Diagnostic[] = [];
return {
id,
Expand All @@ -194,6 +195,7 @@ function parse(id: string, content: any) {
triggers: extractTriggers(content),
intentTriggers: extractIntentTriggers(content),
skills: extractReferredSkills(content),
isFormDialog,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ describe('Bot structure file path', () => {
const targetPath = defaultFilePath(botName, defaultLocale, 'greeting.myimport1.source.qna');
expect(targetPath).toEqual('dialogs/greeting/knowledge-base/source/myimport1.source.qna');
});

// customized endpoint
it('should get child dialog.source.qna file path', async () => {
const targetPath = defaultFilePath(botName, defaultLocale, 'myimport1.qna', 'dialogs/Welcome');
expect(targetPath).toEqual('dialogs/Welcome/knowledge-base/en-us/myimport1.en-us.qna');
});
});

describe('Parse file name', () => {
Expand Down
30 changes: 25 additions & 5 deletions Composer/packages/server/src/models/bot/botProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { promisify } from 'util';
import fs from 'fs';

import has from 'lodash/has';
import axios from 'axios';
import { autofixReferInDialog } from '@bfc/indexers';
import {
Expand Down Expand Up @@ -436,7 +437,14 @@ export class BotProject implements IBotProject {
this._validateFileContent(name, content);
const botName = this.name;
const defaultLocale = this.settings?.defaultLanguage || defaultLanguage;
const relativePath = defaultFilePath(botName, defaultLocale, filename, this.rootDialogId);

// find created file belong to which dialog, all resources should be writed to <dialog>/
const dialogId = name.split('.')[0];
const dialogFile = this.files.get(`${dialogId}.dialog`);
const endpoint = dialogFile ? Path.dirname(dialogFile.relativePath) : '';
const rootDialogId = this.rootDialogId;

const relativePath = defaultFilePath(botName, defaultLocale, filename, { endpoint, rootDialogId });
const file = this.files.get(filename);
if (file) {
throw new Error(`${filename} dialog already exist`);
Expand Down Expand Up @@ -535,10 +543,10 @@ export class BotProject implements IBotProject {

public async generateDialog(name: string, templateDirs?: string[]) {
const defaultLocale = this.settings?.defaultLanguage || defaultLanguage;
const relativePath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.FormDialogSchema}`);
const relativePath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.FormDialogSchema}`, {});
const schemaPath = Path.resolve(this.dir, relativePath);

const dialogPath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.Dialog}`);
const dialogPath = defaultFilePath(this.name, defaultLocale, `${name}${FileExtensions.Dialog}`, {});
const outDir = Path.dirname(Path.resolve(this.dir, dialogPath));

const feedback = (type: FeedbackType, message: string): void => {
Expand Down Expand Up @@ -585,7 +593,7 @@ export class BotProject implements IBotProject {

public async deleteFormDialog(dialogId: string) {
const defaultLocale = this.settings?.defaultLanguage || defaultLanguage;
const dialogPath = defaultFilePath(this.name, defaultLocale, `${dialogId}${FileExtensions.Dialog}`);
const dialogPath = defaultFilePath(this.name, defaultLocale, `${dialogId}${FileExtensions.Dialog}`, {});
const dirToDelete = Path.dirname(Path.resolve(this.dir, dialogPath));

// I check that the path is longer 3 to avoid deleting a drive and all its contents.
Expand Down Expand Up @@ -776,6 +784,9 @@ export class BotProject implements IBotProject {
fileList.set(file.name, file);
});

// flowing migration scripts depends on files;
this.files = new Map<string, FileInfo>([...fileList]);

const migrationFilesList = await Promise.all([
this._createQnAFilesForOldBot(fileList),
this._createBotProjectFileForOldBots(fileList),
Expand All @@ -794,7 +805,16 @@ export class BotProject implements IBotProject {
const qnaFiles: FileInfo[] = [];
files.forEach((file) => {
if (file.name.endsWith('.dialog')) {
dialogFiles.push(file);
try {
// filter form dialog generated file.
const dialogJson = JSON.parse(file.content);
const isFormDialog = has(dialogJson, '$Generator');
Comment thread
zhixzhan marked this conversation as resolved.
Outdated
if (!isFormDialog) {
dialogFiles.push(file);
}
} catch (_e) {
// ignore
}
}
if (file.name.endsWith('.qna')) {
qnaFiles.push(file);
Expand Down
45 changes: 40 additions & 5 deletions Composer/packages/server/src/models/bot/botStructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,27 @@ export const defaultFilePath = (
botName: string,
defaultLocale: string,
filename: string,
rootDialogId = ''
options: {
endpoint?: string; // <endpoint>/<file-path>
rootDialogId?: string;
}
): string => {
const BOTNAME = botName.toLowerCase();
const CommonFileId = 'common';

const { endpoint = '', rootDialogId = '' } = options;
const { fileId, locale, fileType, dialogId } = parseFileName(filename, defaultLocale);
const LOCALE = locale;

// now recognizer extension is .lu.dialog or .qna.dialog
if (isRecognizer(filename)) {
const dialogId = filename.split('.')[0];
const isRoot = filename.startsWith(botName) || (rootDialogId && filename.startsWith(rootDialogId));
if (isRoot) {
if (endpoint) {
return templateInterpolate(Path.join(endpoint, BotStructureTemplate.recognizer), {
RECOGNIZERNAME: filename,
});
} else if (isRoot) {
return templateInterpolate(BotStructureTemplate.recognizer, {
RECOGNIZERNAME: filename,
});
Expand Down Expand Up @@ -137,19 +145,46 @@ export const defaultFilePath = (
const isRootFile = BOTNAME === DIALOGNAME.toLowerCase();

if (fileType === FileExtensions.SourceQnA) {
if (endpoint) {
return templateInterpolate(Path.join(endpoint, BotStructureTemplate.sourceQnA), {
FILENAME: fileId,
DIALOGNAME,
});
}
const TemplatePath =
isRootFile || !dialogId ? BotStructureTemplate.sourceQnA : BotStructureTemplate.dialogs.sourceQnA;
return templateInterpolate(TemplatePath, {
FILENAME: fileId,
DIALOGNAME,
});
}

return templateInterpolate(BotStructureTemplate.skillManifests, {
MANIFESTFILENAME: filename,
let TemplatePath = '';

if (endpoint) {
switch (fileType) {
case FileExtensions.Dialog:
TemplatePath = BotStructureTemplate.entry;
break;
case FileExtensions.Lg:
TemplatePath = BotStructureTemplate.lg;
break;
case FileExtensions.Lu:
TemplatePath = BotStructureTemplate.lu;
break;
case FileExtensions.Qna:
TemplatePath = BotStructureTemplate.qna;
break;
case FileExtensions.DialogSchema:
TemplatePath = BotStructureTemplate.dialogSchema;
}
return templateInterpolate(Path.join(endpoint, TemplatePath), {
BOTNAME: endpoint ? fileId : BOTNAME,
DIALOGNAME,
LOCALE,
});
}

let TemplatePath = '';
if (fileType === FileExtensions.Dialog) {
TemplatePath = isRootFile ? BotStructureTemplate.entry : BotStructureTemplate.dialogs.entry;
} else if (fileType === FileExtensions.Lg) {
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/types/src/indexers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type DialogInfo = {
triggers: ITrigger[];
intentTriggers: IIntentTrigger[];
skills: string[];
isFormDialog: boolean;
};

export type LgTemplateJsonPath = {
Expand Down