Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: chat ai #524

Merged
merged 16 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Export diagram as svg.
* Error management on monaco editor and error footer.
* Generate diagrams from AI proxy.
* Add chat to discuss with AI.

### Changed

Expand All @@ -49,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Replaced old authentication (Parse) by the new Leto-Modelize-Api (Java/Spring).
* Use display name of plugin.
* Use new logo of plugin.
* Improve drawer to modify Component details, permit to resize it.

### Fixed

Expand All @@ -62,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fix bug on creating folder with same name as root folder, see [this issue](https://github.com/ditrit/leto-modelizer/issues/393).
* Fix bug on missing file name in config file when using githubator-plugin, see [this issue](https://github.com/ditrit/leto-modelizer/issues/465).
* Fix bug on deleting last component of model file, the file is deleted and model is no longer displayed in the list, see [this issue](https://github.com/ditrit/leto-modelizer/issues/474).
* Fix some unwanted scroll appear.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/RoundTrips/DrawEditor.feature
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Feature: Test roundtrip of the application: draw editor
When I force click on '.id_3.component .menu-button'
And I click on '[data-cy="delete-button"]'
And I wait 1 second
Then I expect '[data-cy="object-details-panel"]' to be hidden
Then I expect '[data-cy="object-details-panel"]' not exists
And I expect '.id_3.component' not exists

# TODO: Uncomment when following bug will be fixed (https://github.com/ditrit/@ditrit/terrator-plugin/issues/98)
Expand Down
5,314 changes: 105 additions & 5,209 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
"plugin:uninstall": "node node_modules/@ditrit/leto-modelizer-plugin-cli/src/index.js uninstall"
},
"dependencies": {
"@ditrit/leto-modelizer-plugin-core": "=0.28.0",
"@quasar/extras": "=1.16.9",
"axios": "=1.6.2",
"browserfs": "=1.4.3",
"core-js": "=3.34.0",
"crypto-js": "^4.2.0",
"isomorphic-git": "=1.25.1",
"@ditrit/leto-modelizer-plugin-core": "=0.28.0",
"monaco-editor": "=0.45.0",
"monaco-editor-webpack-plugin": "=7.1.0",
"pako": "^2.1.0",
"pinia": "=2.1.7",
"quasar": "=2.13.0",
"rxjs": "=7.8.1",
Expand Down
45 changes: 44 additions & 1 deletion src/boot/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,43 @@ api.interceptors.response.use(
},
);

/**
* Make a filter request (GET) to the specified URL using the provided API.
* @param {object} apiInstance - The API object used to make the request.
* @param {string} url - The URL to make the filter request to.
* @returns {Promise<object>} The response data of the filter request.
*/
async function makeFilterRequest(apiInstance, url) {
return apiInstance.get(url).then((data) => {
if (data.totalPages > 0 && data.pageable.pageNumber + 1 > data.totalPages) {
// TODO : for now we don't have any entity attribute name that ends with 'page'.
// Be careful if this case arises, you'll need to adjust the regex.
const newUrl = url.replace(/page=\d+/, `page=${data.totalPages - 1}`);
return makeFilterRequest(apiInstance, newUrl);
}

return data;
});
}

/**
* Transform filters into query parameters string.
* @param {object} filters - API Filters.
* @returns {string} Formatted string to put in url, works even if there are no filters.
*/
function prepareQueryParameters(filters = {}) {
const queryParameters = Object.keys(filters)
.map((key) => ({ key, value: `${filters[key]}` }))
.filter(({ value }) => value?.length > 0)
.map(({ key, value }) => `${key}=${encodeURIComponent(value)}`);

if (queryParameters.length === 0) {
return '';
}

return `?${queryParameters.join('&')}`;
}

/**
* Asynchronously prepares a request by ensuring the availability of a valid CSRF token.
*
Expand All @@ -65,4 +102,10 @@ async function prepareApiRequest() {
return api;
}

export { api, prepareApiRequest, templateLibraryApiClient };
export {
api,
prepareApiRequest,
templateLibraryApiClient,
prepareQueryParameters,
makeFilterRequest,
};
96 changes: 96 additions & 0 deletions src/components/dialog/DeleteAIConversationDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<template>
<default-dialog
dialog-key="DeleteAIConversation"
data-cy="delete-ai-conversation-dialog"
>
<template #title>
<q-icon
color="primary"
name="fa-solid fa-comments"
/>
{{ $t(`page.modelizer.drawer.aichat.dialog.title`) }}
</template>
<template #default>
<div
class="q-pb-lg"
v-html="$t('page.modelizer.drawer.aichat.dialog.description')"
/>
<div class="flex row items-center justify-center">
<q-btn
icon="fa-solid fa-trash"
:label="$t('actions.default.delete')"
type="submit"
:loading="submitting"
color="negative"
data-cy="submit-button"
@click="submit"
>
<template #loading>
<q-spinner-dots />
</template>
</q-btn>
</div>
</template>
</default-dialog>
</template>

<script setup>
import {
ref,
onMounted,
onUnmounted,
} from 'vue';
import DialogEvent from 'src/composables/events/DialogEvent';
import DefaultDialog from 'components/dialog/DefaultDialog.vue';
import { deleteConversation } from 'src/services/AIService';

const projectName = ref(null);
const diagramPath = ref(null);
const pluginName = ref(null);
const submitting = ref(false);
let dialogEventSubscription;

/**
* Set AI conversation id on valid event.
* @param {object} event - Dialog event.
* @param {string} event.key - Event key.
* @param {string} event.projectName - Project name.
* @param {string} event.diagramPath - Diagram path.
* @param {string} event.pluginName - Plugin name.
*/
function setAIConversationId(event) {
if (event.key === 'DeleteAIConversation') {
projectName.value = event.projectName;
diagramPath.value = event.diagramPath;
pluginName.value = event.pluginName;
}
}

/**
* Delete AI conversation and close dialog.
* @returns {Promise<void>} Promise with nothing on success otherwise an error.
*/
function submit() {
submitting.value = true;

return deleteConversation(
projectName.value,
diagramPath.value,
pluginName.value,
).finally(() => {
submitting.value = false;
DialogEvent.next({
key: 'DeleteAIConversation',
type: 'close',
});
});
}

onMounted(() => {
dialogEventSubscription = DialogEvent.subscribe(setAIConversationId);
});

onUnmounted(() => {
dialogEventSubscription.unsubscribe();
});
</script>
Loading
Loading