@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.
Files changed (126) hide show
  1. package/dist/backend-ai/ai.generator.d.ts +18 -0
  2. package/dist/backend-ai/ai.generator.js +174 -0
  3. package/dist/backend-ai/ai.generator.js.map +1 -0
  4. package/dist/backend-ai/generators/ai-agent-service.generator.d.ts +4 -0
  5. package/dist/backend-ai/generators/ai-agent-service.generator.js +264 -0
  6. package/dist/backend-ai/generators/ai-agent-service.generator.js.map +1 -0
  7. package/dist/backend-ai/generators/ai-cache-service.generator.d.ts +1 -0
  8. package/dist/backend-ai/generators/ai-cache-service.generator.js +110 -0
  9. package/dist/backend-ai/generators/ai-cache-service.generator.js.map +1 -0
  10. package/dist/backend-ai/generators/ai-config.generator.d.ts +1 -0
  11. package/dist/backend-ai/generators/ai-config.generator.js +27 -0
  12. package/dist/backend-ai/generators/ai-config.generator.js.map +1 -0
  13. package/dist/backend-ai/generators/ai-module.generator.d.ts +2 -0
  14. package/dist/backend-ai/generators/ai-module.generator.js +89 -0
  15. package/dist/backend-ai/generators/ai-module.generator.js.map +1 -0
  16. package/dist/backend-ai/generators/ai-route.generator.d.ts +1 -0
  17. package/dist/backend-ai/generators/ai-route.generator.js +29 -0
  18. package/dist/backend-ai/generators/ai-route.generator.js.map +1 -0
  19. package/dist/backend-ai/generators/ai-tools-service.generator.d.ts +4 -0
  20. package/dist/backend-ai/generators/ai-tools-service.generator.js +222 -0
  21. package/dist/backend-ai/generators/ai-tools-service.generator.js.map +1 -0
  22. package/dist/backend-ai/generators/model-provider-interface.generator.d.ts +1 -0
  23. package/dist/backend-ai/generators/model-provider-interface.generator.js +48 -0
  24. package/dist/backend-ai/generators/model-provider-interface.generator.js.map +1 -0
  25. package/dist/backend-ai/generators/openai-model-provider-service.generator.d.ts +1 -0
  26. package/dist/backend-ai/generators/openai-model-provider-service.generator.js +128 -0
  27. package/dist/backend-ai/generators/openai-model-provider-service.generator.js.map +1 -0
  28. package/dist/backend-ai/index.d.ts +4 -0
  29. package/dist/backend-ai/index.js +40 -0
  30. package/dist/backend-ai/index.js.map +1 -0
  31. package/dist/backend-core/generators/main.generator.js +4 -3
  32. package/dist/backend-core/generators/main.generator.js.map +1 -1
  33. package/dist/backend-e2e/backend-e2e.generator.js +4 -4
  34. package/dist/backend-e2e/backend-e2e.generator.js.map +1 -1
  35. package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +1 -1
  36. package/dist/backend-router-trpc/generators/app-routes.generator.js +3 -1
  37. package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
  38. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +3 -0
  39. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
  40. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +2 -1
  41. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
  42. package/dist/backend-router-trpc/generators/trpc-shared.generator.js +7 -1
  43. package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
  44. package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
  45. package/dist/backend-router-trpc/router-trpc.generator.js +2 -0
  46. package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
  47. package/dist/backend-seed/seed.generator.js +10 -1
  48. package/dist/backend-seed/seed.generator.js.map +1 -1
  49. package/dist/base/template/scripts/setup.sh +9 -4
  50. package/dist/base/template/sonar-project.properties +9 -1
  51. package/dist/devops/generators/bitbucket-pipelines-yml.generator.js +1 -0
  52. package/dist/devops/generators/bitbucket-pipelines-yml.generator.js.map +1 -1
  53. package/dist/devops/generators/e2e-yml.generator.js +35 -10
  54. package/dist/devops/generators/e2e-yml.generator.js.map +1 -1
  55. package/dist/devops/generators/jenkinsfile.generator.js +25 -1
  56. package/dist/devops/generators/jenkinsfile.generator.js.map +1 -1
  57. package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
  58. package/dist/frontend-actions/actions.generator.d.ts +9 -0
  59. package/dist/frontend-actions/actions.generator.js +111 -0
  60. package/dist/frontend-actions/actions.generator.js.map +1 -0
  61. package/dist/frontend-actions/generators/ai-action-text.utils.generator.d.ts +1 -0
  62. package/dist/frontend-actions/generators/ai-action-text.utils.generator.js +52 -0
  63. package/dist/frontend-actions/generators/ai-action-text.utils.generator.js.map +1 -0
  64. package/dist/frontend-actions/generators/ai-assistant-store.generator.d.ts +1 -0
  65. package/dist/frontend-actions/generators/ai-assistant-store.generator.js +230 -0
  66. package/dist/frontend-actions/generators/ai-assistant-store.generator.js.map +1 -0
  67. package/dist/frontend-actions/generators/ai-sidebar-content.generator.d.ts +1 -0
  68. package/dist/frontend-actions/generators/ai-sidebar-content.generator.js +139 -0
  69. package/dist/frontend-actions/generators/ai-sidebar-content.generator.js.map +1 -0
  70. package/dist/frontend-actions/generators/ai-sidepane.generator.d.ts +1 -0
  71. package/dist/frontend-actions/generators/ai-sidepane.generator.js +98 -0
  72. package/dist/frontend-actions/generators/ai-sidepane.generator.js.map +1 -0
  73. package/dist/frontend-actions/generators/base-global-actions.generator.d.ts +1 -0
  74. package/dist/frontend-actions/generators/base-global-actions.generator.js +405 -0
  75. package/dist/frontend-actions/generators/base-global-actions.generator.js.map +1 -0
  76. package/dist/frontend-actions/generators/command-palette-action.generator.d.ts +1 -0
  77. package/dist/frontend-actions/generators/command-palette-action.generator.js +87 -0
  78. package/dist/frontend-actions/generators/command-palette-action.generator.js.map +1 -0
  79. package/dist/frontend-actions/generators/command-palette-store.generator.d.ts +1 -0
  80. package/dist/frontend-actions/generators/command-palette-store.generator.js +288 -0
  81. package/dist/frontend-actions/generators/command-palette-store.generator.js.map +1 -0
  82. package/dist/frontend-actions/generators/command-palette.generator.d.ts +5 -0
  83. package/dist/frontend-actions/generators/command-palette.generator.js +332 -0
  84. package/dist/frontend-actions/generators/command-palette.generator.js.map +1 -0
  85. package/dist/frontend-actions/generators/filter-utils.generator.d.ts +1 -0
  86. package/dist/frontend-actions/generators/filter-utils.generator.js +50 -0
  87. package/dist/frontend-actions/generators/filter-utils.generator.js.map +1 -0
  88. package/dist/frontend-actions/generators/sidepanel-toggle.generator.d.ts +1 -0
  89. package/dist/frontend-actions/generators/sidepanel-toggle.generator.js +37 -0
  90. package/dist/frontend-actions/generators/sidepanel-toggle.generator.js.map +1 -0
  91. package/dist/frontend-actions/index.d.ts +4 -0
  92. package/dist/frontend-actions/index.js +40 -0
  93. package/dist/frontend-actions/index.js.map +1 -0
  94. package/dist/frontend-admin/admin.generator.d.ts +3 -1
  95. package/dist/frontend-admin/admin.generator.js +8 -1
  96. package/dist/frontend-admin/admin.generator.js.map +1 -1
  97. package/dist/frontend-admin/generators/admin-global-actions.generator.d.ts +4 -0
  98. package/dist/frontend-admin/generators/admin-global-actions.generator.js +152 -0
  99. package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -0
  100. package/dist/frontend-admin/generators/comment-sidebar.generator.js +5 -3
  101. package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -1
  102. package/dist/frontend-admin/generators/detail-sidebar.generator.js +40 -24
  103. package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -1
  104. package/dist/frontend-admin/generators/model-admin-page.generator.js +172 -11
  105. package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
  106. package/dist/frontend-admin/utils.d.ts +1 -0
  107. package/dist/frontend-admin/utils.js +1 -0
  108. package/dist/frontend-admin/utils.js.map +1 -1
  109. package/dist/frontend-core/generators/tsconfig.generator.js +1 -0
  110. package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
  111. package/dist/frontend-core/template/.env.example +3 -0
  112. package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +22 -4
  113. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +1 -1
  114. package/dist/frontend-core/template/src/lib/color.ts +6 -3
  115. package/dist/frontend-core/template/src/lib/config.ts +3 -1
  116. package/dist/frontend-tables/generators/model-table.generator.js +13 -0
  117. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  118. package/dist/generators.js +4 -0
  119. package/dist/generators.js.map +1 -1
  120. package/dist/index.d.ts +2 -0
  121. package/dist/index.js +8 -2
  122. package/dist/index.js.map +1 -1
  123. package/dist/types/template/ai.types.ts +34 -0
  124. package/dist/types/types.generator.js +1 -0
  125. package/dist/types/types.generator.js.map +1 -1
  126. 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;