@jskit-ai/assistant 0.1.40 → 0.1.42
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 +91 -0
- package/package.descriptor.mjs +193 -117
- package/package.json +2 -2
- package/src/server/buildTemplateContext.js +7 -104
- package/src/server/pageSupport.js +114 -0
- package/src/server/subcommands/page.js +104 -0
- package/src/server/subcommands/settingsPage.js +108 -0
- package/src/server/subcommands/support.js +108 -0
- package/src/server/support.js +78 -0
- package/test/buildTemplateContext.test.js +20 -27
- package/test/packageDescriptor.test.js +41 -26
- package/test/subcommands.test.js +285 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @jskit-ai/assistant
|
|
2
|
+
|
|
3
|
+
Install assistant runtime/config for one surface, then scaffold assistant pages at explicit page files.
|
|
4
|
+
|
|
5
|
+
## Mental Model
|
|
6
|
+
|
|
7
|
+
`@jskit-ai/assistant` is the generator package.
|
|
8
|
+
|
|
9
|
+
- `setup` installs the assistant runtime/config/env wiring.
|
|
10
|
+
- `page <target-file>` creates the assistant runtime page anywhere relative to `src/pages/...`.
|
|
11
|
+
- `settings-page <target-file>` creates the assistant settings page anywhere relative to `src/pages/...`.
|
|
12
|
+
|
|
13
|
+
There is no separate server command and no separate client command.
|
|
14
|
+
|
|
15
|
+
The layering is:
|
|
16
|
+
|
|
17
|
+
- `@jskit-ai/assistant`: generator package with `setup`, `page`, and `settings-page`
|
|
18
|
+
- `@jskit-ai/assistant-runtime`: runtime package installed by `setup`
|
|
19
|
+
- `@jskit-ai/assistant-core`: lower-level provider/client library pulled in by `assistant-runtime`
|
|
20
|
+
|
|
21
|
+
So the normal flow is:
|
|
22
|
+
|
|
23
|
+
- run `setup`
|
|
24
|
+
- optionally create `page`
|
|
25
|
+
- optionally create `settings-page`
|
|
26
|
+
|
|
27
|
+
Page placement follows the same file-driven model as `@jskit-ai/ui-generator`.
|
|
28
|
+
|
|
29
|
+
- The surface comes from where the page file lives.
|
|
30
|
+
- The route URL comes from that file path.
|
|
31
|
+
- The default page-link placement comes from the nearest real parent subpages host when there is one.
|
|
32
|
+
|
|
33
|
+
Only one assistant can be installed per target surface. That rule is enforced by `setup`, which writes the per-surface config entries.
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx jskit generate @jskit-ai/assistant setup \
|
|
39
|
+
--surface admin \
|
|
40
|
+
--settings-surface console \
|
|
41
|
+
--config-scope global \
|
|
42
|
+
--ai-provider openai \
|
|
43
|
+
--ai-api-key "$OPENAI_API_KEY" \
|
|
44
|
+
--ai-base-url "" \
|
|
45
|
+
--ai-timeout-ms 120000
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`setup` does not create pages.
|
|
49
|
+
|
|
50
|
+
It only registers:
|
|
51
|
+
|
|
52
|
+
- `config.assistantSurfaces.<surface>`
|
|
53
|
+
- `config.assistantServer.<surface>`
|
|
54
|
+
- prefixed AI env defaults in `.env`
|
|
55
|
+
- the `@jskit-ai/assistant-runtime` dependency chain, including `@jskit-ai/assistant-core`
|
|
56
|
+
|
|
57
|
+
## Runtime Page
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx jskit generate @jskit-ai/assistant page \
|
|
61
|
+
admin/ops/copilot/index.vue
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The page surface id comes from the target file path.
|
|
65
|
+
|
|
66
|
+
Optional page-link overrides:
|
|
67
|
+
|
|
68
|
+
- `--name`
|
|
69
|
+
- `--link-placement`
|
|
70
|
+
- `--link-component-token`
|
|
71
|
+
- `--link-to`
|
|
72
|
+
|
|
73
|
+
## Settings Page
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx jskit generate @jskit-ai/assistant settings-page \
|
|
77
|
+
admin/settings/index/assistant/index.vue \
|
|
78
|
+
--surface admin
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For `settings-page`, the page location and the assistant target surface are separate concerns:
|
|
82
|
+
|
|
83
|
+
- `<target-file>` decides where the settings page lives and how its page link is placed
|
|
84
|
+
- `--surface` decides which assistant surface the settings page edits
|
|
85
|
+
|
|
86
|
+
The same optional page-link overrides are supported:
|
|
87
|
+
|
|
88
|
+
- `--name`
|
|
89
|
+
- `--link-placement`
|
|
90
|
+
- `--link-component-token`
|
|
91
|
+
- `--link-to`
|
package/package.descriptor.mjs
CHANGED
|
@@ -1,63 +1,74 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/assistant",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.42",
|
|
5
5
|
kind: "generator",
|
|
6
|
-
description: "
|
|
6
|
+
description: "Install assistant runtime/config for one surface and scaffold assistant pages at explicit target files.",
|
|
7
7
|
options: {
|
|
8
8
|
surface: {
|
|
9
9
|
required: true,
|
|
10
10
|
inputType: "text",
|
|
11
|
+
validationType: "enabled-surface-id",
|
|
11
12
|
defaultFromConfig: "surfaceDefaultId",
|
|
12
13
|
promptLabel: "Assistant surface",
|
|
13
|
-
promptHint: "
|
|
14
|
+
promptHint: "Assistant runtime surface id. Used by setup, or selected assistant surface for settings-page.",
|
|
15
|
+
helpHint: "For setup, this is the runtime surface receiving assistant wiring. For settings-page, this is the assistant runtime configured by that page."
|
|
14
16
|
},
|
|
15
17
|
"settings-surface": {
|
|
16
18
|
required: true,
|
|
17
19
|
inputType: "text",
|
|
20
|
+
validationType: "enabled-surface-id",
|
|
18
21
|
defaultValue: "",
|
|
19
|
-
promptLabel: "
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
"settings-route-path": {
|
|
23
|
-
required: false,
|
|
24
|
-
inputType: "text",
|
|
25
|
-
defaultValue: "assistant",
|
|
26
|
-
promptLabel: "Settings route path",
|
|
27
|
-
promptHint: "Route segment to use for the assistant settings section page."
|
|
22
|
+
promptLabel: "Which enabled surface should host the assistant settings UI?",
|
|
23
|
+
helpHint: "Surface that hosts the settings UI for the selected assistant runtime."
|
|
28
24
|
},
|
|
29
25
|
"config-scope": {
|
|
30
26
|
required: true,
|
|
31
27
|
inputType: "text",
|
|
32
28
|
defaultValue: "global",
|
|
33
29
|
promptLabel: "Config scope",
|
|
34
|
-
promptHint: "global | workspace. Workspace scope requires both
|
|
30
|
+
promptHint: "global | workspace. Workspace scope requires both setup surfaces to requireWorkspace=true."
|
|
35
31
|
},
|
|
36
|
-
|
|
32
|
+
name: {
|
|
37
33
|
required: false,
|
|
38
34
|
inputType: "text",
|
|
39
35
|
defaultValue: "",
|
|
40
|
-
promptLabel: "
|
|
41
|
-
promptHint: "Optional
|
|
36
|
+
promptLabel: "Page label",
|
|
37
|
+
promptHint: "Optional page link label override for page and settings-page.",
|
|
38
|
+
helpLabel: "Display label"
|
|
39
|
+
},
|
|
40
|
+
force: {
|
|
41
|
+
required: false,
|
|
42
|
+
inputType: "flag",
|
|
43
|
+
defaultValue: "",
|
|
44
|
+
promptLabel: "Force overwrite",
|
|
45
|
+
promptHint: "Overwrite the generated page file if it already exists."
|
|
42
46
|
},
|
|
43
|
-
"placement
|
|
47
|
+
"link-placement": {
|
|
44
48
|
required: false,
|
|
45
49
|
inputType: "text",
|
|
46
|
-
defaultValue: "
|
|
47
|
-
promptLabel: "
|
|
48
|
-
promptHint: "
|
|
50
|
+
defaultValue: "",
|
|
51
|
+
promptLabel: "Link placement",
|
|
52
|
+
promptHint: "Optional host:position target for the generated page link placement."
|
|
49
53
|
},
|
|
50
|
-
"
|
|
54
|
+
"link-component-token": {
|
|
51
55
|
required: false,
|
|
52
56
|
inputType: "text",
|
|
53
|
-
defaultValue: "
|
|
54
|
-
promptLabel: "
|
|
55
|
-
promptHint: "
|
|
57
|
+
defaultValue: "",
|
|
58
|
+
promptLabel: "Link component token",
|
|
59
|
+
promptHint: "Optional component token override for the generated page link placement."
|
|
56
60
|
},
|
|
57
|
-
"
|
|
61
|
+
"link-to": {
|
|
58
62
|
required: false,
|
|
59
63
|
inputType: "text",
|
|
60
64
|
defaultValue: "",
|
|
65
|
+
promptLabel: "Link to",
|
|
66
|
+
promptHint: "Optional explicit props.to value for the generated page link placement."
|
|
67
|
+
},
|
|
68
|
+
"ai-config-prefix": {
|
|
69
|
+
required: false,
|
|
70
|
+
inputType: "text",
|
|
71
|
+
defaultFromOptionTemplate: "${option:surface|snake|upper}_ASSISTANT",
|
|
61
72
|
promptLabel: "AI config prefix",
|
|
62
73
|
promptHint: "Optional env/config prefix override. Defaults to <SURFACE>_ASSISTANT."
|
|
63
74
|
},
|
|
@@ -75,8 +86,7 @@ export default Object.freeze({
|
|
|
75
86
|
promptHint: "Leave empty to keep the assistant disabled until you add a key."
|
|
76
87
|
},
|
|
77
88
|
"ai-base-url": {
|
|
78
|
-
required:
|
|
79
|
-
allowEmpty: true,
|
|
89
|
+
required: false,
|
|
80
90
|
defaultValue: "",
|
|
81
91
|
promptLabel: "AI base URL",
|
|
82
92
|
promptHint: "Optional provider-compatible base URL override."
|
|
@@ -102,11 +112,131 @@ export default Object.freeze({
|
|
|
102
112
|
}
|
|
103
113
|
},
|
|
104
114
|
metadata: {
|
|
115
|
+
generatorPrimarySubcommand: "setup",
|
|
116
|
+
generatorSubcommands: {
|
|
117
|
+
setup: {
|
|
118
|
+
description: "Install assistant runtime/config for one target surface without creating pages.",
|
|
119
|
+
optionNames: [
|
|
120
|
+
"surface",
|
|
121
|
+
"settings-surface",
|
|
122
|
+
"config-scope",
|
|
123
|
+
"ai-config-prefix",
|
|
124
|
+
"ai-provider",
|
|
125
|
+
"ai-api-key",
|
|
126
|
+
"ai-base-url",
|
|
127
|
+
"ai-timeout-ms"
|
|
128
|
+
],
|
|
129
|
+
notes: [
|
|
130
|
+
"setup installs runtime/config only. It does not create assistant pages.",
|
|
131
|
+
"--surface selects the assistant runtime surface, and --settings-surface selects the surface that will host its settings UI."
|
|
132
|
+
],
|
|
133
|
+
examples: [
|
|
134
|
+
{
|
|
135
|
+
label: "Common usage",
|
|
136
|
+
lines: [
|
|
137
|
+
"npx jskit generate assistant setup \\",
|
|
138
|
+
" --surface admin \\",
|
|
139
|
+
" --settings-surface admin \\",
|
|
140
|
+
" --config-scope workspace \\",
|
|
141
|
+
" --ai-api-key \"$OPENAI_API_KEY\""
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: "More advanced usage",
|
|
146
|
+
lines: [
|
|
147
|
+
"npx jskit generate assistant setup \\",
|
|
148
|
+
" --surface console \\",
|
|
149
|
+
" --settings-surface console \\",
|
|
150
|
+
" --config-scope global \\",
|
|
151
|
+
" --ai-config-prefix CONSOLE_ASSISTANT \\",
|
|
152
|
+
" --ai-provider openai \\",
|
|
153
|
+
" --ai-api-key \"$OPENAI_API_KEY\" \\",
|
|
154
|
+
" --ai-base-url \"http://localhost:11434/v1\" \\",
|
|
155
|
+
" --ai-timeout-ms 60000"
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
page: {
|
|
161
|
+
entrypoint: "src/server/subcommands/page.js",
|
|
162
|
+
export: "runGeneratorSubcommand",
|
|
163
|
+
description: "Create an assistant runtime page at an explicit target file relative to src/pages/.",
|
|
164
|
+
positionalArgs: [
|
|
165
|
+
{
|
|
166
|
+
name: "target-file",
|
|
167
|
+
required: true,
|
|
168
|
+
descriptionKey: "page-target-file"
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
optionNames: ["name", "link-placement", "link-component-token", "link-to", "force"],
|
|
172
|
+
notes: [
|
|
173
|
+
"The target file decides where the page lives.",
|
|
174
|
+
"Page-link placement follows the same inference rules as ui-generator page.",
|
|
175
|
+
"If the target page file already exists, rerun with --force to overwrite it."
|
|
176
|
+
],
|
|
177
|
+
examples: [
|
|
178
|
+
{
|
|
179
|
+
label: "Common usage",
|
|
180
|
+
lines: [
|
|
181
|
+
"npx jskit generate assistant page \\",
|
|
182
|
+
" admin/assistant/index.vue"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
label: "More advanced usage",
|
|
187
|
+
lines: [
|
|
188
|
+
"npx jskit generate assistant page \\",
|
|
189
|
+
" admin/ops/copilot/index.vue \\",
|
|
190
|
+
" --name \"Copilot\" \\",
|
|
191
|
+
" --link-placement shell-layout:top-right"
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"settings-page": {
|
|
197
|
+
entrypoint: "src/server/subcommands/settingsPage.js",
|
|
198
|
+
export: "runGeneratorSubcommand",
|
|
199
|
+
description: "Create an assistant settings page at an explicit target file relative to src/pages/.",
|
|
200
|
+
positionalArgs: [
|
|
201
|
+
{
|
|
202
|
+
name: "target-file",
|
|
203
|
+
required: true,
|
|
204
|
+
descriptionKey: "page-target-file"
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
optionNames: ["surface", "name", "link-placement", "link-component-token", "link-to", "force"],
|
|
208
|
+
requiredOptionNames: ["surface"],
|
|
209
|
+
notes: [
|
|
210
|
+
"The target file decides where the settings page lives.",
|
|
211
|
+
"--surface selects the assistant runtime being configured. It does not place the file.",
|
|
212
|
+
"If the target page file already exists, rerun with --force to overwrite it."
|
|
213
|
+
],
|
|
214
|
+
examples: [
|
|
215
|
+
{
|
|
216
|
+
label: "Common usage",
|
|
217
|
+
lines: [
|
|
218
|
+
"npx jskit generate assistant settings-page \\",
|
|
219
|
+
" admin/settings/index/assistant/index.vue \\",
|
|
220
|
+
" --surface admin"
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: "More advanced usage",
|
|
225
|
+
lines: [
|
|
226
|
+
"npx jskit generate assistant settings-page \\",
|
|
227
|
+
" admin/settings/index/app-assistant/index.vue \\",
|
|
228
|
+
" --surface app \\",
|
|
229
|
+
" --name \"App Assistant\""
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
},
|
|
105
235
|
apiSummary: {
|
|
106
236
|
surfaces: [
|
|
107
237
|
{
|
|
108
238
|
subpath: "./server/buildTemplateContext",
|
|
109
|
-
summary: "Builds deterministic assistant
|
|
239
|
+
summary: "Builds deterministic assistant setup template context values from app surface metadata."
|
|
110
240
|
}
|
|
111
241
|
],
|
|
112
242
|
containerTokens: {
|
|
@@ -124,83 +254,8 @@ export default Object.freeze({
|
|
|
124
254
|
scripts: {}
|
|
125
255
|
},
|
|
126
256
|
procfile: {},
|
|
127
|
-
files: [
|
|
128
|
-
{
|
|
129
|
-
from: "templates/src/pages/assistant/index.vue",
|
|
130
|
-
toSurface: "${option:surface|lower}",
|
|
131
|
-
toSurfacePath: "assistant/index.vue",
|
|
132
|
-
reason: "Install generated assistant runtime page.",
|
|
133
|
-
category: "assistant",
|
|
134
|
-
id: "assistant-page-runtime",
|
|
135
|
-
templateContext: {
|
|
136
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
137
|
-
export: "buildTemplateContext"
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
from: "templates/src/pages/settings/assistant/index.vue",
|
|
142
|
-
toSurface: "${option:settings-surface|lower}",
|
|
143
|
-
toSurfacePath: "settings/${option:settings-route-path|path}/index.vue",
|
|
144
|
-
reason: "Install generated assistant settings section page.",
|
|
145
|
-
category: "assistant",
|
|
146
|
-
id: "assistant-page-settings-standard",
|
|
147
|
-
when: {
|
|
148
|
-
option: "settings-surface",
|
|
149
|
-
notEquals: "admin"
|
|
150
|
-
},
|
|
151
|
-
templateContext: {
|
|
152
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
153
|
-
export: "buildTemplateContext"
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
from: "templates/src/pages/settings/assistant/index.vue",
|
|
158
|
-
toSurface: "${option:settings-surface|lower}",
|
|
159
|
-
toSurfacePath: "workspace/settings/${option:settings-route-path|path}/index.vue",
|
|
160
|
-
reason: "Install generated assistant settings section page.",
|
|
161
|
-
category: "assistant",
|
|
162
|
-
id: "assistant-page-settings-admin",
|
|
163
|
-
when: {
|
|
164
|
-
option: "settings-surface",
|
|
165
|
-
equals: "admin"
|
|
166
|
-
},
|
|
167
|
-
templateContext: {
|
|
168
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
169
|
-
export: "buildTemplateContext"
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
],
|
|
257
|
+
files: [],
|
|
173
258
|
text: [
|
|
174
|
-
{
|
|
175
|
-
op: "append-text",
|
|
176
|
-
file: "src/placement.js",
|
|
177
|
-
position: "bottom",
|
|
178
|
-
skipIfContains: "assistant.generated.menu:${option:surface|lower}",
|
|
179
|
-
value:
|
|
180
|
-
"\n// assistant.generated.menu:${option:surface|lower}\naddPlacement({\n id: \"assistant.generated.menu.${option:surface|kebab}\",\n host: \"__ASSISTANT_MENU_PLACEMENT_HOST__\",\n position: \"__ASSISTANT_MENU_PLACEMENT_POSITION__\",\n surfaces: [\"${option:surface|lower}\"],\n order: 310,\n componentToken: \"__ASSISTANT_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__ASSISTANT_MENU_LABEL__\",\n surface: \"${option:surface|lower}\",\n workspaceSuffix: \"__ASSISTANT_MENU_WORKSPACE_SUFFIX__\",\n nonWorkspaceSuffix: \"__ASSISTANT_MENU_NON_WORKSPACE_SUFFIX__\"\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
181
|
-
reason: "Append generated assistant runtime menu placement into app-owned placement registry.",
|
|
182
|
-
category: "assistant",
|
|
183
|
-
id: "assistant-placement-menu",
|
|
184
|
-
templateContext: {
|
|
185
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
186
|
-
export: "buildTemplateContext"
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
op: "append-text",
|
|
191
|
-
file: "src/placement.js",
|
|
192
|
-
position: "bottom",
|
|
193
|
-
skipIfContains: "assistant.generated.settings.menu:${option:surface|lower}",
|
|
194
|
-
value:
|
|
195
|
-
"\n// assistant.generated.settings.menu:${option:surface|lower}\naddPlacement({\n id: \"assistant.generated.settings.menu.${option:surface|kebab}\",\n host: \"__ASSISTANT_SETTINGS_HOST__\",\n position: \"primary-menu\",\n surfaces: [\"${option:settings-surface|lower}\"],\n order: 250,\n componentToken: \"users.web.shell.surface-aware-menu-link-item\",\n props: {\n label: \"__ASSISTANT_SETTINGS_MENU_LABEL__\",\n surface: \"${option:settings-surface|lower}\",\n workspaceSuffix: \"__ASSISTANT_SETTINGS_MENU_WORKSPACE_SUFFIX__\",\n nonWorkspaceSuffix: \"__ASSISTANT_SETTINGS_MENU_NON_WORKSPACE_SUFFIX__\"\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
196
|
-
reason: "Append generated assistant settings section menu placement into app-owned settings placements.",
|
|
197
|
-
category: "assistant",
|
|
198
|
-
id: "assistant-settings-menu-placement",
|
|
199
|
-
templateContext: {
|
|
200
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
201
|
-
export: "buildTemplateContext"
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
259
|
{
|
|
205
260
|
op: "append-text",
|
|
206
261
|
file: "config/public.js",
|
|
@@ -208,7 +263,7 @@ export default Object.freeze({
|
|
|
208
263
|
skipIfContains: "config.assistantSurfaces.${option:surface|lower} = {",
|
|
209
264
|
value:
|
|
210
265
|
"\nconfig.assistantSurfaces.${option:surface|lower} = {\n settingsSurfaceId: \"__ASSISTANT_SETTINGS_SURFACE_ID__\",\n configScope: \"__ASSISTANT_CONFIG_SCOPE__\"\n};\n",
|
|
211
|
-
reason: "Register the
|
|
266
|
+
reason: "Register the assistant runtime surface in public app config.",
|
|
212
267
|
category: "assistant",
|
|
213
268
|
id: "assistant-public-surface-config",
|
|
214
269
|
templateContext: {
|
|
@@ -223,7 +278,7 @@ export default Object.freeze({
|
|
|
223
278
|
skipIfContains: "config.assistantServer.${option:surface|lower} = {",
|
|
224
279
|
value:
|
|
225
280
|
"\nconfig.assistantServer.${option:surface|lower} = {\n aiConfigPrefix: \"__ASSISTANT_AI_CONFIG_PREFIX__\"\n};\n",
|
|
226
|
-
reason: "Register
|
|
281
|
+
reason: "Register assistant server config for the selected runtime surface.",
|
|
227
282
|
category: "assistant",
|
|
228
283
|
id: "assistant-server-surface-config",
|
|
229
284
|
templateContext: {
|
|
@@ -232,19 +287,40 @@ export default Object.freeze({
|
|
|
232
287
|
}
|
|
233
288
|
},
|
|
234
289
|
{
|
|
235
|
-
op: "append-text",
|
|
236
290
|
file: ".env",
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
value:
|
|
240
|
-
|
|
241
|
-
reason: "Append assistant AI env defaults for the generated surface prefix.",
|
|
291
|
+
op: "upsert-env",
|
|
292
|
+
key: "${option:ai-config-prefix}_AI_PROVIDER",
|
|
293
|
+
value: "${option:ai-provider}",
|
|
294
|
+
reason: "Configure the assistant AI provider for the selected surface.",
|
|
242
295
|
category: "runtime-config",
|
|
243
|
-
id: "assistant-ai-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
296
|
+
id: "assistant-ai-provider"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
file: ".env",
|
|
300
|
+
op: "upsert-env",
|
|
301
|
+
key: "${option:ai-config-prefix}_AI_API_KEY",
|
|
302
|
+
value: "${option:ai-api-key}",
|
|
303
|
+
reason: "Configure the assistant AI API key for the selected surface.",
|
|
304
|
+
category: "runtime-config",
|
|
305
|
+
id: "assistant-ai-api-key"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
file: ".env",
|
|
309
|
+
op: "upsert-env",
|
|
310
|
+
key: "${option:ai-config-prefix}_AI_BASE_URL",
|
|
311
|
+
value: "${option:ai-base-url}",
|
|
312
|
+
reason: "Configure the optional assistant AI base URL override for the selected surface.",
|
|
313
|
+
category: "runtime-config",
|
|
314
|
+
id: "assistant-ai-base-url"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
file: ".env",
|
|
318
|
+
op: "upsert-env",
|
|
319
|
+
key: "${option:ai-config-prefix}_AI_TIMEOUT_MS",
|
|
320
|
+
value: "${option:ai-timeout-ms}",
|
|
321
|
+
reason: "Configure the assistant AI timeout for the selected surface.",
|
|
322
|
+
category: "runtime-config",
|
|
323
|
+
id: "assistant-ai-timeout-ms"
|
|
248
324
|
}
|
|
249
325
|
]
|
|
250
326
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@jskit-ai/kernel": "0.1.
|
|
12
|
+
"@jskit-ai/kernel": "0.1.33"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,97 +1,16 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { pathToFileURL } from "node:url";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const DEFAULT_MENU_COMPONENT_TOKEN = "users.web.shell.surface-aware-menu-link-item";
|
|
12
|
-
|
|
13
|
-
function normalizeConfigScope(value = "") {
|
|
14
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
15
|
-
if (normalized === "global" || normalized === "workspace") {
|
|
16
|
-
return normalized;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
throw new Error('assistant generator option "config-scope" must be "global" or "workspace".');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function loadAppConfig(appRoot = "") {
|
|
23
|
-
const publicConfigUrl = pathToFileURL(path.join(appRoot, "config", "public.js")).href;
|
|
24
|
-
return loadAppConfigFromModuleUrl({
|
|
25
|
-
moduleUrl: publicConfigUrl
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function resolveSurfaceDefinition(appConfig = {}, surfaceId = "", optionName = "surface") {
|
|
30
|
-
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
31
|
-
if (!normalizedSurfaceId) {
|
|
32
|
-
throw new Error(`assistant generator requires --${optionName}.`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const sourceDefinitions = appConfig && typeof appConfig.surfaceDefinitions === "object" && !Array.isArray(appConfig.surfaceDefinitions)
|
|
36
|
-
? appConfig.surfaceDefinitions
|
|
37
|
-
: {};
|
|
38
|
-
const rawDefinition = sourceDefinitions[normalizedSurfaceId];
|
|
39
|
-
if (!rawDefinition || typeof rawDefinition !== "object" || Array.isArray(rawDefinition)) {
|
|
40
|
-
throw new Error(`assistant generator surface "${normalizedSurfaceId}" is not defined in config/public.js.`);
|
|
41
|
-
}
|
|
42
|
-
if (rawDefinition.enabled === false) {
|
|
43
|
-
throw new Error(`assistant generator surface "${normalizedSurfaceId}" is disabled in config/public.js.`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return Object.freeze({
|
|
47
|
-
id: normalizedSurfaceId,
|
|
48
|
-
requiresWorkspace: rawDefinition.requiresWorkspace === true,
|
|
49
|
-
accessPolicyId: normalizeText(rawDefinition.accessPolicyId).toLowerCase()
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function assertAssistantSurfaceIsAvailable(appConfig = {}, surfaceId = "") {
|
|
54
|
-
const assistantSurfaces =
|
|
55
|
-
appConfig && typeof appConfig.assistantSurfaces === "object" && !Array.isArray(appConfig.assistantSurfaces)
|
|
56
|
-
? appConfig.assistantSurfaces
|
|
57
|
-
: {};
|
|
58
|
-
if (assistantSurfaces[surfaceId]) {
|
|
59
|
-
throw new Error(`assistant generator surface "${surfaceId}" already has an assistant configured in config/public.js.`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function resolveAiConfigPrefix(surfaceId = "", explicitPrefix = "") {
|
|
64
|
-
const normalizedExplicitPrefix = normalizeText(explicitPrefix);
|
|
65
|
-
if (normalizedExplicitPrefix) {
|
|
66
|
-
return normalizedExplicitPrefix;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const surfacePrefix = toSnakeCase(surfaceId).toUpperCase();
|
|
70
|
-
return surfacePrefix ? `${surfacePrefix}_ASSISTANT` : "ASSISTANT";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function normalizeSettingsRoutePath(value = "") {
|
|
74
|
-
return normalizeText(value).toLowerCase().replace(/^\/+|\/+$/g, "") || "assistant";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function resolveSettingsRouteBase(surfaceId = "") {
|
|
78
|
-
return normalizeSurfaceId(surfaceId) === "admin" ? "/workspace/settings" : "/settings";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function resolveMenuPlacementTarget(appRoot = "", options = {}) {
|
|
82
|
-
return resolveShellOutletPlacementTargetFromApp({
|
|
83
|
-
appRoot,
|
|
84
|
-
context: "assistant-generator",
|
|
85
|
-
placement: normalizeText(options?.placement)
|
|
86
|
-
});
|
|
87
|
-
}
|
|
2
|
+
assertAssistantSurfaceIsAvailable,
|
|
3
|
+
loadAppConfig,
|
|
4
|
+
normalizeConfigScope,
|
|
5
|
+
resolveAiConfigPrefix,
|
|
6
|
+
resolveSurfaceDefinition
|
|
7
|
+
} from "./support.js";
|
|
88
8
|
|
|
89
9
|
async function buildTemplateContext({ appRoot, options } = {}) {
|
|
90
10
|
const appConfig = await loadAppConfig(appRoot);
|
|
91
11
|
const runtimeSurface = resolveSurfaceDefinition(appConfig, options?.surface, "surface");
|
|
92
12
|
const settingsSurface = resolveSurfaceDefinition(appConfig, options?.["settings-surface"], "settings-surface");
|
|
93
13
|
const configScope = normalizeConfigScope(options?.["config-scope"]);
|
|
94
|
-
const settingsRoutePath = normalizeSettingsRoutePath(options?.["settings-route-path"]);
|
|
95
14
|
|
|
96
15
|
assertAssistantSurfaceIsAvailable(appConfig, runtimeSurface.id);
|
|
97
16
|
|
|
@@ -106,26 +25,10 @@ async function buildTemplateContext({ appRoot, options } = {}) {
|
|
|
106
25
|
);
|
|
107
26
|
}
|
|
108
27
|
|
|
109
|
-
const placementTarget = await resolveMenuPlacementTarget(appRoot, options);
|
|
110
|
-
const menuComponentToken = normalizeText(options?.["placement-component-token"]) || DEFAULT_MENU_COMPONENT_TOKEN;
|
|
111
|
-
const settingsRouteBase = resolveSettingsRouteBase(settingsSurface.id);
|
|
112
|
-
const settingsMenuSuffix = `${settingsRouteBase}/${settingsRoutePath}`;
|
|
113
|
-
|
|
114
28
|
return Object.freeze({
|
|
115
|
-
"__ASSISTANT_SURFACE_ID__": runtimeSurface.id,
|
|
116
29
|
"__ASSISTANT_SETTINGS_SURFACE_ID__": settingsSurface.id,
|
|
117
30
|
"__ASSISTANT_CONFIG_SCOPE__": configScope,
|
|
118
|
-
"
|
|
119
|
-
"__ASSISTANT_AI_CONFIG_PREFIX__": resolveAiConfigPrefix(runtimeSurface.id, options?.["ai-config-prefix"]),
|
|
120
|
-
"__ASSISTANT_MENU_PLACEMENT_HOST__": placementTarget.host,
|
|
121
|
-
"__ASSISTANT_MENU_PLACEMENT_POSITION__": placementTarget.position,
|
|
122
|
-
"__ASSISTANT_MENU_COMPONENT_TOKEN__": menuComponentToken,
|
|
123
|
-
"__ASSISTANT_MENU_LABEL__": normalizeText(options?.["menu-label"]) || "Assistant",
|
|
124
|
-
"__ASSISTANT_MENU_WORKSPACE_SUFFIX__": "/assistant",
|
|
125
|
-
"__ASSISTANT_MENU_NON_WORKSPACE_SUFFIX__": "/assistant",
|
|
126
|
-
"__ASSISTANT_SETTINGS_MENU_LABEL__": normalizeText(options?.["menu-label"]) || "Assistant",
|
|
127
|
-
"__ASSISTANT_SETTINGS_MENU_WORKSPACE_SUFFIX__": settingsMenuSuffix,
|
|
128
|
-
"__ASSISTANT_SETTINGS_MENU_NON_WORKSPACE_SUFFIX__": settingsMenuSuffix
|
|
31
|
+
"__ASSISTANT_AI_CONFIG_PREFIX__": resolveAiConfigPrefix(runtimeSurface.id, options?.["ai-config-prefix"])
|
|
129
32
|
});
|
|
130
33
|
}
|
|
131
34
|
|