@lark-base-open/base-database-cli 1.1.1 → 1.3.7

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 (222) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +6 -0
  3. package/README.md +45 -27
  4. package/dist/ai-functions.d.ts +42 -0
  5. package/dist/ai-functions.d.ts.map +1 -0
  6. package/dist/ai-functions.js +327 -0
  7. package/dist/ai-functions.js.map +1 -0
  8. package/dist/archive.d.ts +21 -0
  9. package/dist/archive.d.ts.map +1 -1
  10. package/dist/archive.js +65 -10
  11. package/dist/archive.js.map +1 -1
  12. package/dist/auth.d.ts +5 -0
  13. package/dist/auth.d.ts.map +1 -1
  14. package/dist/auth.js +26 -3
  15. package/dist/auth.js.map +1 -1
  16. package/dist/check.d.ts +74 -0
  17. package/dist/check.d.ts.map +1 -0
  18. package/dist/check.js +283 -0
  19. package/dist/check.js.map +1 -0
  20. package/dist/dependency-policy.d.ts +10 -0
  21. package/dist/dependency-policy.d.ts.map +1 -0
  22. package/dist/dependency-policy.js +162 -0
  23. package/dist/dependency-policy.js.map +1 -0
  24. package/dist/deploy.d.ts +1 -0
  25. package/dist/deploy.d.ts.map +1 -1
  26. package/dist/deploy.js +122 -175
  27. package/dist/deploy.js.map +1 -1
  28. package/dist/environment-variables.d.ts +52 -0
  29. package/dist/environment-variables.d.ts.map +1 -0
  30. package/dist/environment-variables.js +563 -0
  31. package/dist/environment-variables.js.map +1 -0
  32. package/dist/environment.d.ts +1 -1
  33. package/dist/environment.js +1 -1
  34. package/dist/environment.js.map +1 -1
  35. package/dist/function-input-types.d.ts +10 -0
  36. package/dist/function-input-types.d.ts.map +1 -0
  37. package/dist/function-input-types.js +160 -0
  38. package/dist/function-input-types.js.map +1 -0
  39. package/dist/function-schema.d.ts.map +1 -1
  40. package/dist/function-schema.js +89 -36
  41. package/dist/function-schema.js.map +1 -1
  42. package/dist/init-field-retry.d.ts +17 -0
  43. package/dist/init-field-retry.d.ts.map +1 -0
  44. package/dist/init-field-retry.js +127 -0
  45. package/dist/init-field-retry.js.map +1 -0
  46. package/dist/init-journal.d.ts +98 -0
  47. package/dist/init-journal.d.ts.map +1 -0
  48. package/dist/init-journal.js +492 -0
  49. package/dist/init-journal.js.map +1 -0
  50. package/dist/init-schema.d.ts +8 -0
  51. package/dist/init-schema.d.ts.map +1 -0
  52. package/dist/init-schema.js +423 -0
  53. package/dist/init-schema.js.map +1 -0
  54. package/dist/init.d.ts +14 -0
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/init.js +883 -261
  57. package/dist/init.js.map +1 -1
  58. package/dist/lark-cli-error.d.ts +46 -0
  59. package/dist/lark-cli-error.d.ts.map +1 -0
  60. package/dist/lark-cli-error.js +171 -0
  61. package/dist/lark-cli-error.js.map +1 -0
  62. package/dist/lark-cli.d.ts +1 -1
  63. package/dist/lark-cli.d.ts.map +1 -1
  64. package/dist/lark-cli.js +66 -18
  65. package/dist/lark-cli.js.map +1 -1
  66. package/dist/mutation-schema.d.ts +146 -0
  67. package/dist/mutation-schema.d.ts.map +1 -0
  68. package/dist/mutation-schema.js +153 -0
  69. package/dist/mutation-schema.js.map +1 -0
  70. package/dist/mutation.d.ts +10 -0
  71. package/dist/mutation.d.ts.map +1 -0
  72. package/dist/mutation.js +131 -0
  73. package/dist/mutation.js.map +1 -0
  74. package/dist/npm.d.ts +1 -0
  75. package/dist/npm.d.ts.map +1 -1
  76. package/dist/npm.js +2 -2
  77. package/dist/npm.js.map +1 -1
  78. package/dist/preview-receipt.d.ts.map +1 -1
  79. package/dist/preview-receipt.js +15 -5
  80. package/dist/preview-receipt.js.map +1 -1
  81. package/dist/process.d.ts +3 -1
  82. package/dist/process.d.ts.map +1 -1
  83. package/dist/process.js +16 -4
  84. package/dist/process.js.map +1 -1
  85. package/dist/program.d.ts.map +1 -1
  86. package/dist/program.js +240 -41
  87. package/dist/program.js.map +1 -1
  88. package/dist/project-build.d.ts +25 -0
  89. package/dist/project-build.d.ts.map +1 -0
  90. package/dist/project-build.js +229 -0
  91. package/dist/project-build.js.map +1 -0
  92. package/dist/project-context.d.ts +344 -0
  93. package/dist/project-context.d.ts.map +1 -0
  94. package/dist/project-context.js +485 -0
  95. package/dist/project-context.js.map +1 -0
  96. package/dist/project-layout.d.ts +5 -0
  97. package/dist/project-layout.d.ts.map +1 -0
  98. package/dist/project-layout.js +24 -0
  99. package/dist/project-layout.js.map +1 -0
  100. package/dist/public-sql-error.d.ts +25 -0
  101. package/dist/public-sql-error.d.ts.map +1 -0
  102. package/dist/public-sql-error.js +81 -0
  103. package/dist/public-sql-error.js.map +1 -0
  104. package/dist/pull.d.ts.map +1 -1
  105. package/dist/pull.js +54 -15
  106. package/dist/pull.js.map +1 -1
  107. package/dist/site-readme.d.ts +3 -0
  108. package/dist/site-readme.d.ts.map +1 -0
  109. package/dist/site-readme.js +83 -0
  110. package/dist/site-readme.js.map +1 -0
  111. package/dist/workspace-fingerprint.d.ts +21 -1
  112. package/dist/workspace-fingerprint.d.ts.map +1 -1
  113. package/dist/workspace-fingerprint.js +81 -2
  114. package/dist/workspace-fingerprint.js.map +1 -1
  115. package/package.json +2 -1
  116. package/skills/lark-base-database/SKILL.md +66 -50
  117. package/skills/lark-base-database/references/ai-function.md +471 -0
  118. package/skills/lark-base-database/references/base-capability-routing.md +3 -3
  119. package/skills/lark-base-database/references/database-function-limitations.md +46 -0
  120. package/skills/lark-base-database/references/database-function.md +349 -0
  121. package/skills/lark-base-database/references/design/components/actions-fields.md +16 -0
  122. package/skills/lark-base-database/references/design/components/code/controls.css +122 -0
  123. package/skills/lark-base-database/references/design/components/code/controls.tsx +73 -0
  124. package/skills/lark-base-database/references/design/components/code/date-picker.tsx +42 -0
  125. package/skills/lark-base-database/references/design/components/code/floating.tsx +41 -0
  126. package/skills/lark-base-database/references/design/components/code/overlays.tsx +19 -0
  127. package/skills/lark-base-database/references/design/components/code/select.tsx +34 -0
  128. package/skills/lark-base-database/references/design/components/code/styles.d.ts +1 -0
  129. package/skills/lark-base-database/references/design/components/code/theme.css +49 -0
  130. package/skills/lark-base-database/references/design/components/code/usage.tsx +38 -0
  131. package/skills/lark-base-database/references/design/components/date-time-number.md +9 -0
  132. package/skills/lark-base-database/references/design/components/feedback-navigation.md +9 -0
  133. package/skills/lark-base-database/references/design/components/index.md +18 -0
  134. package/skills/lark-base-database/references/design/components/overlays.md +9 -0
  135. package/skills/lark-base-database/references/design/components/patterns/flat-business.md +7 -0
  136. package/skills/lark-base-database/references/design/components/selection.md +24 -0
  137. package/skills/lark-base-database/references/design/components.md +91 -0
  138. package/skills/lark-base-database/references/design/composition.md +90 -0
  139. package/skills/lark-base-database/references/design/control-recipes.md +107 -0
  140. package/skills/lark-base-database/references/design/examples.md +172 -0
  141. package/skills/lark-base-database/references/design/foundations.md +126 -0
  142. package/skills/lark-base-database/references/design/review.md +82 -0
  143. package/skills/lark-base-database/references/design/scenes/analysis-report.md +66 -0
  144. package/skills/lark-base-database/references/design/scenes/brand-showcase.md +70 -0
  145. package/skills/lark-base-database/references/design/scenes/business-operations.md +85 -0
  146. package/skills/lark-base-database/references/design/scenes/campaign-report.md +66 -0
  147. package/skills/lark-base-database/references/design/scenes/dashboard.md +77 -0
  148. package/skills/lark-base-database/references/design/scenes/festive-campaign.md +69 -0
  149. package/skills/lark-base-database/references/design/theme.md +27 -0
  150. package/skills/lark-base-database/references/design/typography.md +56 -0
  151. package/skills/lark-base-database/references/development.md +165 -32
  152. package/skills/lark-base-database/references/environment-variables.md +55 -0
  153. package/skills/lark-base-database/references/faas.md +41 -48
  154. package/skills/lark-base-database/references/file-transfer.md +25 -25
  155. package/skills/lark-base-database/references/frontend-design-guide.md +33 -77
  156. package/skills/lark-base-database/references/mutations.md +295 -0
  157. package/skills/lark-base-database/references/permission-design.md +24 -24
  158. package/skills/lark-base-database/references/release.md +51 -24
  159. package/skills/lark-base-database/references/style-guide.md +10 -67
  160. package/skills/lark-base-database/references/verification.md +15 -11
  161. package/skills/lark-base-database/references/workspace.md +46 -8
  162. package/templates/database-starter/README.md +34 -4
  163. package/templates/database-starter/database-functions/README.md +8 -0
  164. package/templates/database-starter/mutations/README.md +19 -0
  165. package/templates/database-starter/page/README.md +25 -19
  166. package/templates/database-starter/page/package-lock.json +8 -16
  167. package/templates/database-starter/page/package.json +2 -3
  168. package/templates/database-starter/page/src/App.tsx +23 -3
  169. package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
  170. package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
  171. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
  172. package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
  173. package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
  174. package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
  175. package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
  176. package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
  177. package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
  178. package/templates/database-starter/page/src/components/floating-button/index.tsx +75 -112
  179. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
  180. package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
  181. package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
  182. package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
  183. package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
  184. package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
  185. package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
  186. package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
  187. package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
  188. package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
  189. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
  190. package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
  191. package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
  192. package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
  193. package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
  194. package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
  195. package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
  196. package/templates/database-starter/page/src/lib/date-time.ts +61 -0
  197. package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
  198. package/templates/database-starter/page/src/main.tsx +1 -1
  199. package/templates/database-starter/page/tsconfig.json +2 -2
  200. package/templates/database-starter/page/vite.config.ts +1 -2
  201. package/templates/database-starter/prettierignore +1 -1
  202. package/templates/database-starter/server-functions/README.md +2 -2
  203. package/templates/database-starter/server-functions/package-lock.json +4 -4
  204. package/templates/database-starter/server-functions/package.json +1 -1
  205. package/dist/agent-guide.d.ts +0 -19
  206. package/dist/agent-guide.d.ts.map +0 -1
  207. package/dist/agent-guide.js +0 -31
  208. package/dist/agent-guide.js.map +0 -1
  209. package/dist/database-skill.d.ts +0 -24
  210. package/dist/database-skill.d.ts.map +0 -1
  211. package/dist/database-skill.js +0 -148
  212. package/dist/database-skill.js.map +0 -1
  213. package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
  214. package/skills/lark-base-database/references/database-capability-skill.md +0 -40
  215. package/skills/lark-base-database/references/sql-function.md +0 -289
  216. package/templates/database-capability/SKILL.md.tmpl +0 -58
  217. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
  218. package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
  219. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
  220. package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
  221. package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
  222. package/templates/database-starter/sql/README.md +0 -8
