@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
@@ -1,4 +1,9 @@
1
1
  import { fetch as databaseFetch } from '../../lib/database-runtime'
2
+ import {
3
+ getUiLocale,
4
+ getUiLanguage,
5
+ type UiLanguage,
6
+ } from '../../lib/ui-language'
2
7
  import { fetchProjectMeta } from '../floating-button/utils/project-meta'
3
8
 
4
9
  const RECOMMEND_USERS_API_PATH = '/space/api/mention/recommend.v2/'
@@ -7,6 +12,41 @@ const BITABLE_MENTION_SOURCE = '4'
7
12
  const USER_MENTION_TYPE = '0'
8
13
  const SEARCH_PAGE_SIZE = 20
9
14
 
15
+ const USER_SEARCH_COPY = {
16
+ en: {
17
+ businessError: (code: number) =>
18
+ `Could not search members (error code ${code})`,
19
+ httpError: (status: number) =>
20
+ `Member search request failed: HTTP ${status}`,
21
+ invalidResponse: 'Member search response has an invalid structure',
22
+ noBoundBase:
23
+ 'Cannot search members: the application is not bound to a Base',
24
+ },
25
+ zh: {
26
+ businessError: (code: number) => `搜索成员失败(错误码 ${code})`,
27
+ httpError: (status: number) => `搜索成员请求失败:HTTP ${status}`,
28
+ invalidResponse: '成员搜索响应结构无效',
29
+ noBoundBase: '无法搜索成员:应用尚未绑定多维表格',
30
+ },
31
+ ja: {
32
+ businessError: (code: number) =>
33
+ `メンバーを検索できませんでした(エラーコード ${code})`,
34
+ httpError: (status: number) =>
35
+ `メンバー検索のリクエストに失敗しました:HTTP ${status}`,
36
+ invalidResponse: 'メンバー検索の応答形式が正しくありません',
37
+ noBoundBase:
38
+ 'メンバーを検索できません:アプリにBaseが関連付けられていません',
39
+ },
40
+ } satisfies Record<
41
+ UiLanguage,
42
+ {
43
+ businessError: (code: number) => string
44
+ httpError: (status: number) => string
45
+ invalidResponse: string
46
+ noBoundBase: string
47
+ }
48
+ >
49
+
10
50
  export interface UserAvatar {
11
51
  avatar_72: string
12
52
  avatar_240: string
@@ -38,11 +78,14 @@ export type SearchUsers = (keyword: string) => Promise<SearchUsersResponse>
38
78
 
39
79
  export async function requestUsers(
40
80
  keyword: string,
81
+ language?: UiLanguage,
41
82
  ): Promise<SearchUsersResponse> {
83
+ const selectedLanguage = language ?? getUiLanguage()
84
+ const i18n = USER_SEARCH_COPY[selectedLanguage]
42
85
  const projectMeta = await fetchProjectMeta()
43
86
  const baseToken = projectMeta.baseBindings[0]?.baseToken
44
87
  if (!baseToken) {
45
- throw new Error('应用未绑定 Base,无法搜索成员')
88
+ throw new Error(i18n.noBoundBase)
46
89
  }
47
90
 
48
91
  const body = new URLSearchParams({
@@ -66,30 +109,32 @@ export async function requestUsers(
66
109
  })
67
110
 
68
111
  if (!response.ok) {
69
- throw new Error(`搜索用户请求失败,HTTP 状态码:${response.status}`)
112
+ throw new Error(i18n.httpError(response.status))
70
113
  }
71
114
 
72
115
  const payload: unknown = await response.json()
73
116
  return {
74
117
  code: 0,
75
118
  msg: 'ok',
76
- data: normalizeRecommendUsersResponse(payload),
119
+ data: normalizeRecommendUsersResponse(payload, selectedLanguage),
77
120
  }
78
121
  }
79
122
 
80
123
  export function normalizeRecommendUsersResponse(
81
124
  value: unknown,
82
- locale = getLocale(),
125
+ language: UiLanguage = getUiLanguage(),
83
126
  ): SearchUsersData {
84
- const payload = unwrapRecommendPayload(value)
127
+ const locale = getUiLocale(language)
128
+ const i18n = USER_SEARCH_COPY[language]
129
+ const payload = unwrapRecommendPayload(value, i18n)
85
130
  if (!payload) {
86
- throw new Error('搜索用户返回结构不正确')
131
+ throw new Error(i18n.invalidResponse)
87
132
  }
88
133
 
89
134
  const entities = payload.entities
90
135
  const users = isRecord(entities) ? entities.users : undefined
91
136
  if (!isRecord(users) || !Array.isArray(payload.result_list)) {
92
- throw new Error('搜索用户返回结构不正确')
137
+ throw new Error(i18n.invalidResponse)
93
138
  }
94
139
 
95
140
  const result: User[] = []
@@ -130,11 +175,16 @@ export function normalizeRecommendUsersResponse(
130
175
 
131
176
  function unwrapRecommendPayload(
132
177
  value: unknown,
178
+ i18n: (typeof USER_SEARCH_COPY)[UiLanguage],
133
179
  ): Record<string, unknown> | undefined {
134
180
  if (!isRecord(value)) return undefined
135
181
 
136
182
  if (typeof value.code === 'number' && value.code !== 0) {
137
- throw new Error(readString(value.msg) || '搜索成员失败')
183
+ const message = readString(value.msg)
184
+ throw Object.assign(new Error(message || i18n.businessError(value.code)), {
185
+ code: String(value.code),
186
+ cause: value,
187
+ })
138
188
  }
139
189
 
140
190
  if ('entities' in value && 'result_list' in value) return value
@@ -144,12 +194,12 @@ function unwrapRecommendPayload(
144
194
  function readUserName(entity: Record<string, unknown>, locale: string): string {
145
195
  return (
146
196
  readLocalizedString(entity.display_name, locale) ||
147
- (locale.toLowerCase().startsWith('en')
148
- ? readString(entity.en_name)
149
- : readString(entity.cn_name)) ||
197
+ (locale.toLowerCase().startsWith('zh')
198
+ ? readString(entity.cn_name)
199
+ : readString(entity.en_name)) ||
150
200
  readString(entity.name) ||
151
- readString(entity.cn_name) ||
152
- readString(entity.en_name)
201
+ readString(entity.en_name) ||
202
+ readString(entity.cn_name)
153
203
  )
154
204
  }
155
205
 
@@ -168,11 +218,14 @@ function readLocalizedString(value: unknown, locale: string): string {
168
218
 
169
219
  const normalizedLocale = locale.toLowerCase().replace('-', '_')
170
220
  const language = normalizedLocale.split('_')[0] ?? ''
221
+ const fallback =
222
+ language === 'zh'
223
+ ? readString(value.zh_cn) || readString(value.en_us)
224
+ : readString(value.en_us) || readString(value.zh_cn)
171
225
  return (
172
226
  readString(value[normalizedLocale]) ||
173
227
  readString(value[language]) ||
174
- readString(value.zh_cn) ||
175
- readString(value.en_us)
228
+ fallback
176
229
  )
177
230
  }
178
231
 
@@ -186,16 +239,6 @@ function readStringArray(value: unknown): string[] {
186
239
  : []
187
240
  }
188
241
 
189
- function getLocale(): string {
190
- if (typeof document !== 'undefined' && document.documentElement.lang) {
191
- return document.documentElement.lang
192
- }
193
- if (typeof navigator !== 'undefined' && navigator.language) {
194
- return navigator.language
195
- }
196
- return 'zh-CN'
197
- }
198
-
199
242
  function isRecord(value: unknown): value is Record<string, unknown> {
200
243
  return typeof value === 'object' && value !== null && !Array.isArray(value)
201
244
  }
@@ -11,7 +11,7 @@ import { siteRuntime } from '../lib/database-runtime'
11
11
  export function DatabaseRuntimeReady(): null {
12
12
  useEffect(() => {
13
13
  void siteRuntime.ready().catch((error: unknown) => {
14
- console.error('Application Runtime ready 上报失败', error)
14
+ console.error('Failed to report Application Runtime readiness', error)
15
15
  })
16
16
  }, [])
17
17
 
@@ -5,8 +5,7 @@ import {
5
5
  } from '../floating-button/utils/current-user'
6
6
  import { fetchProjectMeta } from '../floating-button/utils/project-meta'
7
7
  import { logout } from '../../lib/database-runtime'
8
-
9
- export type DatabaseUserMenuLanguage = 'en' | 'zh'
8
+ import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
10
9
 
11
10
  /** 应用面向的使用场景。
12
11
  *
@@ -17,8 +16,6 @@ export type DatabaseUserMenuLanguage = 'en' | 'zh'
17
16
  export type DatabaseAudience = 'enterprise' | 'personal'
18
17
 
19
18
  interface DatabaseUserMenuProps {
20
- /** 界面语言,默认 en。 */
21
- language?: DatabaseUserMenuLanguage
22
19
  /** 默认 enterprise:拿不准时宁可多显示一个头像,也不要让用户找不到退出登录。 */
23
20
  audience?: DatabaseAudience
24
21
  /** 弹层相对触发器的对齐方向,默认 right(触发器在右上角时用)。 */
@@ -35,9 +32,10 @@ interface DatabaseUserMenuI18n {
35
32
  logout: string
36
33
  }
37
34
 
38
- const I18N: Record<DatabaseUserMenuLanguage, DatabaseUserMenuI18n> = {
35
+ const I18N: Record<UiLanguage, DatabaseUserMenuI18n> = {
39
36
  en: { account: 'Account', logout: 'Log out' },
40
37
  zh: { account: '账号', logout: '退出登录' },
38
+ ja: { account: 'アカウント', logout: 'ログアウト' },
41
39
  }
42
40
 
43
41
  /** 应用自身导航里的当前用户入口:头像 + 姓名,点开是组织名与退出登录。
@@ -51,7 +49,6 @@ const I18N: Record<DatabaseUserMenuLanguage, DatabaseUserMenuI18n> = {
51
49
  * 不占位、不显示空头像。
52
50
  */
53
51
  export function DatabaseUserMenu({
54
- language = 'en',
55
52
  audience = 'enterprise',
56
53
  align = 'right',
57
54
  side = 'down',
@@ -62,7 +59,7 @@ export function DatabaseUserMenu({
62
59
  const [organization, setOrganization] = useState('')
63
60
  const [isOpen, setIsOpen] = useState(false)
64
61
  const rootRef = useRef<HTMLDivElement>(null)
65
- const i18n = I18N[language]
62
+ const i18n = I18N[useUiLanguage()]
66
63
 
67
64
  useEffect(() => {
68
65
  if (audience !== 'enterprise') {
@@ -1,6 +1,40 @@
1
1
  import { CircleAlert, Inbox, LoaderCircle } from 'lucide-react'
2
+ import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
2
3
  import { Button } from '../ui/button'
3
4
 
5
+ const PAGE_STATE_COPY = {
6
+ en: {
7
+ empty: 'No data yet',
8
+ error: 'Could not load data',
9
+ loading: 'Loading',
10
+ retry: 'Retry',
11
+ retryLater: 'Please try again later',
12
+ },
13
+ zh: {
14
+ empty: '暂无数据',
15
+ error: '加载失败',
16
+ loading: '正在加载',
17
+ retry: '重试',
18
+ retryLater: '请稍后重试',
19
+ },
20
+ ja: {
21
+ empty: 'データがありません',
22
+ error: 'データを読み込めませんでした',
23
+ loading: '読み込み中',
24
+ retry: '再試行',
25
+ retryLater: 'しばらくしてからもう一度お試しください',
26
+ },
27
+ } satisfies Record<
28
+ UiLanguage,
29
+ {
30
+ empty: string
31
+ error: string
32
+ loading: string
33
+ retry: string
34
+ retryLater: string
35
+ }
36
+ >
37
+
4
38
  interface PageStateProps {
5
39
  status: 'loading' | 'empty' | 'error'
6
40
  title?: string
@@ -15,6 +49,7 @@ export function PageState({
15
49
  description,
16
50
  onRetry,
17
51
  }: PageStateProps) {
52
+ const i18n = PAGE_STATE_COPY[useUiLanguage()]
18
53
  if (status === 'loading') {
19
54
  return (
20
55
  <div
@@ -25,7 +60,7 @@ export function PageState({
25
60
  aria-hidden="true"
26
61
  className="size-6 motion-safe:animate-spin"
27
62
  />
28
- <p className="text-sm font-medium">{title ?? '正在加载'}</p>
63
+ <p className="text-sm font-medium">{title ?? i18n.loading}</p>
29
64
  </div>
30
65
  )
31
66
  }
@@ -35,7 +70,7 @@ export function PageState({
35
70
  <div className="flex min-h-48 flex-col items-center justify-center gap-3 text-center">
36
71
  <Inbox aria-hidden="true" className="size-7 text-muted-foreground" />
37
72
  <div>
38
- <p className="font-semibold">{title ?? '暂无数据'}</p>
73
+ <p className="font-semibold">{title ?? i18n.empty}</p>
39
74
  {description ? (
40
75
  <p className="mt-1 text-sm text-muted-foreground">{description}</p>
41
76
  ) : null}
@@ -51,14 +86,14 @@ export function PageState({
51
86
  >
52
87
  <CircleAlert aria-hidden="true" className="size-7 text-danger" />
53
88
  <div>
54
- <p className="font-semibold">{title ?? '加载失败'}</p>
89
+ <p className="font-semibold">{title ?? i18n.error}</p>
55
90
  <p className="mt-1 text-sm text-muted-foreground">
56
- {description ?? '请稍后重试'}
91
+ {description ?? i18n.retryLater}
57
92
  </p>
58
93
  </div>
59
94
  {onRetry ? (
60
95
  <Button variant="secondary" onClick={onRetry}>
61
- 重试
96
+ {i18n.retry}
62
97
  </Button>
63
98
  ) : null}
64
99
  </div>
@@ -0,0 +1,3 @@
1
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M17.1956 16.3595C17.6558 16.3595 18.0289 16.7326 18.0289 17.1928C18.0287 17.6529 17.6557 18.0261 17.1956 18.0261H9.48724C9.0273 18.0259 8.65411 17.6527 8.6539 17.1928C8.6539 16.7327 9.02717 16.3597 9.48724 16.3595H17.1956ZM12.5732 2.47195C13.8817 1.28992 15.902 1.32977 17.163 2.59076L17.281 2.71527C18.4249 3.98149 18.4257 5.91435 17.2818 7.18061L17.163 7.3043L9.32366 15.1445C9.12419 15.3439 8.89959 15.5169 8.65716 15.6596L8.40976 15.7923L4.13079 17.8471C2.75554 18.5075 1.31776 17.1336 1.84889 15.7565L1.90667 15.623L3.96152 11.344C4.12431 11.0051 4.34347 10.696 4.60931 10.4301L12.4495 2.59076L12.5732 2.47195ZM5.78769 11.6093C5.65491 11.7421 5.54596 11.8966 5.46461 12.0659L3.40976 16.344L7.68792 14.2892C7.85719 14.2078 8.01164 14.0989 8.14446 13.9661L13.4643 8.64545L11.1075 6.28868L5.78769 11.6093ZM15.9846 3.76915C15.3338 3.11835 14.2788 3.11851 13.6279 3.76915L12.2867 5.11029L14.6435 7.46706L15.9846 6.12592C16.6353 5.47503 16.6354 4.41994 15.9846 3.76915Z" fill="#1456F0"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M17.1956 16.3595C17.6558 16.3595 18.0289 16.7326 18.0289 17.1928C18.0287 17.6529 17.6557 18.0261 17.1956 18.0261H9.48724C9.0273 18.0259 8.65411 17.6527 8.6539 17.1928C8.6539 16.7327 9.02717 16.3597 9.48724 16.3595H17.1956ZM12.5732 2.47195C13.8817 1.28992 15.902 1.32977 17.163 2.59076L17.281 2.71527C18.4249 3.98149 18.4257 5.91435 17.2818 7.18061L17.163 7.3043L9.32366 15.1445C9.12419 15.3439 8.89959 15.5169 8.65716 15.6596L8.40976 15.7923L4.13079 17.8471C2.75554 18.5075 1.31776 17.1336 1.84889 15.7565L1.90667 15.623L3.96152 11.344C4.12431 11.0051 4.34347 10.696 4.60931 10.4301L12.4495 2.59076L12.5732 2.47195ZM5.78769 11.6093C5.65491 11.7421 5.54596 11.8966 5.46461 12.0659L3.40976 16.344L7.68792 14.2892C7.85719 14.2078 8.01164 14.0989 8.14446 13.9661L13.4643 8.64545L11.1075 6.28868L5.78769 11.6093ZM15.9846 3.76915C15.3338 3.11835 14.2788 3.11851 13.6279 3.76915L12.2867 5.11029L14.6435 7.46706L15.9846 6.12592C16.6353 5.47503 16.6354 4.41994 15.9846 3.76915Z" fill="#2B2F36"/>
3
+ </svg>
@@ -1,84 +1,60 @@
1
1
  import { useCallback, useEffect, useState } from 'react'
2
+ import editDefaultIcon from './assets/edit-default.svg'
3
+ import editActiveIcon from './assets/edit-active.svg'
2
4
  import { fetchProjectMeta, type ProjectMeta } from './utils/project-meta'
3
- import { ChatbotEntry, resolveChatbotDisplayName } from './chatbot-entry'
4
5
  import {
5
6
  getDatabaseContext,
6
7
  resolveDatabaseContext,
7
8
  siteRuntime,
8
9
  } from '../../lib/database-runtime'
9
-
10
- export type FloatingButtonLanguage = 'en' | 'zh'
11
-
12
- interface FloatingButtonProps {
13
- language?: FloatingButtonLanguage
14
- showChatbotEntry?: boolean
15
- }
10
+ import { useUiLanguage, type UiLanguage } from '../../lib/ui-language'
16
11
 
17
12
  interface FloatingButtonI18n {
18
13
  editSite: string
14
+ hidePanel: string
19
15
  }
20
16
 
21
17
  const BASE_DATABASE_OVERVIEW_URL = 'https://feishu.cn/base/site/detail/overview'
22
- const CHATBOT_SIDEBAR_DEFAULT_WIDTH = 420
23
18
  const FLOATING_BUTTON_PAGE_GAP = 24
24
19
  const EMPTY_PROJECT_META: ProjectMeta = {
25
20
  baseBindings: [],
26
- chatList: [],
27
21
  description: '',
28
22
  isOwner: false,
23
+ allowEdit: false,
29
24
  name: '',
30
25
  organization: '',
31
26
  }
32
- const FLOATING_BUTTON_I18N: Record<FloatingButtonLanguage, FloatingButtonI18n> =
33
- {
34
- en: {
35
- editSite: 'Edit application',
36
- },
37
- zh: {
38
- editSite: '编辑应用',
39
- },
40
- }
27
+ const FLOATING_BUTTON_I18N: Record<UiLanguage, FloatingButtonI18n> = {
28
+ en: {
29
+ editSite: 'Edit and manage your apps',
30
+ hidePanel: 'Hide until next visit',
31
+ },
32
+ zh: {
33
+ editSite: '编辑与管理应用',
34
+ hidePanel: '隐藏面板',
35
+ },
36
+ ja: {
37
+ editSite: 'アプリを編集・管理',
38
+ hidePanel: '次回のアクセスまで非表示',
39
+ },
40
+ }
41
41
 
42
- /** 渲染 Base Database 悬浮按钮入口:编辑、Chatbot。
43
- *
44
- * 原来这里还有一个「三个点」菜单,承载当前账号 / 组织 / 退出登录。它已经下掉了:
45
- * 悬浮控件是应用之外的一层浮层,用户不会去那里找账号。身份信息改由应用自己的导航
46
- * 承载,见 `page/src/components/database-user-menu`。
47
- *
48
- * @param props.language 界面语言,支持 en 和 zh,默认 en。
49
- * @param props.showChatbotEntry 是否显示 Chatbot 入口,默认 true。
50
- * @returns 悬浮按钮组。
42
+ /** 渲染有管理权限的访问者在正式环境中的编辑入口。
43
+ * @returns 编辑按钮;Preview 或无管理权限时不显示。
51
44
  */
52
- export function FloatingButton({
53
- language = 'en',
54
- showChatbotEntry = true,
55
- }: FloatingButtonProps) {
45
+ export function FloatingButton() {
56
46
  const [projectMeta, setProjectMeta] = useState<ProjectMeta | null>(null)
57
- const [isFloatingDataLoaded, setIsFloatingDataLoaded] = useState(false)
58
- const [isChatbotOpen, setIsChatbotOpen] = useState(false)
59
- const [chatbotSidebarWidth, setChatbotSidebarWidth] = useState(
60
- CHATBOT_SIDEBAR_DEFAULT_WIDTH,
47
+ const [isHidden, setIsHidden] = useState(false)
48
+ const [activeTooltip, setActiveTooltip] = useState<'edit' | 'hide' | null>(
49
+ null,
61
50
  )
62
- // 编辑只在线上给 owner。Preview 本身就只有 owner 进得来,isOwner 恒为 true,
63
- // 靠它永远隐藏不掉,必须显式按产物环境判断。
51
+ // Preview 不展示返回编辑态入口;按钮显隐不替代宿主点击时的鉴权。
64
52
  const shouldShowEditButton =
65
- projectMeta?.isOwner === true && getDatabaseContext().env === 'prod'
66
- const shouldOffsetForChatbot = showChatbotEntry && isChatbotOpen
67
- const shouldShowFloatingControls =
68
- shouldShowEditButton || (showChatbotEntry && !isChatbotOpen)
69
- const floatingButtonRight = shouldOffsetForChatbot
70
- ? chatbotSidebarWidth + FLOATING_BUTTON_PAGE_GAP
71
- : FLOATING_BUTTON_PAGE_GAP
72
- const i18n = FLOATING_BUTTON_I18N[language]
73
- const genericChatbotName = language === 'zh' ? '智能助手' : 'AI assistant'
74
- const chatbotFallbackName = resolveChatbotDisplayName(
75
- projectMeta?.name ?? '',
76
- genericChatbotName,
77
- )
53
+ projectMeta?.allowEdit === true && getDatabaseContext().env === 'prod'
54
+ const i18n = FLOATING_BUTTON_I18N[useUiLanguage()]
78
55
 
79
56
  useEffect(() => {
80
57
  let isCurrentRequest = true
81
- setIsFloatingDataLoaded(false)
82
58
 
83
59
  /** 读取悬浮控件所需的项目资源数据。 */
84
60
  async function loadFloatingData(): Promise<void> {
@@ -91,7 +67,6 @@ export function FloatingButton({
91
67
  }
92
68
 
93
69
  setProjectMeta(nextProjectMeta)
94
- setIsFloatingDataLoaded(true)
95
70
  }
96
71
 
97
72
  void loadFloatingData()
@@ -101,18 +76,6 @@ export function FloatingButton({
101
76
  }
102
77
  }, [])
103
78
 
104
- useEffect(() => {
105
- if (showChatbotEntry) {
106
- return
107
- }
108
-
109
- setIsChatbotOpen(false)
110
- }, [showChatbotEntry])
111
-
112
- const handleChatbotOpenChange = useCallback((nextIsOpen: boolean) => {
113
- setIsChatbotOpen(nextIsOpen)
114
- }, [])
115
-
116
79
  // 编辑按钮走 site.v1 契约:应用发 request-edit,宿主切编辑态后广播 mode-changed。
117
80
  // 应用被独立访问(无宿主)时事件无人消费,回落为打开 Site 管理页。
118
81
  const requestEdit = useCallback(() => {
@@ -124,41 +87,63 @@ export function FloatingButton({
124
87
  })
125
88
  }, [])
126
89
 
127
- if (!isFloatingDataLoaded || !projectMeta) {
90
+ if (!shouldShowEditButton || isHidden) {
128
91
  return null
129
92
  }
130
93
 
131
94
  return (
132
95
  <div
133
- className={`fixed bottom-10 flex items-center pr-0 ${shouldOffsetForChatbot ? 'z-[80]' : 'z-40'}`}
134
- style={{ right: floatingButtonRight }}
96
+ className="group fixed bottom-10 z-40 flex items-center"
97
+ style={{ right: FLOATING_BUTTON_PAGE_GAP }}
135
98
  >
99
+ {activeTooltip ? (
100
+ <div
101
+ role="tooltip"
102
+ className="pointer-events-none absolute right-0 bottom-[calc(100%+14px)] z-10 whitespace-nowrap rounded-lg bg-[#1f2329] px-3 py-2 text-sm text-white shadow-lg"
103
+ >
104
+ {activeTooltip === 'edit' ? i18n.editSite : i18n.hidePanel}
105
+ <span className="absolute -bottom-1 right-[22px] size-2 rotate-45 bg-[#1f2329]" />
106
+ </div>
107
+ ) : null}
136
108
  <div
137
- className={`${shouldShowFloatingControls ? 'relative z-[80] flex' : 'hidden'} items-center gap-1.5 rounded-full bg-white p-0.5 shadow-[0_4px_4px_rgba(31,35,41,0.06),0_6px_6px_rgba(31,35,41,0.04),0_8px_12px_rgba(31,35,41,0.04)]`}
109
+ className="relative flex items-center rounded-full bg-white p-0.5 shadow-[0_4px_4px_rgba(31,35,41,0.06),0_6px_6px_rgba(31,35,41,0.04),0_8px_12px_rgba(31,35,41,0.04)]"
138
110
  data-floating-controls="true"
139
111
  >
140
- {shouldShowEditButton ? (
141
- <button
142
- type="button"
143
- aria-label={i18n.editSite}
144
- title={i18n.editSite}
145
- className="flex size-12 cursor-pointer items-center justify-center rounded-full bg-transparent text-[#2b2f36] outline-none transition-colors hover:bg-[#1f2329]/5 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:bg-[#1f2329]/10"
146
- onClick={requestEdit}
147
- >
148
- <EditIcon />
149
- </button>
150
- ) : null}
151
-
152
- {showChatbotEntry ? (
153
- <ChatbotEntry
154
- chatbots={projectMeta.chatList}
155
- baseId={projectMeta.baseBindings[0]?.baseToken}
156
- className="shrink-0"
157
- fallbackName={chatbotFallbackName}
158
- onOpenChange={handleChatbotOpenChange}
159
- onSidebarWidthChange={setChatbotSidebarWidth}
112
+ <button
113
+ type="button"
114
+ aria-label={i18n.editSite}
115
+ className="group/edit flex size-12 cursor-pointer items-center justify-center rounded-full bg-transparent outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
116
+ onMouseEnter={() => setActiveTooltip('edit')}
117
+ onMouseLeave={() => setActiveTooltip(null)}
118
+ onFocus={() => setActiveTooltip('edit')}
119
+ onBlur={() => setActiveTooltip(null)}
120
+ onClick={requestEdit}
121
+ >
122
+ <img
123
+ src={editDefaultIcon}
124
+ alt=""
125
+ className="size-5 group-hover/edit:hidden group-focus-visible/edit:hidden"
160
126
  />
161
- ) : null}
127
+ <img
128
+ src={editActiveIcon}
129
+ alt=""
130
+ className="hidden size-5 group-hover/edit:block group-focus-visible/edit:block"
131
+ />
132
+ </button>
133
+ <button
134
+ type="button"
135
+ aria-label={i18n.hidePanel}
136
+ className="absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full bg-[#c8c9cc] text-[#646a73] opacity-0 transition-opacity group-hover:opacity-100 hover:bg-[#b7b9bd] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-[#1456f0]"
137
+ onMouseEnter={() => setActiveTooltip('hide')}
138
+ onMouseLeave={() => setActiveTooltip(null)}
139
+ onFocus={() => setActiveTooltip('hide')}
140
+ onBlur={() => setActiveTooltip(null)}
141
+ onClick={() => setIsHidden(true)}
142
+ >
143
+ <span aria-hidden="true" className="text-[14px] leading-none">
144
+ ×
145
+ </span>
146
+ </button>
162
147
  </div>
163
148
  </div>
164
149
  )
@@ -171,26 +156,4 @@ function openExternalUrl(url: string): void {
171
156
  window.open(url, '_blank', 'noopener,noreferrer')
172
157
  }
173
158
 
174
- /** 编辑应用图标。
175
- * @returns 铅笔图标节点。
176
- */
177
- function EditIcon() {
178
- return (
179
- <svg
180
- aria-hidden="true"
181
- width="20"
182
- height="20"
183
- viewBox="0 0 24 24"
184
- fill="none"
185
- stroke="currentColor"
186
- strokeWidth="1.8"
187
- strokeLinecap="round"
188
- strokeLinejoin="round"
189
- >
190
- <path d="M12 20h9" />
191
- <path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
192
- </svg>
193
- )
194
- }
195
-
196
159
  export default FloatingButton