Skip to content

Commit 369c8e4

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents 7be6207 + 6b15492 commit 369c8e4

3 files changed

Lines changed: 73 additions & 22 deletions

File tree

adminforth/documentation/docs/tutorial/08-Plugins/01-agent.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function allowedForSuperAdmins({ adminUser }: { adminUser: AdminUser }): P
4343
}
4444

4545
export default {
46-
dataSource: 'sqlite',
46+
dataSource: 'maindb',
4747
table: 'sessions',
4848
resourceId: 'sessions',
4949
label: 'Sessions',
@@ -102,7 +102,7 @@ async function allowedForSuperAdmins({ adminUser }: { adminUser: AdminUser }): P
102102
}
103103

104104
export default {
105-
dataSource: 'sqlite',
105+
dataSource: 'maindb',
106106
table: 'turns',
107107
resourceId: 'turns',
108108
label: 'Turns',
@@ -479,11 +479,17 @@ The plugin ships with bundled skills from `plugins/adminforth-agent/custom/skill
479479
| `mutate_data` | `mutate_data` | Create, update, delete, or run actions on records. Before any mutation it must show the exact target row and ask the user for confirmation. |
480480

481481

482-
## Persistent checkpointer
482+
## Persistent agent memory
483483

484-
If you do not configure `checkpointResource`, the plugin falls back to an in-memory `MemorySaver`. This is fine for local testing, but checkpoints are lost on process restart.
484+
The plugin stores visible chat history in the `sessions` and `turns` resources. This is what users see in the chat sidebar and in old conversations.
485485

486-
If you want persistent LangGraph checkpoints between requests, add a dedicated resource and pass it via `checkpointResource`.
486+
The agent also needs its own internal conversation state to continue an existing chat. If you do not configure `checkpointResource`, that internal state is kept only in the Node.js process memory. This is fine for local testing and for new chats, but it is not durable:
487+
488+
- after a server restart, users can still see old chat messages because `sessions` and `turns` are stored in your database
489+
- after a server restart, users can continue an old chat, but the agent will treat it like a new conversation because its internal state was lost
490+
- new chats will work normally again until the next restart
491+
492+
For production, configure `checkpointResource` so the agent can safely continue old chats after restarts and deployments.
487493

488494
You can use your own table and field names. The plugin does not require a specific schema name, only a mapping for these logical fields:
489495

@@ -508,7 +514,7 @@ import { AdminForthDataTypes } from 'adminforth';
508514
import type { AdminForthResourceInput } from 'adminforth';
509515

510516
export default {
511-
dataSource: 'sqlite',
517+
dataSource: 'maindb',
512518
table: 'checkpoints',
513519
resourceId: 'checkpoints',
514520
label: 'Checkpoints',

adminforth/spa/src/components/AcceptModal.vue

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,70 @@
66
backgroundCustomClasses="z-[998]"
77
modalCustomClasses="z-[999]"
88
>
9-
<div class="relative p-4 w-full max-w-md max-h-full" >
10-
<button type="button" @click="modalStore.togleModal()" class="absolute top-3 end-2.5 text-lightAcceptModalCloseIcon bg-transparent hover:bg-lightAcceptModalCloseIconHoverBackground hover:text-lightAcceptModalCloseIconHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkAcceptModalCloseIcon dark:hover:bg-darkAcceptModalCloseIconHoverBackground dark:hover:text-darkAcceptModalCloseIconHover" >
11-
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
9+
<div class="relative p-6 sm:p-8 w-[440px] bg-white rounded-2xl shadow-xl dark:bg-gray-800" >
10+
11+
<button type="button" @click="modalStore.togleModal()" class="absolute top-4 right-4 text-gray-400 hover:text-gray-900 transition-colors dark:hover:text-white" >
12+
<svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
1213
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
1314
</svg>
14-
<span class="sr-only">{{ $t('Close modal') }}</span>
1515
</button>
16-
<div class="p-4 md:p-5 text-center">
17-
<svg class="mx-auto mb-4 text-lightAcceptModalWarningIcon w-12 h-12 dark:text-darkAcceptModalWarningIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
18-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
19-
</svg>
20-
<h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText">{{ modalStore?.modalContent?.content }}</h3>
21-
<h3 class=" afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText" v-html="modalStore?.modalContent?.contentHTML"></h3>
2216

23-
<button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="afcl-confirmation-accept-button text-lightAcceptModalConfirmButtonText bg-lightAcceptModalConfirmButtonBackground hover:bg-lightAcceptModalConfirmButtonBackgroundHover focus:ring-4 focus:outline-none focus:ring-lightAcceptModalConfirmButtonFocus font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center dark:text-darkAcceptModalConfirmButtonText dark:bg-darkAcceptModalConfirmButtonBackground dark:hover:bg-darkAcceptModalConfirmButtonBackgroundHover dark:focus:ring-darkAcceptModalConfirmButtonFocus">
24-
{{ modalStore?.modalContent?.acceptText }}
25-
</button>
26-
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="afcl-confirmation-cancel-button py-2.5 px-5 ms-3 text-sm font-medium text-lightAcceptModalCancelButtonText focus:outline-none bg-lightAcceptModalCancelButtonBackground rounded-lg border border-lightAcceptModalCancelButtonBorder hover:bg-lightAcceptModalCancelButtonBackgroundHover hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-lightAcceptModalCancelButtonFocus dark:focus:ring-darkAcceptModalCancelButtonFocus dark:bg-darkAcceptModalCancelButtonBackground dark:text-darkAcceptModalCancelButtonText dark:border-darkAcceptModalCancelButtonBorder dark:hover:text-darkAcceptModalCancelButtonTextHover dark:hover:bg-darkAcceptModalCancelButtonBackgroundHover">{{ modalStore?.modalContent?.cancelText }}</button>
17+
<div class="text-center">
18+
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-red-50 mb-5 relative dark:bg-red-900/20">
19+
<IconClipboardDocumentSolid class="w-10 h-10 text-red-500" />
20+
<div class="absolute bottom-1 right-1 bg-red-500 rounded-full w-[18px] h-[18px] flex items-center justify-center border-2 border-white dark:border-gray-800">
21+
<span class="text-white text-[10px] font-medium">!</span>
22+
</div>
23+
</div>
24+
25+
<h3 class="mb-4 text-2xl font-bold text-gray-900 dark:text-white">
26+
There are unsaved changes.
27+
</h3>
28+
29+
<div class="mb-2 text-[15px] text-gray-600 dark:text-gray-300">
30+
<div v-if="modalStore?.modalContent?.contentHTML" class="font-medium" v-html="modalStore.modalContent.contentHTML"></div>
31+
<p v-else-if="modalStore?.modalContent?.content" class="font-medium">{{ modalStore.modalContent.content }}</p>
32+
</div>
33+
34+
<hr class="border-t border-gray-100 dark:border-gray-700 mb-6">
35+
36+
<div class="flex justify-center gap-4 w-full">
37+
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="flex-1 py-2.5 px-4 text-sm font-medium text-gray-700 bg-white rounded-lg border border-gray-200 hover:bg-gray-50 focus:ring-4 focus:ring-gray-100 transition-all dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700">
38+
Stay and continue
39+
</button>
40+
41+
<button
42+
@click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}"
43+
type="button"
44+
class="flex-1 flex items-center justify-center py-2.5 px-4 text-sm font-medium transition-all focus:outline-none
45+
text-white bg-red-600 hover:bg-red-700
46+
dark:bg-red-500 dark:hover:bg-red-600
47+
border border-red-700 dark:border-red-600
48+
rounded-lg shadow-sm focus:z-10 focus:ring-4
49+
focus:ring-red-100 dark:focus:ring-red-900 gap-1">
50+
Leave without saving
51+
</button>
52+
</div>
53+
54+
<div class="flex items-center justify-center mt-5 text-xs text-gray-400 gap-1.5 font-medium">
55+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
56+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
57+
</svg>
58+
<span>Your changes will not be saved</span>
59+
</div>
60+
2761
</div>
2862
</div>
2963
</Modal>
3064
</Teleport>
3165
</template>
3266

3367
<script setup lang="ts">
34-
import { watch, onMounted, nextTick, ref } from 'vue';
68+
import { watch, ref } from 'vue';
3569
import { useModalStore } from '@/stores/modal';
3670
import { Modal } from '@/afcl';
71+
import { IconClipboardDocumentSolid } from '@iconify-prerendered/vue-heroicons';
72+
3773
3874
const modalRef = ref();
3975
const modalStore = useModalStore();

adminforth/spa/src/utils/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,16 @@ export function generateMessageHtmlForRecordChange(changedFields: Record<string,
707707
}).join('');
708708

709709
const listHtml = items ? `<ul class="mt-2 list-disc list-inside">${items}</ul>` : '';
710-
const messageHtml = `<div>${escapeHtml(t('There are unsaved changes. Are you sure you want to leave this page?'))}${listHtml}</div>`;
710+
const messageHtml = `
711+
<div class="flex flex-col gap-y-2 text-center">
712+
<div class="text-gray-500 dark:text-gray-400">
713+
${listHtml}
714+
</div>
715+
<p class="font-medium text-gray-900 dark:text-white mt-4">
716+
${escapeHtml(t('Are you sure you want to leave this page?'))}
717+
</p>
718+
</div>
719+
`;
711720
return messageHtml;
712721
}
713722

0 commit comments

Comments
 (0)