@@ -0,0 +1,382 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { Button } from '../../components/ui/button'
3
+ import {
4
+ createSiteAIFunctionTask,
5
+ getSiteAIFunctionTask,
6
+ hasRunnableMediaTask,
7
+ type SiteAIMediaTask,
8
+ } from '../../lib/database-runtime'
9
+ import { useUiLanguage } from '../../lib/ui-language'
10
+ import { AI_FUNCTION_COPY, formatAiTaskStatus } from './ai-copy'
11
+
12
+ // AI Function 媒体示例:文生图和文生视频统一使用任务提交 + 轮询。
13
+ // functionKey 指向对应能力 AI Function;页面提交 prompt,model/尺寸/时长由配置声明。
14
+
15
+ export interface AIImagePanelProps {
16
+ functionKey: string
17
+ referenceImages?: string[]
18
+ title?: string
19
+ pollIntervalMs?: number
20
+ pollDeadlineMs?: number
21
+ }
22
+
23
+ export interface AIVideoPanelProps {
24
+ functionKey: string
25
+ title?: string
26
+ // pollIntervalMs 轮询节奏,默认 3s;服务端也有轮询上限。
27
+ pollIntervalMs?: number
28
+ // pollDeadlineMs 单次任务总轮询时限,超过后放弃并结束 loading。默认 5 分钟。
29
+ pollDeadlineMs?: number
30
+ }
31
+
32
+ const DEFAULT_POLL_INTERVAL_MS = 3000
33
+ const DEFAULT_POLL_DEADLINE_MS = 5 * 60 * 1000
34
+
35
+ export interface MediaTaskRequestTracker {
36
+ activate: () => void
37
+ begin: () => number
38
+ deactivate: () => void
39
+ isCurrent: (requestId: number) => boolean
40
+ }
41
+
42
+ export function createMediaTaskRequestTracker(): MediaTaskRequestTracker {
43
+ let active = false
44
+ let sequence = 0
45
+ return {
46
+ activate: () => {
47
+ active = true
48
+ },
49
+ begin: () => {
50
+ sequence += 1
51
+ return sequence
52
+ },
53
+ deactivate: () => {
54
+ active = false
55
+ sequence += 1
56
+ },
57
+ isCurrent: (requestId) => active && requestId === sequence,
58
+ }
59
+ }
60
+
61
+ export function activateMediaTaskRequestTracker(
62
+ tracker: MediaTaskRequestTracker,
63
+ ): () => void {
64
+ tracker.activate()
65
+ return () => tracker.deactivate()
66
+ }
67
+
68
+ export interface MediaTaskPollUpdate {
69
+ error?: string
70
+ finished: boolean
71
+ task?: SiteAIMediaTask
72
+ }
73
+
74
+ /** pollMediaTaskUntilTerminal 是可复用的轮询循环:
75
+ * - `isCurrent()` 返回 false 时立即中止(旧任务),不 emit 更新;
76
+ * - `now()` 超过 `deadline` 时按超时结束;
77
+ * - 收到 succeeded/failed/cancelled 时结束;否则继续下一轮。
78
+ * 参数 <= 3 个:通过 config 传入,便于测试与复用。 */
79
+ export async function pollMediaTaskUntilTerminal(config: {
80
+ fetchTask: () => Promise<SiteAIMediaTask>
81
+ isCurrent: () => boolean
82
+ timing: {
83
+ deadline: number
84
+ intervalMs: number
85
+ now: () => number
86
+ sleep: (ms: number) => Promise<void>
87
+ }
88
+ onUpdate: (update: MediaTaskPollUpdate) => void
89
+ timeoutMessage: string
90
+ failureMessage: string
91
+ }): Promise<void> {
92
+ const { fetchTask, isCurrent, timing, onUpdate } = config
93
+ const { deadline, intervalMs, now, sleep } = timing
94
+ while (isCurrent()) {
95
+ await sleep(intervalMs)
96
+ if (!isCurrent()) return
97
+ if (now() >= deadline) {
98
+ onUpdate({ error: config.timeoutMessage, finished: true })
99
+ return
100
+ }
101
+ try {
102
+ const next = await fetchTask()
103
+ if (!isCurrent()) return
104
+ if (next.status === 'succeeded') {
105
+ onUpdate({ finished: true, task: next })
106
+ return
107
+ }
108
+ if (next.status === 'failed' || next.status === 'cancelled') {
109
+ onUpdate({
110
+ error: next.errMessage ?? config.failureMessage,
111
+ finished: true,
112
+ task: next,
113
+ })
114
+ return
115
+ }
116
+ onUpdate({ finished: false, task: next })
117
+ } catch (cause) {
118
+ if (!isCurrent()) return
119
+ onUpdate({
120
+ error: cause instanceof Error ? cause.message : config.failureMessage,
121
+ finished: true,
122
+ })
123
+ return
124
+ }
125
+ }
126
+ }
127
+
128
+ interface MediaTaskState {
129
+ error: string
130
+ loading: boolean
131
+ task: SiteAIMediaTask | null
132
+ }
133
+
134
+ const INITIAL_STATE: MediaTaskState = { error: '', loading: false, task: null }
135
+
136
+ interface MediaTaskOptions {
137
+ capability: 'image' | 'video'
138
+ failureMessage: string
139
+ functionKey: string
140
+ pollDeadlineMs?: number
141
+ pollIntervalMs?: number
142
+ referenceImages?: string[]
143
+ timeoutMessage: string
144
+ }
145
+
146
+ interface SubmitMediaTaskOptions
147
+ extends Required<
148
+ Pick<MediaTaskOptions, 'pollDeadlineMs' | 'pollIntervalMs'>
149
+ > {
150
+ capability: MediaTaskOptions['capability']
151
+ failureMessage: string
152
+ functionKey: string
153
+ isCurrent: () => boolean
154
+ onCreated: (task: SiteAIMediaTask) => void
155
+ onUpdate: (update: MediaTaskPollUpdate) => void
156
+ prompt: string
157
+ referenceImages: string[]
158
+ timeoutMessage: string
159
+ }
160
+
161
+ function useMediaTaskRequestTracker(scope: string): MediaTaskRequestTracker {
162
+ const trackerRef = useRef<MediaTaskRequestTracker | null>(null)
163
+ if (!trackerRef.current) trackerRef.current = createMediaTaskRequestTracker()
164
+ const tracker = trackerRef.current
165
+ useEffect(() => activateMediaTaskRequestTracker(tracker), [scope, tracker])
166
+ return tracker
167
+ }
168
+
169
+ function applyPollUpdate(
170
+ current: MediaTaskState,
171
+ update: MediaTaskPollUpdate,
172
+ ): MediaTaskState {
173
+ return {
174
+ ...current,
175
+ ...(update.task ? { task: update.task } : {}),
176
+ ...(update.error ? { error: update.error } : {}),
177
+ ...(update.finished ? { loading: false } : {}),
178
+ }
179
+ }
180
+
181
+ async function submitMediaTask(options: SubmitMediaTaskOptions): Promise<void> {
182
+ const created = await createSiteAIFunctionTask({
183
+ functionKey: options.functionKey,
184
+ capability: options.capability,
185
+ input: {
186
+ prompt: options.prompt,
187
+ ...(options.capability === 'image' && options.referenceImages.length
188
+ ? { image: options.referenceImages }
189
+ : {}),
190
+ },
191
+ })
192
+ if (!options.isCurrent()) return
193
+ options.onCreated(created)
194
+ if (!hasRunnableMediaTask(created)) {
195
+ options.onUpdate({ finished: true })
196
+ return
197
+ }
198
+ await pollMediaTaskUntilTerminal({
199
+ fetchTask: () => getSiteAIFunctionTask(options.functionKey, created.taskId),
200
+ isCurrent: options.isCurrent,
201
+ timing: {
202
+ deadline: Date.now() + options.pollDeadlineMs,
203
+ intervalMs: options.pollIntervalMs,
204
+ now: () => Date.now(),
205
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
206
+ },
207
+ onUpdate: options.onUpdate,
208
+ timeoutMessage: options.timeoutMessage,
209
+ failureMessage: options.failureMessage,
210
+ })
211
+ }
212
+
213
+ function useMediaTask(options: MediaTaskOptions) {
214
+ const {
215
+ capability,
216
+ failureMessage,
217
+ functionKey,
218
+ pollDeadlineMs = DEFAULT_POLL_DEADLINE_MS,
219
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
220
+ referenceImages = [],
221
+ timeoutMessage,
222
+ } = options
223
+ const [state, setState] = useState<MediaTaskState>(INITIAL_STATE)
224
+ const tracker = useMediaTaskRequestTracker(`${capability}:${functionKey}`)
225
+
226
+ const submit = async (prompt: string) => {
227
+ const value = prompt.trim()
228
+ if (!value) return
229
+ const requestId = tracker.begin()
230
+ const isCurrent = () => tracker.isCurrent(requestId)
231
+ setState({ ...INITIAL_STATE, loading: true })
232
+ try {
233
+ await submitMediaTask({
234
+ capability,
235
+ failureMessage,
236
+ functionKey,
237
+ isCurrent,
238
+ onCreated: (task) => setState((current) => ({ ...current, task })),
239
+ onUpdate: (update) =>
240
+ setState((current) => applyPollUpdate(current, update)),
241
+ pollDeadlineMs,
242
+ pollIntervalMs,
243
+ prompt: value,
244
+ referenceImages,
245
+ timeoutMessage,
246
+ })
247
+ } catch (cause) {
248
+ if (!isCurrent()) return
249
+ setState((current) => ({
250
+ ...current,
251
+ error: cause instanceof Error ? cause.message : failureMessage,
252
+ loading: false,
253
+ }))
254
+ }
255
+ }
256
+ return {
257
+ error: state.error,
258
+ loading: state.loading,
259
+ submit,
260
+ task: state.task,
261
+ }
262
+ }
263
+
264
+ /** 图片生成示例:支持纯文本、单参考图、多参考图,提交任务后轮询并展示图片资源。 */
265
+ export function AIImagePanel({
266
+ functionKey,
267
+ pollDeadlineMs,
268
+ pollIntervalMs,
269
+ referenceImages = [],
270
+ title,
271
+ }: AIImagePanelProps) {
272
+ const i18n = AI_FUNCTION_COPY[useUiLanguage()].image
273
+ const [prompt, setPrompt] = useState('')
274
+ const { error, loading, submit, task } = useMediaTask({
275
+ capability: 'image',
276
+ failureMessage: i18n.unavailable,
277
+ functionKey,
278
+ pollDeadlineMs,
279
+ pollIntervalMs,
280
+ referenceImages,
281
+ timeoutMessage: i18n.timeout,
282
+ })
283
+
284
+ const generate = async () => {
285
+ const value = prompt.trim()
286
+ if (!value || loading) return
287
+ await submit(value)
288
+ }
289
+
290
+ return (
291
+ <section className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-5">
292
+ <h2 className="font-semibold text-card-foreground">
293
+ {title ?? i18n.title}
294
+ </h2>
295
+ <textarea
296
+ aria-label={i18n.description}
297
+ className="min-h-20 rounded-lg border border-border bg-background p-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
298
+ onChange={(event) => setPrompt(event.target.value)}
299
+ placeholder={i18n.descriptionPlaceholder}
300
+ value={prompt}
301
+ />
302
+ <Button disabled={loading || !prompt.trim()} onClick={generate}>
303
+ {loading ? i18n.generating : i18n.generate}
304
+ </Button>
305
+ {error ? <p className="text-sm text-danger">{error}</p> : null}
306
+ <div className="grid grid-cols-2 gap-3">
307
+ {(task?.type === 'image_task' ? (task.imageAssets ?? []) : [])
308
+ .filter((asset): asset is typeof asset & { url: string } =>
309
+ Boolean(asset.url),
310
+ )
311
+ .map((asset) => (
312
+ <img
313
+ alt={i18n.generatedImage}
314
+ className="w-full rounded-lg border border-border"
315
+ key={asset.url}
316
+ src={asset.url}
317
+ />
318
+ ))}
319
+ </div>
320
+ </section>
321
+ )
322
+ }
323
+
324
+ /** 文生视频示例:提交任务后按节奏轮询,成功后展示视频。 */
325
+ export function AIVideoPanel({
326
+ functionKey,
327
+ pollDeadlineMs,
328
+ pollIntervalMs,
329
+ title,
330
+ }: AIVideoPanelProps) {
331
+ const language = useUiLanguage()
332
+ const i18n = AI_FUNCTION_COPY[language].video
333
+ const [prompt, setPrompt] = useState('')
334
+ const { error, loading, submit, task } = useMediaTask({
335
+ capability: 'video',
336
+ failureMessage: i18n.unavailable,
337
+ functionKey,
338
+ pollDeadlineMs,
339
+ pollIntervalMs,
340
+ timeoutMessage: i18n.timeout,
341
+ })
342
+
343
+ const generate = async () => {
344
+ const value = prompt.trim()
345
+ if (!value || loading) return
346
+ await submit(value)
347
+ }
348
+
349
+ return (
350
+ <section className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-5">
351
+ <h2 className="font-semibold text-card-foreground">
352
+ {title ?? i18n.title}
353
+ </h2>
354
+ <textarea
355
+ aria-label={i18n.description}
356
+ className="min-h-20 rounded-lg border border-border bg-background p-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
357
+ onChange={(event) => setPrompt(event.target.value)}
358
+ placeholder={i18n.descriptionPlaceholder}
359
+ value={prompt}
360
+ />
361
+ <Button disabled={loading || !prompt.trim()} onClick={generate}>
362
+ {loading ? i18n.processing : i18n.generate}
363
+ </Button>
364
+ {task ? (
365
+ <p className="text-sm text-muted-foreground">
366
+ {formatAiTaskStatus(task.status, task.progress, language)}
367
+ </p>
368
+ ) : null}
369
+ {error ? <p className="text-sm text-danger">{error}</p> : null}
370
+ {task?.type === 'video_task' &&
371
+ task.status === 'succeeded' &&
372
+ task.videoAsset?.url ? (
373
+ <video
374
+ aria-label={i18n.generatedVideo}
375
+ className="w-full rounded-lg border border-border"
376
+ controls
377
+ src={task.videoAsset.url}
378
+ />
379
+ ) : null}
380
+ </section>
381
+ )
382
+ }
@@ -0,0 +1,144 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import {
3
+ fetchAttachmentCover,
4
+ type DatabaseAttachmentRef,
5
+ } from './database-runtime'
6
+ import { useUiLanguage, type UiLanguage } from './ui-language'
7
+
8
+ const ATTACHMENT_IMAGE_COPY = {
9
+ en: {
10
+ decodeFailed: 'Image could not be decoded',
11
+ loadFailed: 'Failed to load image',
12
+ loading: 'Loading image',
13
+ retry: 'Retry image',
14
+ },
15
+ zh: {
16
+ decodeFailed: '图片无法解码',
17
+ loadFailed: '图片加载失败',
18
+ loading: '图片加载中',
19
+ retry: '重试图片',
20
+ },
21
+ ja: {
22
+ decodeFailed: '画像を表示できませんでした',
23
+ loadFailed: '画像を読み込めませんでした',
24
+ loading: '画像を読み込み中',
25
+ retry: '画像を再読み込み',
26
+ },
27
+ } satisfies Record<
28
+ UiLanguage,
29
+ {
30
+ decodeFailed: string
31
+ loadFailed: string
32
+ loading: string
33
+ retry: string
34
+ }
35
+ >
36
+
37
+ /** 缩略图与大图共用;只在靠近可见区域时加载,组件持有并释放本地图片 URL。 */
38
+ export function AttachmentImage({
39
+ attachment,
40
+ size = 360,
41
+ alt,
42
+ className,
43
+ }: {
44
+ attachment: DatabaseAttachmentRef
45
+ size?: number
46
+ alt: string
47
+ className?: string
48
+ }) {
49
+ const uiLanguage = useUiLanguage()
50
+ const i18n = ATTACHMENT_IMAGE_COPY[uiLanguage]
51
+ const container = useRef<HTMLSpanElement>(null)
52
+ const [visible, setVisible] = useState(false)
53
+ const [attempt, setAttempt] = useState(0)
54
+ const [image, setImage] = useState<{
55
+ key: string
56
+ url: string
57
+ error: string
58
+ localError?: 'loadFailed' | 'decodeFailed'
59
+ }>({ key: '', url: '', error: '' })
60
+ const { tableId, fieldId, recordId, fileToken } = attachment
61
+ const key = JSON.stringify([
62
+ tableId,
63
+ fieldId,
64
+ recordId,
65
+ fileToken,
66
+ size,
67
+ attempt,
68
+ ])
69
+
70
+ useEffect(() => {
71
+ if (typeof IntersectionObserver === 'undefined') {
72
+ setVisible(true)
73
+ return
74
+ }
75
+ const observer = new IntersectionObserver(
76
+ (entries) => {
77
+ if (entries.some((entry) => entry.isIntersecting)) {
78
+ setVisible(true)
79
+ observer.disconnect()
80
+ }
81
+ },
82
+ { rootMargin: '200px' },
83
+ )
84
+ if (container.current) observer.observe(container.current)
85
+ return () => observer.disconnect()
86
+ }, [])
87
+
88
+ useEffect(() => {
89
+ if (!visible) return
90
+ const controller = new AbortController()
91
+ let url = ''
92
+ fetchAttachmentCover({ tableId, fieldId, recordId, fileToken }, size, {
93
+ signal: controller.signal,
94
+ })
95
+ .then((blob) => {
96
+ if (controller.signal.aborted) return
97
+ url = URL.createObjectURL(blob)
98
+ setImage({ key, url, error: '' })
99
+ })
100
+ .catch((cause: unknown) => {
101
+ if (!controller.signal.aborted) {
102
+ setImage({
103
+ key,
104
+ url: '',
105
+ error: cause instanceof Error ? cause.message : '',
106
+ localError: cause instanceof Error ? undefined : 'loadFailed',
107
+ })
108
+ }
109
+ })
110
+ return () => {
111
+ controller.abort()
112
+ if (url) URL.revokeObjectURL(url)
113
+ }
114
+ }, [visible, key, tableId, fieldId, recordId, fileToken, size])
115
+
116
+ const current = image.key === key ? image : undefined
117
+ return (
118
+ <span ref={container} className={className} aria-busy={!current}>
119
+ {current?.url ? (
120
+ <img
121
+ src={current.url}
122
+ alt={alt}
123
+ onError={() =>
124
+ setImage({ key, url: '', error: '', localError: 'decodeFailed' })
125
+ }
126
+ />
127
+ ) : current?.error || current?.localError ? (
128
+ <span>
129
+ <span role="status">
130
+ {current.localError ? i18n[current.localError] : current.error}
131
+ </span>
132
+ <button
133
+ type="button"
134
+ onClick={() => setAttempt((value) => value + 1)}
135
+ >
136
+ {i18n.retry}
137
+ </button>
138
+ </span>
139
+ ) : (
140
+ <span role="status">{i18n.loading}</span>
141
+ )}
142
+ </span>
143
+ )
144
+ }
@@ -0,0 +1,4 @@
1
+ /* Generated by base-database-cli from Function metadata. Do not edit. */
2
+ // prettier-ignore
3
+ export type DatabaseFunctionInputMap = {
4
+ }