@antelopejs/dms-ai 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/frontend-vue/app/plugins/ai.client.ts +17 -5
- package/frontend-vue/app/runtime/header-action.ts +1 -1
- package/frontend-vue/app/runtime/session-gate.ts +22 -0
- package/frontend-vue/dms.frontend.ts +1 -1
- package/frontend-vue/tests/session-gate.test.ts +39 -0
- package/frontend-vue/tests/sidecar-guest.test.ts +53 -19
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DmsAppContext,
|
|
3
|
+
defineDmsPlugin,
|
|
4
|
+
useDmsRouter,
|
|
5
|
+
} from '#dms/frontend-module'
|
|
2
6
|
import {
|
|
3
7
|
SIDECAR_HOST_NAME,
|
|
4
8
|
SIDECAR_INFO_PATH,
|
|
@@ -14,6 +18,7 @@ import { registerLauncherAction } from '../runtime/header-action'
|
|
|
14
18
|
import { createHostCommandDispatcher } from '../runtime/host-commands'
|
|
15
19
|
import { installNavigationCompleteEmitter } from '../runtime/navigation-complete'
|
|
16
20
|
import { injectOverlay } from '../runtime/overlay'
|
|
21
|
+
import { runWhenLoggedIn } from '../runtime/session-gate'
|
|
17
22
|
import { installToggleShortcut } from '../runtime/shortcuts'
|
|
18
23
|
import {
|
|
19
24
|
createSidecarStatusController,
|
|
@@ -30,10 +35,7 @@ function buildIframeUrl(port: number, token: string): string {
|
|
|
30
35
|
return `${SIDECAR_PROBE_HOST}:${port}/?theme=${mode}#token=${encodeURIComponent(token)}`
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
if (!import.meta.env.DEV) return
|
|
35
|
-
const { loggedIn } = useUserSession()
|
|
36
|
-
if (!loggedIn.value) return
|
|
38
|
+
async function startAssistant({ vueApp }: DmsAppContext): Promise<void> {
|
|
37
39
|
const { $authFetch } = useAuthFetch()
|
|
38
40
|
const controller = createSidecarStatusController(() =>
|
|
39
41
|
$authFetch(SIDECAR_INFO_PATH),
|
|
@@ -106,4 +108,14 @@ export default defineDmsPlugin(async ({ vueApp }) => {
|
|
|
106
108
|
}
|
|
107
109
|
vueApp.onUnmount(stop)
|
|
108
110
|
import.meta.hot?.dispose(stop)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default defineDmsPlugin((context) => {
|
|
114
|
+
if (!import.meta.env.DEV) return
|
|
115
|
+
const { loggedIn } = useUserSession()
|
|
116
|
+
const stopGate = runWhenLoggedIn(
|
|
117
|
+
() => loggedIn.value,
|
|
118
|
+
() => void context.runWithContext(() => startAssistant(context)),
|
|
119
|
+
)
|
|
120
|
+
context.vueApp.onUnmount(stopGate)
|
|
109
121
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
// Plugins run once, when the Vue app boots, on whatever page the browser landed
|
|
4
|
+
// on. Landing on the sign-in screen means there is no session yet, and signing
|
|
5
|
+
// in is a client-side navigation that never re-creates the app: a plugin that
|
|
6
|
+
// gave up there would stay silent until the next full page load. Wait for the
|
|
7
|
+
// session to appear instead, then run exactly once.
|
|
8
|
+
export function runWhenLoggedIn(
|
|
9
|
+
isLoggedIn: () => boolean,
|
|
10
|
+
start: () => void,
|
|
11
|
+
): () => void {
|
|
12
|
+
if (isLoggedIn()) {
|
|
13
|
+
start()
|
|
14
|
+
return () => undefined
|
|
15
|
+
}
|
|
16
|
+
const stop = watch(isLoggedIn, (loggedIn) => {
|
|
17
|
+
if (!loggedIn) return
|
|
18
|
+
stop()
|
|
19
|
+
start()
|
|
20
|
+
})
|
|
21
|
+
return stop
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { nextTick, ref } from 'vue'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { runWhenLoggedIn } from '../app/runtime/session-gate'
|
|
4
|
+
|
|
5
|
+
describe('session gate', () => {
|
|
6
|
+
it('starts immediately when the session is already hydrated', () => {
|
|
7
|
+
const start = vi.fn()
|
|
8
|
+
runWhenLoggedIn(() => true, start)
|
|
9
|
+
expect(start).toHaveBeenCalledTimes(1)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('starts once the session appears, and only once', async () => {
|
|
13
|
+
const loggedIn = ref(false)
|
|
14
|
+
const start = vi.fn()
|
|
15
|
+
runWhenLoggedIn(() => loggedIn.value, start)
|
|
16
|
+
expect(start).not.toHaveBeenCalled()
|
|
17
|
+
|
|
18
|
+
loggedIn.value = true
|
|
19
|
+
await nextTick()
|
|
20
|
+
expect(start).toHaveBeenCalledTimes(1)
|
|
21
|
+
|
|
22
|
+
loggedIn.value = false
|
|
23
|
+
await nextTick()
|
|
24
|
+
loggedIn.value = true
|
|
25
|
+
await nextTick()
|
|
26
|
+
expect(start).toHaveBeenCalledTimes(1)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('never starts once the gate is stopped', async () => {
|
|
30
|
+
const loggedIn = ref(false)
|
|
31
|
+
const start = vi.fn()
|
|
32
|
+
const stop = runWhenLoggedIn(() => loggedIn.value, start)
|
|
33
|
+
|
|
34
|
+
stop()
|
|
35
|
+
loggedIn.value = true
|
|
36
|
+
await nextTick()
|
|
37
|
+
expect(start).not.toHaveBeenCalled()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { resolve } from 'node:path'
|
|
3
3
|
import ts from 'typescript'
|
|
4
|
+
import { nextTick, ref, type Ref } from 'vue'
|
|
4
5
|
import { expect, it, vi } from 'vitest'
|
|
6
|
+
import { runWhenLoggedIn } from '../app/runtime/session-gate'
|
|
5
7
|
|
|
6
8
|
interface PluginExports {
|
|
7
|
-
default: (context: unknown) => Promise<void>
|
|
9
|
+
default: (context: unknown) => void | Promise<void>
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
interface PluginHarness {
|
|
13
|
+
useAuthFetch: ReturnType<typeof vi.fn>
|
|
14
|
+
run: () => void | Promise<void>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SESSION_GATE_MODULE = '../runtime/session-gate'
|
|
18
|
+
const SIDECAR_STATUS_MODULE = '../runtime/sidecar-status'
|
|
19
|
+
|
|
20
|
+
// A first probe reporting "no assistant here" stops the startup right after the
|
|
21
|
+
// authenticated fetch, which is all these tests need to observe.
|
|
22
|
+
const silentController = {
|
|
23
|
+
createSidecarStatusController: () => ({ init: async () => false }),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadPlugin(loggedIn: Ref<boolean>): PluginHarness {
|
|
11
27
|
const source = readFileSync(
|
|
12
28
|
resolve(__dirname, '../app/plugins/ai.client.ts'),
|
|
13
29
|
'utf8',
|
|
@@ -16,30 +32,48 @@ it('does not invoke authenticated fetching or inject the assistant for a guest',
|
|
|
16
32
|
source
|
|
17
33
|
.replace('import.meta.env.DEV', 'true')
|
|
18
34
|
.replace('import.meta.hot', 'undefined'),
|
|
19
|
-
{
|
|
20
|
-
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
|
21
|
-
},
|
|
35
|
+
{ compilerOptions: { module: ts.ModuleKind.CommonJS } },
|
|
22
36
|
)
|
|
23
37
|
const exports = {} as PluginExports
|
|
24
|
-
const useAuthFetch = vi.fn(() => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
const useAuthFetch = vi.fn(() => ({ $authFetch: vi.fn() }))
|
|
39
|
+
const requireModule = (name: string) => {
|
|
40
|
+
if (name === '#dms/frontend-module') {
|
|
41
|
+
return { defineDmsPlugin: (plugin: unknown) => plugin }
|
|
42
|
+
}
|
|
43
|
+
if (name === SESSION_GATE_MODULE) return { runWhenLoggedIn }
|
|
44
|
+
if (name === SIDECAR_STATUS_MODULE) return silentController
|
|
45
|
+
return {}
|
|
46
|
+
}
|
|
31
47
|
new Function(
|
|
32
48
|
'require',
|
|
33
49
|
'exports',
|
|
34
50
|
'useUserSession',
|
|
35
51
|
'useAuthFetch',
|
|
36
52
|
compiled.outputText,
|
|
37
|
-
)(
|
|
38
|
-
|
|
39
|
-
exports,
|
|
40
|
-
() => ({ loggedIn: { value: false } }),
|
|
53
|
+
)(requireModule, exports, () => ({ loggedIn }), useAuthFetch)
|
|
54
|
+
return {
|
|
41
55
|
useAuthFetch,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
run: () =>
|
|
57
|
+
exports.default({
|
|
58
|
+
vueApp: { onUnmount: vi.fn() },
|
|
59
|
+
runWithContext: (callback: () => unknown) => callback(),
|
|
60
|
+
}),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
it('does not invoke authenticated fetching or inject the assistant for a guest', async () => {
|
|
65
|
+
const plugin = loadPlugin(ref(false))
|
|
66
|
+
await plugin.run()
|
|
67
|
+
expect(plugin.useAuthFetch).not.toHaveBeenCalled()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('starts the assistant when a guest signs in without reloading the page', async () => {
|
|
71
|
+
const loggedIn = ref(false)
|
|
72
|
+
const plugin = loadPlugin(loggedIn)
|
|
73
|
+
await plugin.run()
|
|
74
|
+
expect(plugin.useAuthFetch).not.toHaveBeenCalled()
|
|
75
|
+
|
|
76
|
+
loggedIn.value = true
|
|
77
|
+
await nextTick()
|
|
78
|
+
expect(plugin.useAuthFetch).toHaveBeenCalledTimes(1)
|
|
45
79
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-ai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"lint:fix": "oxlint --disable-nested-config --fix --max-warnings=1000000 && oxfmt --disable-nested-config . && cd frontend-vue && pnpm lint:fix && cd ../sidecar && pnpm lint:fix",
|
|
39
39
|
"typecheck": "tsc --noEmit && cd frontend-vue && pnpm typecheck && cd ../sidecar && pnpm typecheck",
|
|
40
40
|
"test": "cd sidecar && pnpm test && cd ../frontend-vue && pnpm test",
|
|
41
|
+
"test:sidecar-smoke": "pnpm run build:sidecar && node --test tests/sidecar-build.test.cjs",
|
|
41
42
|
"test:frontend-registration": "pnpm run build && node --test tests/frontend-registration.test.cjs",
|
|
42
43
|
"test:release-script": "node --test tests/release-script.test.cjs",
|
|
43
44
|
"test:e2e": "cd sidecar && pnpm test:e2e",
|