@postxl/generators 1.12.2 → 1.13.0
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/dist/backend-ai/ai.generator.d.ts +18 -0
- package/dist/backend-ai/ai.generator.js +174 -0
- package/dist/backend-ai/ai.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js +264 -0
- package/dist/backend-ai/generators/ai-agent-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js +110 -0
- package/dist/backend-ai/generators/ai-cache-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-config.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-config.generator.js +27 -0
- package/dist/backend-ai/generators/ai-config.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-module.generator.d.ts +2 -0
- package/dist/backend-ai/generators/ai-module.generator.js +89 -0
- package/dist/backend-ai/generators/ai-module.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-route.generator.d.ts +1 -0
- package/dist/backend-ai/generators/ai-route.generator.js +29 -0
- package/dist/backend-ai/generators/ai-route.generator.js.map +1 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.d.ts +4 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js +222 -0
- package/dist/backend-ai/generators/ai-tools-service.generator.js.map +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.d.ts +1 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js +48 -0
- package/dist/backend-ai/generators/model-provider-interface.generator.js.map +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.d.ts +1 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js +128 -0
- package/dist/backend-ai/generators/openai-model-provider-service.generator.js.map +1 -0
- package/dist/backend-ai/index.d.ts +4 -0
- package/dist/backend-ai/index.js +40 -0
- package/dist/backend-ai/index.js.map +1 -0
- package/dist/backend-core/generators/main.generator.js +4 -3
- package/dist/backend-core/generators/main.generator.js.map +1 -1
- package/dist/backend-e2e/backend-e2e.generator.js +4 -4
- package/dist/backend-e2e/backend-e2e.generator.js.map +1 -1
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +1 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js +3 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +3 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +2 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js +7 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
- package/dist/backend-router-trpc/router-trpc.generator.js +2 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-seed/seed.generator.js +10 -1
- package/dist/backend-seed/seed.generator.js.map +1 -1
- package/dist/base/template/scripts/setup.sh +9 -4
- package/dist/base/template/sonar-project.properties +9 -1
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js +1 -0
- package/dist/devops/generators/bitbucket-pipelines-yml.generator.js.map +1 -1
- package/dist/devops/generators/e2e-yml.generator.js +35 -10
- package/dist/devops/generators/e2e-yml.generator.js.map +1 -1
- package/dist/devops/generators/jenkinsfile.generator.js +25 -1
- package/dist/devops/generators/jenkinsfile.generator.js.map +1 -1
- package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
- package/dist/frontend-actions/actions.generator.d.ts +9 -0
- package/dist/frontend-actions/actions.generator.js +111 -0
- package/dist/frontend-actions/actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js +52 -0
- package/dist/frontend-actions/generators/ai-action-text.utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js +230 -0
- package/dist/frontend-actions/generators/ai-assistant-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js +139 -0
- package/dist/frontend-actions/generators/ai-sidebar-content.generator.js.map +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js +98 -0
- package/dist/frontend-actions/generators/ai-sidepane.generator.js.map +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js +405 -0
- package/dist/frontend-actions/generators/base-global-actions.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js +87 -0
- package/dist/frontend-actions/generators/command-palette-action.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js +288 -0
- package/dist/frontend-actions/generators/command-palette-store.generator.js.map +1 -0
- package/dist/frontend-actions/generators/command-palette.generator.d.ts +5 -0
- package/dist/frontend-actions/generators/command-palette.generator.js +332 -0
- package/dist/frontend-actions/generators/command-palette.generator.js.map +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js +50 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js.map +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.d.ts +1 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js +37 -0
- package/dist/frontend-actions/generators/sidepanel-toggle.generator.js.map +1 -0
- package/dist/frontend-actions/index.d.ts +4 -0
- package/dist/frontend-actions/index.js +40 -0
- package/dist/frontend-actions/index.js.map +1 -0
- package/dist/frontend-admin/admin.generator.d.ts +3 -1
- package/dist/frontend-admin/admin.generator.js +8 -1
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-global-actions.generator.d.ts +4 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js +152 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -0
- package/dist/frontend-admin/generators/comment-sidebar.generator.js +5 -3
- package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/detail-sidebar.generator.js +40 -24
- package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/model-admin-page.generator.js +172 -11
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-admin/utils.d.ts +1 -0
- package/dist/frontend-admin/utils.js +1 -0
- package/dist/frontend-admin/utils.js.map +1 -1
- package/dist/frontend-core/generators/tsconfig.generator.js +1 -0
- package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
- package/dist/frontend-core/template/.env.example +3 -0
- package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +22 -4
- package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +1 -1
- package/dist/frontend-core/template/src/lib/color.ts +6 -3
- package/dist/frontend-core/template/src/lib/config.ts +3 -1
- package/dist/frontend-tables/generators/model-table.generator.js +13 -0
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/generators.js +4 -0
- package/dist/generators.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/types/template/ai.types.ts +34 -0
- package/dist/types/types.generator.js +1 -0
- package/dist/types/types.generator.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAiAssistantStore = generateAiAssistantStore;
|
|
4
|
+
function generateAiAssistantStore() {
|
|
5
|
+
return `import { useSyncExternalStore } from 'react'
|
|
6
|
+
import type { ReactNode } from 'react'
|
|
7
|
+
|
|
8
|
+
export type AiEventKind = 'user' | 'action' | 'assistant' | 'system'
|
|
9
|
+
export type AiEventStatus = 'running' | 'done' | 'error'
|
|
10
|
+
|
|
11
|
+
export type AiAssistantEvent = {
|
|
12
|
+
id: string
|
|
13
|
+
kind: AiEventKind
|
|
14
|
+
text: string
|
|
15
|
+
status?: AiEventStatus
|
|
16
|
+
createdAt: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type AiSidepaneTab = {
|
|
20
|
+
id: string
|
|
21
|
+
label: string
|
|
22
|
+
render: () => ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AiExecutionResult = {
|
|
26
|
+
summary?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type AiExecutor = (
|
|
30
|
+
task: string,
|
|
31
|
+
helpers: {
|
|
32
|
+
signal: AbortSignal
|
|
33
|
+
addEvent: (entry: Omit<AiAssistantEvent, 'id' | 'createdAt'>) => string
|
|
34
|
+
patchEvent: (id: string, patch: Partial<Pick<AiAssistantEvent, 'text' | 'status'>>) => void
|
|
35
|
+
requestUserInput: (question: string) => Promise<string>
|
|
36
|
+
},
|
|
37
|
+
) => Promise<AiExecutionResult>
|
|
38
|
+
|
|
39
|
+
type AiAssistantState = {
|
|
40
|
+
open: boolean
|
|
41
|
+
running: boolean
|
|
42
|
+
pendingQuestion: string | null
|
|
43
|
+
events: AiAssistantEvent[]
|
|
44
|
+
tabs: AiSidepaneTab[]
|
|
45
|
+
activeAdminTab: string
|
|
46
|
+
executor: AiExecutor | null
|
|
47
|
+
controller: AbortController | null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type AiAssistantActions = {
|
|
51
|
+
setOpen: (open: boolean) => void
|
|
52
|
+
clearEvents: () => void
|
|
53
|
+
cancel: () => void
|
|
54
|
+
submit: (task: string) => Promise<void>
|
|
55
|
+
setExecutor: (executor: AiExecutor | null) => void
|
|
56
|
+
registerTabs: (...tabs: AiSidepaneTab[]) => void
|
|
57
|
+
deregisterTabs: (...tabIds: string[]) => void
|
|
58
|
+
setActiveAdminTab: (tabId: string) => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type AiAssistantStore = AiAssistantState & { actions: AiAssistantActions }
|
|
62
|
+
|
|
63
|
+
const listeners = new Set<() => void>()
|
|
64
|
+
const subscribe = (listener: () => void) => {
|
|
65
|
+
listeners.add(listener)
|
|
66
|
+
return () => listeners.delete(listener)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const notify = () => {
|
|
70
|
+
for (const listener of listeners) {
|
|
71
|
+
listener()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const setStore = (update: Partial<AiAssistantState>) => {
|
|
76
|
+
store = { ...store, ...update }
|
|
77
|
+
notify()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const newId = () => globalThis.crypto?.randomUUID?.() ?? String(Date.now() + Math.random())
|
|
81
|
+
|
|
82
|
+
const addEvent = (entry: Omit<AiAssistantEvent, 'id' | 'createdAt'>): string => {
|
|
83
|
+
const id = newId()
|
|
84
|
+
const next: AiAssistantEvent = { id, createdAt: Date.now(), ...entry }
|
|
85
|
+
setStore({ events: [...store.events, next] })
|
|
86
|
+
return id
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const patchEvent = (id: string, patch: Partial<Pick<AiAssistantEvent, 'text' | 'status'>>) => {
|
|
90
|
+
setStore({
|
|
91
|
+
events: store.events.map((event) => (event.id === id ? { ...event, ...patch } : event)),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let pendingAnswerResolve: ((answer: string) => void) | null = null
|
|
96
|
+
let pendingAnswerReject: ((error: Error) => void) | null = null
|
|
97
|
+
let pendingSignalAbortUnsubscribe: (() => void) | null = null
|
|
98
|
+
|
|
99
|
+
const resetPendingInputRequest = () => {
|
|
100
|
+
pendingAnswerResolve = null
|
|
101
|
+
pendingAnswerReject = null
|
|
102
|
+
pendingSignalAbortUnsubscribe?.()
|
|
103
|
+
pendingSignalAbortUnsubscribe = null
|
|
104
|
+
if (store.pendingQuestion) {
|
|
105
|
+
setStore({ pendingQuestion: null })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const requestUserInput = (question: string, signal: AbortSignal): Promise<string> => {
|
|
110
|
+
resetPendingInputRequest()
|
|
111
|
+
setStore({ pendingQuestion: question })
|
|
112
|
+
|
|
113
|
+
return new Promise<string>((resolve, reject) => {
|
|
114
|
+
pendingAnswerResolve = (answer) => {
|
|
115
|
+
resetPendingInputRequest()
|
|
116
|
+
resolve(answer)
|
|
117
|
+
}
|
|
118
|
+
pendingAnswerReject = (error) => {
|
|
119
|
+
resetPendingInputRequest()
|
|
120
|
+
reject(error)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const onAbort = () => {
|
|
124
|
+
pendingAnswerReject?.(new Error('Execution cancelled'))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
128
|
+
pendingSignalAbortUnsubscribe = () => signal.removeEventListener('abort', onAbort)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let store: AiAssistantStore = {
|
|
133
|
+
open: false,
|
|
134
|
+
running: false,
|
|
135
|
+
pendingQuestion: null,
|
|
136
|
+
events: [],
|
|
137
|
+
tabs: [],
|
|
138
|
+
activeAdminTab: 'ai',
|
|
139
|
+
executor: null,
|
|
140
|
+
controller: null,
|
|
141
|
+
actions: {} as AiAssistantActions,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
store.actions = {
|
|
145
|
+
setOpen(open) {
|
|
146
|
+
setStore({ open })
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
clearEvents() {
|
|
150
|
+
setStore({ events: [] })
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
cancel() {
|
|
154
|
+
pendingAnswerReject?.(new Error('Execution cancelled'))
|
|
155
|
+
store.controller?.abort()
|
|
156
|
+
setStore({ running: false, controller: null, pendingQuestion: null })
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
async submit(task) {
|
|
160
|
+
const trimmed = task.trim()
|
|
161
|
+
if (!trimmed) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (store.running && store.pendingQuestion) {
|
|
166
|
+
addEvent({ kind: 'user', text: trimmed })
|
|
167
|
+
pendingAnswerResolve?.(trimmed)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (store.running || !store.executor) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const controller = new AbortController()
|
|
176
|
+
setStore({ running: true, open: true, controller })
|
|
177
|
+
|
|
178
|
+
addEvent({ kind: 'user', text: trimmed })
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const result = await store.executor(trimmed, {
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
addEvent,
|
|
184
|
+
patchEvent,
|
|
185
|
+
requestUserInput: (question) => requestUserInput(question, controller.signal),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (result.summary) {
|
|
189
|
+
addEvent({ kind: 'assistant', text: result.summary, status: 'done' })
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (controller.signal.aborted) {
|
|
193
|
+
addEvent({ kind: 'system', text: 'Execution cancelled.' })
|
|
194
|
+
} else {
|
|
195
|
+
addEvent({ kind: 'assistant', text: String(error), status: 'error' })
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
resetPendingInputRequest()
|
|
199
|
+
setStore({ running: false, controller: null, pendingQuestion: null })
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
setExecutor(executor) {
|
|
204
|
+
setStore({ executor })
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
registerTabs(...tabs) {
|
|
208
|
+
const incoming = new Set(tabs.map((tab) => tab.id))
|
|
209
|
+
const next = [...store.tabs.filter((tab) => !incoming.has(tab.id)), ...tabs]
|
|
210
|
+
setStore({ tabs: next })
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
deregisterTabs(...tabIds) {
|
|
214
|
+
const toRemove = new Set(tabIds)
|
|
215
|
+
setStore({ tabs: store.tabs.filter((tab) => !toRemove.has(tab.id)) })
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
setActiveAdminTab(tabId) {
|
|
219
|
+
setStore({ activeAdminTab: tabId })
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const useAiAssistantState = <T>(selector: (state: AiAssistantState) => T): T =>
|
|
224
|
+
useSyncExternalStore(subscribe, () => selector(store), () => selector(store))
|
|
225
|
+
|
|
226
|
+
export const useAiAssistantActions = (): AiAssistantActions =>
|
|
227
|
+
useSyncExternalStore(subscribe, () => store.actions, () => store.actions)
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=ai-assistant-store.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-assistant-store.generator.js","sourceRoot":"","sources":["../../../src/frontend-actions/generators/ai-assistant-store.generator.ts"],"names":[],"mappings":";;AAAA,4DAiOC;AAjOD,SAAgB,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+NR,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateAiSidebarContent(): string;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAiSidebarContent = generateAiSidebarContent;
|
|
4
|
+
function generateAiSidebarContent() {
|
|
5
|
+
return `import { BotIcon, SparklesIcon } from 'lucide-react'
|
|
6
|
+
import { useEffect, useRef, useState } from 'react'
|
|
7
|
+
|
|
8
|
+
import { Button, Textarea } from '@postxl/ui-components'
|
|
9
|
+
|
|
10
|
+
import { AiAssistantEvent, useAiAssistantActions, useAiAssistantState } from './ai-assistant-store'
|
|
11
|
+
|
|
12
|
+
const EventBubble = ({ event }: { event: AiAssistantEvent }) => {
|
|
13
|
+
if (event.kind === 'user') {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex justify-end">
|
|
16
|
+
<div className="max-w-[85%] rounded-lg bg-primary px-3 py-2 text-sm text-primary-foreground">{event.text}</div>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (event.kind === 'action') {
|
|
22
|
+
let statusClass = 'border-muted bg-muted/30 text-foreground'
|
|
23
|
+
if (event.status === 'running') {
|
|
24
|
+
statusClass = 'border-blue-300 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-200'
|
|
25
|
+
} else if (event.status === 'error') {
|
|
26
|
+
statusClass = 'border-red-300 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex justify-start">
|
|
31
|
+
<div className={\`max-w-[95%] rounded-lg border px-3 py-2 text-xs \${statusClass}\`}>
|
|
32
|
+
<div className="flex items-center gap-2">
|
|
33
|
+
<SparklesIcon className={event.status === 'running' ? 'size-3 animate-pulse' : 'size-3'} />
|
|
34
|
+
<span className="font-medium">Agent Action</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="mt-1">{event.text}</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (event.kind === 'assistant') {
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex justify-start">
|
|
45
|
+
<div className="max-w-[90%] rounded-lg border bg-card px-3 py-2 text-sm">
|
|
46
|
+
<div className="mb-1 flex items-center gap-2 text-xs text-muted-foreground">
|
|
47
|
+
<BotIcon className="size-3" />
|
|
48
|
+
Assistant
|
|
49
|
+
</div>
|
|
50
|
+
<div>{event.text}</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <div className="text-center text-xs text-muted-foreground">{event.text}</div>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const AiSidebarContent = () => {
|
|
60
|
+
const events = useAiAssistantState((state) => state.events)
|
|
61
|
+
const running = useAiAssistantState((state) => state.running)
|
|
62
|
+
const pendingQuestion = useAiAssistantState((state) => state.pendingQuestion)
|
|
63
|
+
const { cancel, clearEvents, submit } = useAiAssistantActions()
|
|
64
|
+
|
|
65
|
+
const [prompt, setPrompt] = useState('')
|
|
66
|
+
const scrollRef = useRef<HTMLDivElement | null>(null)
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const el = scrollRef.current
|
|
70
|
+
if (el) {
|
|
71
|
+
el.scrollTop = el.scrollHeight
|
|
72
|
+
}
|
|
73
|
+
}, [events, running])
|
|
74
|
+
|
|
75
|
+
const handleSubmit = () => {
|
|
76
|
+
const trimmed = prompt.trim()
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setPrompt('')
|
|
82
|
+
void submit(trimmed)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
87
|
+
<div className="flex items-center justify-between border-b px-3 py-2">
|
|
88
|
+
<p className="text-xs font-medium text-muted-foreground">AI Assistant</p>
|
|
89
|
+
<Button variant="ghost" size="sm" onClick={clearEvents} disabled={running}>
|
|
90
|
+
Clear
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div ref={scrollRef} className="min-h-0 flex-1 space-y-2 overflow-y-auto px-3 py-3">
|
|
95
|
+
{events.length === 0 && (
|
|
96
|
+
<p className="text-sm text-muted-foreground">Ask for navigation, filtering, or data insights.</p>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{events.map((event) => (
|
|
100
|
+
<EventBubble key={event.id} event={event} />
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="space-y-2 border-t p-3">
|
|
105
|
+
<Textarea
|
|
106
|
+
value={prompt}
|
|
107
|
+
onChange={(event) => setPrompt(event.target.value)}
|
|
108
|
+
placeholder={pendingQuestion ?? 'Ask the assistant...'}
|
|
109
|
+
className="min-h-[90px] resize-none"
|
|
110
|
+
onKeyDown={(event) => {
|
|
111
|
+
if (event.key === 'Enter' && !event.metaKey && !event.ctrlKey) {
|
|
112
|
+
event.preventDefault()
|
|
113
|
+
handleSubmit()
|
|
114
|
+
}
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<div className="flex items-center justify-between">
|
|
119
|
+
<p className="text-xs text-muted-foreground">
|
|
120
|
+
{pendingQuestion ? 'Assistant is waiting for your answer' : 'Press Enter to send, Ctrl/Cmd + Enter for a new line'}
|
|
121
|
+
</p>
|
|
122
|
+
<div className="flex gap-2">
|
|
123
|
+
{running && (
|
|
124
|
+
<Button variant="outline" size="sm" onClick={cancel}>
|
|
125
|
+
Stop
|
|
126
|
+
</Button>
|
|
127
|
+
)}
|
|
128
|
+
<Button size="sm" onClick={handleSubmit} disabled={(!pendingQuestion && running) || !prompt.trim()}>
|
|
129
|
+
{pendingQuestion ? 'Reply' : 'Send'}
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=ai-sidebar-content.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sidebar-content.generator.js","sourceRoot":"","sources":["../../../src/frontend-actions/generators/ai-sidebar-content.generator.ts"],"names":[],"mappings":";;AAAA,4DAsIC;AAtID,SAAgB,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoIR,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateAiSidepane(): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAiSidepane = generateAiSidepane;
|
|
4
|
+
function generateAiSidepane() {
|
|
5
|
+
return `import { Component, type ErrorInfo, type PropsWithChildren, useMemo } from 'react'
|
|
6
|
+
import { useLocation } from '@tanstack/react-router'
|
|
7
|
+
|
|
8
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle, Tabs, TabsContent, TabsList, TabsTrigger } from '@postxl/ui-components'
|
|
9
|
+
|
|
10
|
+
import { APP_CONFIG } from '@lib/config'
|
|
11
|
+
import { useAiAssistantActions, useAiAssistantState } from './ai-assistant-store'
|
|
12
|
+
import { AiSidebarContent } from './ai-sidebar-content'
|
|
13
|
+
|
|
14
|
+
type AiSidepaneErrorBoundaryState = {
|
|
15
|
+
hasError: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class AiSidepaneErrorBoundary extends Component<PropsWithChildren, AiSidepaneErrorBoundaryState> {
|
|
19
|
+
public constructor(props: PropsWithChildren) {
|
|
20
|
+
super(props)
|
|
21
|
+
this.state = { hasError: false }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static getDerivedStateFromError(): AiSidepaneErrorBoundaryState {
|
|
25
|
+
return { hasError: true }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public componentDidCatch(error: unknown, info: ErrorInfo) {
|
|
29
|
+
console.error('AiSidepane rendering failed:', error, info)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public render() {
|
|
33
|
+
if (this.state.hasError) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.props.children
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const AiSidepane = () => {
|
|
42
|
+
return (
|
|
43
|
+
<AiSidepaneErrorBoundary>
|
|
44
|
+
<AiSidepaneInner />
|
|
45
|
+
</AiSidepaneErrorBoundary>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const AiSidepaneInner = () => {
|
|
50
|
+
const pathname = useLocation({ select: (location) => location.pathname })
|
|
51
|
+
const open = useAiAssistantState((state) => state.open)
|
|
52
|
+
const tabs = useAiAssistantState((state) => state.tabs)
|
|
53
|
+
const { setOpen } = useAiAssistantActions()
|
|
54
|
+
|
|
55
|
+
const isAdminRoute = pathname.startsWith('/admin')
|
|
56
|
+
const isAdminOverviewRoute = pathname === '/admin' || pathname === '/admin/'
|
|
57
|
+
|
|
58
|
+
const allTabs = useMemo(
|
|
59
|
+
() => [
|
|
60
|
+
{ id: 'ai', label: 'AI', render: () => <AiSidebarContent /> },
|
|
61
|
+
...tabs,
|
|
62
|
+
],
|
|
63
|
+
[tabs],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if (!APP_CONFIG.enableAI || (isAdminRoute && !isAdminOverviewRoute)) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
72
|
+
<SheetContent side="right" className="w-full p-0 sm:max-w-xl">
|
|
73
|
+
<SheetHeader className="border-b px-4 py-3">
|
|
74
|
+
<SheetTitle>Assistant</SheetTitle>
|
|
75
|
+
</SheetHeader>
|
|
76
|
+
|
|
77
|
+
<Tabs defaultValue={allTabs[0]?.id} className="flex h-[calc(100%-56px)] min-h-0 flex-col">
|
|
78
|
+
<TabsList variant="protocol" className="mx-2 mt-2 justify-start">
|
|
79
|
+
{allTabs.map((tab) => (
|
|
80
|
+
<TabsTrigger key={tab.id} value={tab.id} variant="protocol" className="cursor-pointer px-3 h-8">
|
|
81
|
+
{tab.label}
|
|
82
|
+
</TabsTrigger>
|
|
83
|
+
))}
|
|
84
|
+
</TabsList>
|
|
85
|
+
|
|
86
|
+
{allTabs.map((tab) => (
|
|
87
|
+
<TabsContent key={tab.id} value={tab.id} className="mt-0 h-full min-h-0 flex-1">
|
|
88
|
+
{tab.render()}
|
|
89
|
+
</TabsContent>
|
|
90
|
+
))}
|
|
91
|
+
</Tabs>
|
|
92
|
+
</SheetContent>
|
|
93
|
+
</Sheet>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=ai-sidepane.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sidepane.generator.js","sourceRoot":"","sources":["../../../src/frontend-actions/generators/ai-sidepane.generator.ts"],"names":[],"mappings":";;AAAA,gDA6FC;AA7FD,SAAgB,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2FR,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateBaseGlobalActions(): string;
|