evomeme 0.1.10-beta.1 → 0.1.10-beta.20

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 (143) hide show
  1. package/README.md +2 -0
  2. package/dist/canvas-public/canvas/actions.js +2 -1
  3. package/dist/canvas-public/canvas/chrome.js +2 -1
  4. package/dist/canvas-public/core/api.js +8 -3
  5. package/dist/canvas-public/core/layout.js +3 -3
  6. package/dist/canvas-public/elements/task-pipeline.js +4 -3
  7. package/dist/canvas-public/features/assets/official-assets.js +4 -3
  8. package/dist/canvas-public/features/editing/canvas-size.js +2 -2
  9. package/dist/canvas-public/features/editing/color-key.js +2 -0
  10. package/dist/canvas-public/features/editing/interactive-image-editing.js +9 -8
  11. package/dist/canvas-public/features/editing/layer-decomposition.js +2 -1
  12. package/dist/canvas-public/features/editing/manual-split.js +2 -1
  13. package/dist/canvas-public/features/editing/perfect-pixel.js +56 -93
  14. package/dist/canvas-public/features/editing/scroll-preview.js +3 -2
  15. package/dist/canvas-public/features/editing/smart-split.js +2 -1
  16. package/dist/canvas-public/features/editing/source-preview.js +2 -2
  17. package/dist/canvas-public/features/frame-editor/frame-editing.js +5 -5
  18. package/dist/canvas-public/features/frame-editor/preview.js +4 -2
  19. package/dist/canvas-public/features/generation/image.js +25 -25
  20. package/dist/canvas-public/features/map/tile-map-editor.js +7 -5
  21. package/dist/canvas-public/language/index.js +14 -2
  22. package/dist/canvas-public/map/export.js +2 -1
  23. package/dist/canvas-public/ui/feature-form.js +2 -1
  24. package/dist/canvas-public/ui/settings.js +2 -1
  25. package/dist/{canvas-public/workers/perfect-pixel-worker.js → image-workers/perfect-pixel/perfect-pixel-core.js} +3 -72
  26. package/dist/image-workers/perfect-pixel/perfect-pixel-worker.cjs +10 -0
  27. package/dist/index.js +13350 -7773
  28. package/dist/skills/en-US/evomeme-cloud-fs/SKILL.md +31 -4
  29. package/dist/skills/en-US/evomeme-cover/SKILL.md +9 -8
  30. package/dist/skills/en-US/evomeme-login/SKILL.md +5 -0
  31. package/dist/skills/en-US/evomeme-mcp/SKILL.md +71 -0
  32. package/dist/skills/en-US/evomeme-mcp/agents/openai.yaml +7 -0
  33. package/dist/skills/en-US/evomeme-mcp/references/troubleshooting.md +55 -0
  34. package/dist/skills/en-US/game-asset-align/SKILL.md +6 -4
  35. package/dist/skills/en-US/game-asset-align/references/blender-scene-pipeline.md +7 -17
  36. package/dist/skills/en-US/game-asset-align/references/canonical-package.md +1 -1
  37. package/dist/skills/en-US/game-asset-align/references/lightweight-review.md +7 -5
  38. package/dist/skills/en-US/game-asset-align/references/repair-policy.md +2 -2
  39. package/dist/skills/en-US/game-asset-align/references/tool-contract.md +47 -17
  40. package/dist/skills/en-US/game-asset-batch-generator/SKILL.md +117 -0
  41. package/dist/skills/en-US/game-asset-batch-generator/agents/openai.yaml +7 -0
  42. package/dist/skills/en-US/game-asset-image-generator/SKILL.md +53 -71
  43. package/dist/skills/en-US/game-asset-image-generator/references/background.md +3 -1
  44. package/dist/skills/en-US/game-asset-image-generator/references/static-game-assets.md +2 -2
  45. package/dist/skills/en-US/game-asset-mesh-generator/SKILL.md +35 -0
  46. package/dist/skills/en-US/game-asset-mesh-generator/agents/openai.yaml +7 -0
  47. package/dist/skills/en-US/game-asset-mesh-generator/references/animation-presets.md +313 -0
  48. package/dist/skills/en-US/game-asset-mesh-generator/references/generation-contracts.md +131 -0
  49. package/dist/skills/en-US/game-asset-mesh-generator/references/image-generator-workflows.md +65 -0
  50. package/dist/skills/en-US/game-asset-mesh-generator/references/postprocess-contracts.md +187 -0
  51. package/dist/skills/en-US/game-asset-mesh-generator/references/threejs-integration.md +113 -0
  52. package/dist/skills/en-US/game-asset-mesh-generator/references/tool-reference.md +67 -0
  53. package/dist/skills/en-US/game-audio/SKILL.md +55 -16
  54. package/dist/skills/en-US/game-audio/agents/openai.yaml +2 -2
  55. package/dist/skills/en-US/game-audio/references/bgm-cdn.md +7 -11
  56. package/dist/skills/en-US/game-audio/references/sfx-cdn.md +6 -4
  57. package/dist/skills/en-US/game-character-view-generator/SKILL.md +110 -0
  58. package/dist/skills/en-US/game-character-view-generator/agents/openai.yaml +7 -0
  59. package/dist/skills/en-US/game-image-layer-decomposer/SKILL.md +99 -0
  60. package/dist/skills/en-US/game-image-layer-decomposer/agents/openai.yaml +7 -0
  61. package/dist/skills/en-US/game-image-splitter/SKILL.md +114 -0
  62. package/dist/skills/en-US/game-image-splitter/agents/openai.yaml +7 -0
  63. package/dist/skills/en-US/game-perfect-pixel/SKILL.md +123 -0
  64. package/dist/skills/en-US/game-perfect-pixel/agents/openai.yaml +7 -0
  65. package/dist/skills/en-US/game-scroll-generator/SKILL.md +110 -0
  66. package/dist/skills/en-US/game-scroll-generator/agents/openai.yaml +7 -0
  67. package/dist/skills/en-US/game-tile-generator/SKILL.md +152 -0
  68. package/dist/skills/en-US/game-tile-generator/agents/openai.yaml +7 -0
  69. package/dist/skills/en-US/game-ui-generator/SKILL.md +82 -0
  70. package/dist/skills/en-US/game-ui-generator/agents/openai.yaml +7 -0
  71. package/dist/skills/en-US/game-ui-generator/references/component-patterns.md +65 -0
  72. package/dist/skills/en-US/game-ui-generator/references/runtime-integration.md +45 -0
  73. package/dist/skills/en-US/game-video-cycle/SKILL.md +98 -0
  74. package/dist/skills/en-US/game-video-cycle/agents/openai.yaml +7 -0
  75. package/dist/skills/en-US/game-video-generator/SKILL.md +78 -0
  76. package/dist/skills/en-US/game-video-generator/agents/openai.yaml +7 -0
  77. package/dist/skills/en-US/godot-3d-game/SKILL.md +4 -2
  78. package/dist/skills/en-US/godot-3d-game/references/alignment-gates.md +1 -1
  79. package/dist/skills/en-US/godot-3d-game/references/appearance-alignment.md +4 -2
  80. package/dist/skills/en-US/threejs-3d-game/SKILL.md +3 -3
  81. package/dist/skills/en-US/threejs-3d-game/references/engine-asset-pipeline.md +5 -3
  82. package/dist/skills/en-US/threejs-aaa-graphics-builder/SKILL.md +6 -6
  83. package/dist/skills/zh-CN/evomeme-cloud-fs/SKILL.md +32 -5
  84. package/dist/skills/zh-CN/evomeme-cover/SKILL.md +9 -8
  85. package/dist/skills/zh-CN/evomeme-login/SKILL.md +5 -0
  86. package/dist/skills/zh-CN/evomeme-mcp/SKILL.md +71 -0
  87. package/dist/skills/zh-CN/evomeme-mcp/agents/openai.yaml +7 -0
  88. package/dist/skills/zh-CN/evomeme-mcp/references/troubleshooting.md +55 -0
  89. package/dist/skills/zh-CN/game-asset-align/SKILL.md +6 -4
  90. package/dist/skills/zh-CN/game-asset-align/references/blender-scene-pipeline.md +7 -17
  91. package/dist/skills/zh-CN/game-asset-align/references/canonical-package.md +1 -1
  92. package/dist/skills/zh-CN/game-asset-align/references/lightweight-review.md +7 -5
  93. package/dist/skills/zh-CN/game-asset-align/references/repair-policy.md +2 -2
  94. package/dist/skills/zh-CN/game-asset-align/references/tool-contract.md +48 -18
  95. package/dist/skills/zh-CN/game-asset-batch-generator/SKILL.md +117 -0
  96. package/dist/skills/zh-CN/game-asset-batch-generator/agents/openai.yaml +7 -0
  97. package/dist/skills/zh-CN/game-asset-image-generator/SKILL.md +53 -71
  98. package/dist/skills/zh-CN/game-asset-image-generator/references/background.md +3 -1
  99. package/dist/skills/zh-CN/game-asset-image-generator/references/static-game-assets.md +2 -2
  100. package/dist/skills/zh-CN/game-asset-mesh-generator/SKILL.md +35 -0
  101. package/dist/skills/zh-CN/game-asset-mesh-generator/agents/openai.yaml +7 -0
  102. package/dist/skills/zh-CN/game-asset-mesh-generator/references/animation-presets.md +313 -0
  103. package/dist/skills/zh-CN/game-asset-mesh-generator/references/generation-contracts.md +131 -0
  104. package/dist/skills/zh-CN/game-asset-mesh-generator/references/image-generator-workflows.md +65 -0
  105. package/dist/skills/zh-CN/game-asset-mesh-generator/references/postprocess-contracts.md +187 -0
  106. package/dist/skills/zh-CN/game-asset-mesh-generator/references/threejs-integration.md +113 -0
  107. package/dist/skills/zh-CN/game-asset-mesh-generator/references/tool-reference.md +67 -0
  108. package/dist/skills/zh-CN/game-audio/SKILL.md +55 -16
  109. package/dist/skills/zh-CN/game-audio/agents/openai.yaml +2 -2
  110. package/dist/skills/zh-CN/game-audio/references/bgm-cdn.md +7 -11
  111. package/dist/skills/zh-CN/game-audio/references/sfx-cdn.md +6 -4
  112. package/dist/skills/zh-CN/game-character-view-generator/SKILL.md +110 -0
  113. package/dist/skills/zh-CN/game-character-view-generator/agents/openai.yaml +7 -0
  114. package/dist/skills/zh-CN/game-image-layer-decomposer/SKILL.md +99 -0
  115. package/dist/skills/zh-CN/game-image-layer-decomposer/agents/openai.yaml +7 -0
  116. package/dist/skills/zh-CN/game-image-splitter/SKILL.md +114 -0
  117. package/dist/skills/zh-CN/game-image-splitter/agents/openai.yaml +7 -0
  118. package/dist/skills/zh-CN/game-perfect-pixel/SKILL.md +123 -0
  119. package/dist/skills/zh-CN/game-perfect-pixel/agents/openai.yaml +7 -0
  120. package/dist/skills/zh-CN/game-scroll-generator/SKILL.md +110 -0
  121. package/dist/skills/zh-CN/game-scroll-generator/agents/openai.yaml +7 -0
  122. package/dist/skills/zh-CN/game-tile-generator/SKILL.md +151 -0
  123. package/dist/skills/zh-CN/game-tile-generator/agents/openai.yaml +7 -0
  124. package/dist/skills/zh-CN/game-ui-generator/SKILL.md +82 -0
  125. package/dist/skills/zh-CN/game-ui-generator/agents/openai.yaml +7 -0
  126. package/dist/skills/zh-CN/game-ui-generator/references/component-patterns.md +65 -0
  127. package/dist/skills/zh-CN/game-ui-generator/references/runtime-integration.md +45 -0
  128. package/dist/skills/zh-CN/game-video-cycle/SKILL.md +98 -0
  129. package/dist/skills/zh-CN/game-video-cycle/agents/openai.yaml +7 -0
  130. package/dist/skills/zh-CN/game-video-generator/SKILL.md +78 -0
  131. package/dist/skills/zh-CN/game-video-generator/agents/openai.yaml +7 -0
  132. package/dist/skills/zh-CN/godot-3d-game/SKILL.md +4 -2
  133. package/dist/skills/zh-CN/godot-3d-game/references/alignment-gates.md +1 -1
  134. package/dist/skills/zh-CN/godot-3d-game/references/appearance-alignment.md +4 -2
  135. package/dist/skills/zh-CN/threejs-3d-game/SKILL.md +3 -3
  136. package/dist/skills/zh-CN/threejs-3d-game/references/engine-asset-pipeline.md +5 -3
  137. package/dist/skills/zh-CN/threejs-aaa-graphics-builder/SKILL.md +6 -6
  138. package/dist/stage-public/app.js +1 -1
  139. package/dist/stage-public/delivery.js +2 -2
  140. package/dist/stage-public/review-app.js +7 -7
  141. package/dist/stage-public/review-runtime.js +1 -1
  142. package/dist/stage-public/scene-runtime.js +11 -11
  143. package/package.json +4 -2
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  npm 包名:`evomeme`。用 `npx` 安装到 AI 工具后,即可在对话里做游戏、生图、提交草稿。
7
7
 
8
+ 本地 3D 资产工作流默认使用 `evomeme asset`、`evomeme scene export` 和 `evomeme stage`,详见 [资产 CLI 使用说明](../docs/asset-cli.md)。IDE 插件安装不等于全局安装 CLI:使用已发布且支持这些命令的 `npx -y evomeme@latest`,或本地构建后的 `node dist/index.js`;先执行 `asset --help` 检查能力。这 12 个资产/Stage MCP 入口已移除,对应操作使用 CLI;其余 20 个 MCP 工具继续可用。
9
+
8
10
  ---
9
11
 
10
12
  ## 一、这是什么?
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../language/index.js'
1
2
  import { removeFeatureReferences } from '../core/feature-state.js'
2
3
  import { t } from '../language/index.js'
3
4
  import { api } from '../core/api.js'
@@ -52,7 +53,7 @@ export function createCanvasActions({
52
53
  body: '{}',
53
54
  })
54
55
  const assets = Array.isArray(result.assets) ? result.assets : []
55
- if (!assets.length) throw new Error(t("canvas.actions.text001"))
56
+ if (!assets.length) throw new Error(errorT("canvas.actions.text001"))
56
57
  const ids = assets.map((asset) => asset.id)
57
58
  const columns = Math.min(5, Math.max(1, Math.ceil(Math.sqrt(assets.length))))
58
59
  assets.forEach((asset, index) => {
@@ -1,10 +1,11 @@
1
+ import { errorText } from '../language/index.js'
1
2
  import { state } from '../core/state.js'
2
3
 
3
4
  // Form surfaces subscribe to feature state; chrome does not mirror business DOM.
4
5
  export function createChromeController({ elements, renderInspector }) {
5
6
  return { renderChrome() {
6
7
  elements.bannerError.hidden = !state.error
7
- elements.bannerError.textContent = state.error || ''
8
+ elements.bannerError.textContent = errorText(state.error) || ''
8
9
  renderInspector()
9
10
  } }
10
11
  }
@@ -1,4 +1,4 @@
1
- import { t } from '../language/index.js'
1
+ import { t, errorText } from '../language/index.js'
2
2
  export async function api(path, options) {
3
3
  const headers = new Headers(options?.headers)
4
4
  if (options?.method === 'POST') headers.set('Prefer', 'respond-async')
@@ -9,7 +9,8 @@ export async function api(path, options) {
9
9
  const message = Array.isArray(detail)
10
10
  ? detail.map((d) => d.msg || JSON.stringify(d)).join('; ')
11
11
  : detail || data.message || res.statusText || t("core.api.text001")
12
- const error = new Error(message)
12
+ const error = new Error(errorText(data.error?.message || message))
13
+ error.errorInfo = data.error
13
14
  error.status = res.status
14
15
  error.data = data
15
16
  throw error
@@ -18,7 +19,11 @@ export async function api(path, options) {
18
19
  for (;;) {
19
20
  const task = await api(`/api/tasks/${encodeURIComponent(data.task_id)}`)
20
21
  if (task.status === 'SUCCEEDED') return task.result || {}
21
- if (['FAILED', 'NEEDS_ATTENTION'].includes(task.status)) throw new Error(task.error || t("core.api.text002"))
22
+ if (['FAILED', 'NEEDS_ATTENTION'].includes(task.status)) {
23
+ const error = new Error(errorText(task.errorInfo?.message || task.error || t("core.api.text002")))
24
+ error.errorInfo = task.errorInfo
25
+ throw error
26
+ }
22
27
  // INTERRUPTED is recoverable; the scheduler continues polling the saved backend job.
23
28
  await new Promise(resolve => setTimeout(resolve, 750))
24
29
  }
@@ -1,4 +1,4 @@
1
- import { t } from '../language/index.js'
1
+ import { t, errorT } from '../language/index.js'
2
2
  import { api } from './api.js'
3
3
  import { state } from './state.js'
4
4
 
@@ -78,7 +78,7 @@ export function createLayoutController({
78
78
  const request = saveQueue.then(async () => {
79
79
  try {
80
80
  if (epoch !== saveEpoch) {
81
- const staleError = new Error(t("core.layout.text001"))
81
+ const staleError = new Error(errorT("core.layout.text001"))
82
82
  staleError.status = 409
83
83
  throw staleError
84
84
  }
@@ -102,7 +102,7 @@ export function createLayoutController({
102
102
  const firstFailureInEpoch = epoch === saveEpoch
103
103
  if (firstFailureInEpoch) saveEpoch += 1
104
104
  if (error?.status === 409 && firstFailureInEpoch) {
105
- state.error = t("core.layout.text001")
105
+ state.error = errorT("core.layout.text001")
106
106
  await onConflict().catch(() => undefined)
107
107
  }
108
108
  throw error
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../language/index.js'
1
2
  import { t } from '../language/index.js'
2
3
  import { api } from '../core/api.js'
3
4
  import { state } from '../core/state.js'
@@ -129,7 +130,7 @@ export function createCanvasTaskPipeline({
129
130
  function insertPendingItem(source, pending, placement, options = {}) {
130
131
  if (placement === 'replace') {
131
132
  if (!replaceGenerationInputWithPending(source?.id, pending, options)) {
132
- throw new Error(t("elements.taskPipeline.text002"))
133
+ throw new Error(errorT("elements.taskPipeline.text002"))
133
134
  }
134
135
  return
135
136
  }
@@ -188,8 +189,8 @@ export function createCanvasTaskPipeline({
188
189
  onFailure,
189
190
  onSettled,
190
191
  }) {
191
- if (!source?.id) throw new Error(t("elements.taskPipeline.text003"))
192
- if (typeof execute !== 'function') throw new Error(t("elements.taskPipeline.text004"))
192
+ if (!source?.id) throw new Error(errorT("elements.taskPipeline.text003"))
193
+ if (typeof execute !== 'function') throw new Error(errorT("elements.taskPipeline.text004"))
193
194
 
194
195
  const operationId = wait.id || createCanvasClientId('pending')
195
196
  const pending = createPendingItem(source, operationId, wait, placement)
@@ -1,3 +1,4 @@
1
+ import { errorT, errorText } from '../../language/index.js'
1
2
  import { ensureFeatureState, peekFeatureState } from '../../core/feature-state.js'
2
3
  import { t } from '../../language/index.js'
3
4
  import { api } from '../../core/api.js'
@@ -312,7 +313,7 @@ function createOfficialAssetsTool(context) {
312
313
  )
313
314
  const catalog = await response.json()
314
315
  if (!Array.isArray(catalog?.categories))
315
- throw new Error(t('features.assets.officialAssets.text006'))
316
+ throw new Error(errorT('features.assets.officialAssets.text006'))
316
317
  officialAssetCatalog = catalog
317
318
  return catalog
318
319
  })
@@ -371,7 +372,7 @@ function createOfficialAssetsTool(context) {
371
372
  body: JSON.stringify({ files, place: false }),
372
373
  })
373
374
  const imported = Array.isArray(result.images) ? result.images : []
374
- if (!imported.length) throw new Error(t('features.assets.officialAssets.text008'))
375
+ if (!imported.length) throw new Error(errorT('features.assets.officialAssets.text008'))
375
376
  importedIds = imported.map((asset) => asset.id).filter(Boolean)
376
377
 
377
378
  const resultItems = layoutCanvasElementsAtSlot(
@@ -499,7 +500,7 @@ function createOfficialAssetsTool(context) {
499
500
  const footer = el('div', 'official-assets-footer')
500
501
  const feedback = el('div', `official-assets-feedback${ds.error ? ' is-error' : ''}`)
501
502
  feedback.textContent =
502
- ds.error ||
503
+ errorText(ds.error) ||
503
504
  ds.status ||
504
505
  `${t('features.assets.officialAssets.text017')}${ds.selectedIds.size}${t('features.assets.officialAssets.text017.part1')}`
505
506
  const actions = el('div', 'official-assets-actions')
@@ -1,4 +1,4 @@
1
- import { t } from '../../language/index.js'
1
+ import { t, errorT } from '../../language/index.js'
2
2
  import {
3
3
  ensureFeatureState,
4
4
  notifyFeatureState,
@@ -306,7 +306,7 @@ export class ImageCanvasSizeEditor {
306
306
  const image = new Image()
307
307
  await new Promise((resolve, reject) => {
308
308
  image.onload = resolve
309
- image.onerror = () => reject(new Error(t('features.editing.imageCanvasSizeEditor.text001')))
309
+ image.onerror = () => reject(new Error(errorT('features.editing.imageCanvasSizeEditor.text001')))
310
310
  image.src = url
311
311
  })
312
312
  this.image = image
@@ -129,6 +129,7 @@ function createColorKeyTool(context) {
129
129
  const canvas = findNodeEl(editorId)?.querySelector('[data-role="color-key-preview"]')
130
130
  if (!ds || !canvas || !previewUrl) return
131
131
  const image = new Image()
132
+ image.crossOrigin = 'anonymous'
132
133
  image.onload = () => {
133
134
  if (!canvas.isConnected || peekFeatureState(editorId)?.previewUrl !== previewUrl) return
134
135
  const scale = Math.min(1, 900 / Math.max(image.naturalWidth, image.naturalHeight))
@@ -161,6 +162,7 @@ function createColorKeyTool(context) {
161
162
  return
162
163
  }
163
164
  const image = new Image()
165
+ image.crossOrigin = 'anonymous'
164
166
  image.onload = () => {
165
167
  const current = peekFeatureState(editorId)
166
168
  const currentCanvas = findNodeEl(editorId)?.querySelector('[data-role="color-key-preview"]')
@@ -1,3 +1,4 @@
1
+ import { errorT, errorText } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  import { api } from '../../core/api.js'
3
4
  import { state } from '../../core/state.js'
@@ -400,7 +401,7 @@ function createInteractiveImageEditing(services) {
400
401
  async task(item, params) {
401
402
  const referenceImageData = await views.get(item.id)?.exportImage()
402
403
  if (!referenceImageData?.startsWith('data:image/'))
403
- throw new Error(t('features.editing.interactiveImageEditing.text036'))
404
+ throw new Error(errorT('features.editing.interactiveImageEditing.text036'))
404
405
  params.referenceImageData = referenceImageData
405
406
  return {
406
407
  endpoint: '/api/generate',
@@ -556,7 +557,7 @@ function mountDrawingView(host, item, services) {
556
557
  interactiveEditorExportRequest = null
557
558
  if (exportRequest) {
558
559
  clearTimeout(exportRequest.timeout)
559
- exportRequest.reject(new Error(t('features.editing.interactiveImageEditing.text024')))
560
+ exportRequest.reject(new Error(errorT('features.editing.interactiveImageEditing.text024')))
560
561
  }
561
562
  if (editor) {
562
563
  try {
@@ -572,7 +573,7 @@ function mountDrawingView(host, item, services) {
572
573
  try {
573
574
  const PainterroEditor = window.Painterro
574
575
  if (typeof PainterroEditor !== 'function')
575
- throw new Error(t('features.editing.interactiveImageEditing.text027'))
576
+ throw new Error(errorT('features.editing.interactiveImageEditing.text027'))
576
577
  if (disposed) return
577
578
  let editor = null
578
579
  let loadTimeout = null
@@ -616,7 +617,7 @@ function mountDrawingView(host, item, services) {
616
617
  },
617
618
  onImageLoaded: () => finish(resolve),
618
619
  onImageFailedOpen: () =>
619
- finish(reject, new Error(t('features.editing.interactiveImageEditing.text028'))),
620
+ finish(reject, new Error(errorT('features.editing.interactiveImageEditing.text028'))),
620
621
  saveHandler: (image, done) => {
621
622
  const request = interactiveEditorExportRequest
622
623
  if (!request || request.editor !== editor) {
@@ -638,7 +639,7 @@ function mountDrawingView(host, item, services) {
638
639
  interactiveEditorInteractionCleanup = installInteractiveEditorInteractions(editor)
639
640
  editor.show(params.referenceImageData || imageUrl(imageId))
640
641
  loadTimeout = setTimeout(
641
- () => reject(new Error(t('features.editing.interactiveImageEditing.text029'))),
642
+ () => reject(new Error(errorT('features.editing.interactiveImageEditing.text029'))),
642
643
  10_000,
643
644
  )
644
645
  })
@@ -654,7 +655,7 @@ function mountDrawingView(host, item, services) {
654
655
  params.ready = false
655
656
  notifyFeatureState(item.id)
656
657
  elements.interactiveEditStatus.hidden = false
657
- elements.interactiveEditStatus.textContent = String(error.message || error)
658
+ elements.interactiveEditStatus.textContent = errorText(error)
658
659
  }
659
660
  }
660
661
 
@@ -893,13 +894,13 @@ function mountDrawingView(host, item, services) {
893
894
 
894
895
  function exportInteractiveEditor(editor) {
895
896
  if (interactiveEditorExportRequest) {
896
- return Promise.reject(new Error(t('features.editing.interactiveImageEditing.text033')))
897
+ return Promise.reject(new Error(errorT('features.editing.interactiveImageEditing.text033')))
897
898
  }
898
899
  return new Promise((resolve, reject) => {
899
900
  const timeout = setTimeout(() => {
900
901
  if (interactiveEditorExportRequest?.editor !== editor) return
901
902
  interactiveEditorExportRequest = null
902
- reject(new Error(t('features.editing.interactiveImageEditing.text034')))
903
+ reject(new Error(errorT('features.editing.interactiveImageEditing.text034')))
903
904
  }, 10_000)
904
905
  interactiveEditorExportRequest = { editor, resolve, reject, timeout }
905
906
  try {
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  export default {
3
4
  render: (ui) => ui.prompt('prompt'),
@@ -37,7 +38,7 @@ export default {
37
38
  },
38
39
  checkResult(result) {
39
40
  if (!result.images?.length)
40
- throw new Error(t('features.editing.interactiveImageEditing.text047'))
41
+ throw new Error(errorT('features.editing.interactiveImageEditing.text047'))
41
42
  },
42
43
  }
43
44
  },
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  import { ensureFeatureState, subscribeFeatureState } from '../../core/feature-state.js'
3
4
  import { el } from '../../ui/controls.js'
@@ -103,7 +104,7 @@ export default {
103
104
  checkResult(result) {
104
105
  if ((result.images || []).length !== p.rows * p.columns)
105
106
  throw new Error(
106
- t('features.editing.imageSplitting.text012') +
107
+ errorT('features.editing.imageSplitting.text012') +
107
108
  (result.images || []).length +
108
109
  t('features.editing.imageSplitting.text012.part1') +
109
110
  p.rows * p.columns,
@@ -3,7 +3,7 @@ import {
3
3
  peekFeatureState,
4
4
  notifyFeatureState,
5
5
  } from '../../core/feature-state.js'
6
- import { t, translateWorkerError } from '../../language/index.js'
6
+ import { t, errorT, errorText } from '../../language/index.js'
7
7
  import { api } from '../../core/api.js'
8
8
  import { state } from '../../core/state.js'
9
9
  import { el } from '../../ui/controls.js'
@@ -85,7 +85,7 @@ export default {
85
85
  },
86
86
  ],
87
87
  runtimeDefaults: () => ({
88
- worker: null,
88
+ previewController: null,
89
89
  processTimer: null,
90
90
  layoutSaveTimer: null,
91
91
  requestId: 0,
@@ -103,21 +103,14 @@ export default {
103
103
  function createPerfectPixelTool(context) {
104
104
  const { findNodeEl, formatPerfectPixelSourceArea, imageUrl, saveLayout } = context
105
105
 
106
- function workerErrorText(message) {
107
- if (message?.messageKey === 'workers.imageLoadFailed') {
108
- return `${t('workers.imageLoadFailed')}${message.messageVariables?.status ?? ''}`
109
- }
110
- return translateWorkerError(message)
111
- }
112
-
113
106
  function perfectPixelSettings(ds) {
114
107
  return {
115
- autoGrid: ds.autoGrid,
116
- pixelSize: ds.pixelSize,
117
- sampleMethod: ds.sampleMethod,
118
- exportScale: ds.exportScale,
119
- alphaMode: ds.alphaMode,
120
- alphaThreshold: ds.alphaThreshold,
108
+ auto_grid: ds.autoGrid,
109
+ pixel_size: ds.pixelSize,
110
+ sample_method: ds.sampleMethod,
111
+ export_scale: ds.exportScale,
112
+ alpha_mode: ds.alphaMode,
113
+ alpha_threshold: ds.alphaThreshold,
121
114
  }
122
115
  }
123
116
 
@@ -132,7 +125,7 @@ function createPerfectPixelTool(context) {
132
125
  if (status) {
133
126
  status.classList.toggle('is-error', Boolean(ds.error))
134
127
  if (ds.error) {
135
- status.textContent = ds.error
128
+ status.textContent = errorText(ds.error)
136
129
  } else if (ds.resultMeta) {
137
130
  const meta = ds.resultMeta
138
131
  const confidence = meta.automatic
@@ -157,78 +150,60 @@ function createPerfectPixelTool(context) {
157
150
  }, 220)
158
151
  }
159
152
 
160
- function startPerfectPixelWorker(editorId, sourceId, requestId) {
153
+ async function startPerfectPixelPreview(editorId, sourceId, requestId) {
161
154
  const ds = peekFeatureState(editorId)
162
155
  if (!ds || ds.requestId !== requestId) return
163
- ds.worker?.terminate()
164
- const worker = new Worker('/workers/perfect-pixel-worker.js')
165
- ds.worker = worker
166
- worker.addEventListener('message', (event) => {
167
- const current = peekFeatureState(editorId)
168
- if (!current || current.worker !== worker || current.requestId !== requestId) return
169
- const message = event.data || {}
170
- if (message.type === 'ready') {
171
- worker.postMessage({
172
- type: 'process',
173
- requestId,
174
- settings: perfectPixelSettings(current),
175
- })
176
- return
156
+ const controller = new AbortController()
157
+ ds.previewController = controller
158
+ try {
159
+ const response = await fetch('/api/perfect-pixel/preview', {
160
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({ id: sourceId, ...perfectPixelSettings(ds) }),
162
+ signal: controller.signal,
163
+ })
164
+ if (!response.ok) {
165
+ const error = await response.json().catch(() => ({}))
166
+ throw new Error(error.detail || response.statusText)
177
167
  }
178
- if (message.type === 'result' && message.requestId === requestId && message.blob) {
179
- worker.terminate()
180
- current.worker = null
181
- if (current.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(current.previewUrl)
182
- current.resultBlob = message.blob
183
- current.previewUrl = URL.createObjectURL(message.blob)
184
- current.resultMeta = {
185
- width: message.width,
186
- height: message.height,
187
- gridWidth: message.gridWidth,
188
- gridHeight: message.gridHeight,
189
- pixelSizeX: message.pixelSizeX,
190
- pixelSizeY: message.pixelSizeY,
191
- confidence: message.confidence,
192
- automatic: message.automatic,
193
- }
194
- current.pending = false
195
- current.error = ''
196
- const image = findNodeEl(editorId)?.querySelector('[data-role="perfect-pixel-preview"]')
197
- if (image) image.src = current.previewUrl
198
- updatePerfectPixelEditorUi(editorId)
199
- return
200
- }
201
- if (message.type === 'error') {
202
- worker.terminate()
203
- current.worker = null
204
- current.pending = false
205
- current.resultBlob = null
206
- current.resultMeta = null
207
- current.error = `${t('features.editing.perfectPixel.text007')}${workerErrorText(message)}`
208
- updatePerfectPixelEditorUi(editorId)
168
+ const meta = JSON.parse(response.headers.get('X-Perfect-Pixel-Metadata'))
169
+ const blob = await response.blob()
170
+ const current = peekFeatureState(editorId)
171
+ if (!current || current.requestId !== requestId || controller.signal.aborted) return
172
+ if (current.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(current.previewUrl)
173
+ current.resultBlob = blob
174
+ current.previewUrl = URL.createObjectURL(blob)
175
+ current.resultMeta = {
176
+ width: meta.width, height: meta.height,
177
+ gridWidth: meta.grid_width, gridHeight: meta.grid_height,
178
+ pixelSizeX: meta.pixel_size_x, pixelSizeY: meta.pixel_size_y,
179
+ confidence: meta.confidence, automatic: meta.auto_grid,
209
180
  }
210
- })
211
- worker.addEventListener('error', () => {
181
+ current.error = ''
182
+ const image = findNodeEl(editorId)?.querySelector('[data-role="perfect-pixel-preview"]')
183
+ if (image) image.src = current.previewUrl
184
+ } catch (error) {
212
185
  const current = peekFeatureState(editorId)
213
- if (!current || current.worker !== worker || current.requestId !== requestId) return
214
- worker.terminate()
215
- current.worker = null
216
- current.pending = false
186
+ if (!current || current.requestId !== requestId || controller.signal.aborted) return
217
187
  current.resultBlob = null
218
188
  current.resultMeta = null
219
- current.error = t('features.editing.perfectPixel.text008')
220
- updatePerfectPixelEditorUi(editorId)
221
- })
222
- worker.postMessage({ type: 'init', requestId, url: imageUrl(sourceId) })
189
+ current.error = errorT('features.editing.perfectPixel.text007') + error.message
190
+ } finally {
191
+ const current = peekFeatureState(editorId)
192
+ if (current?.requestId === requestId && !controller.signal.aborted) {
193
+ current.previewController = null
194
+ current.pending = false
195
+ updatePerfectPixelEditorUi(editorId)
196
+ }
197
+ }
223
198
  }
224
199
 
225
- function schedulePerfectPixelProcess(editorId, delay = 100) {
200
+ function schedulePerfectPixelProcess(editorId, delay = 200) {
226
201
  const ds = peekFeatureState(editorId)
227
202
  const item = state.layoutItems.find((candidate) => candidate.id === editorId)
228
203
  if (!ds || !item?.sourceId || ds.busy) return
229
204
  clearTimeout(ds.processTimer)
230
- ds.worker?.terminate()
231
- ds.worker = null
205
+ ds.previewController?.abort()
206
+ ds.previewController = null
232
207
  ds.requestId += 1
233
208
  const requestId = ds.requestId
234
209
  if (ds.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(ds.previewUrl)
@@ -240,7 +215,7 @@ function createPerfectPixelTool(context) {
240
215
  updatePerfectPixelEditorUi(editorId)
241
216
  ds.processTimer = setTimeout(() => {
242
217
  ds.processTimer = null
243
- startPerfectPixelWorker(editorId, item.sourceId, requestId)
218
+ void startPerfectPixelPreview(editorId, item.sourceId, requestId)
244
219
  }, delay)
245
220
  }
246
221
 
@@ -266,25 +241,12 @@ function createPerfectPixelTool(context) {
266
241
  if (!ds) return
267
242
  clearTimeout(ds.processTimer)
268
243
  clearTimeout(ds.layoutSaveTimer)
269
- ds.worker?.terminate()
244
+ ds.previewController?.abort()
270
245
  if (ds.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(ds.previewUrl)
271
246
  }
272
247
 
273
248
  function task(item, ds) {
274
- const blob = ds.resultBlob
275
- const settings = {
276
- source_id: item.sourceId,
277
- auto_grid: String(ds.autoGrid),
278
- grid_width: String(ds.resultMeta.gridWidth),
279
- grid_height: String(ds.resultMeta.gridHeight),
280
- pixel_size: String(ds.pixelSize),
281
- pixel_size_x: String(ds.resultMeta.pixelSizeX),
282
- pixel_size_y: String(ds.resultMeta.pixelSizeY),
283
- sample_method: ds.sampleMethod,
284
- export_scale: String(ds.exportScale),
285
- alpha_mode: ds.alphaMode,
286
- alpha_threshold: String(ds.alphaThreshold),
287
- }
249
+ const settings = { id: item.sourceId, ...perfectPixelSettings(ds) }
288
250
  return {
289
251
  wait: {
290
252
  name: t('features.editing.perfectPixel.text009'),
@@ -294,8 +256,9 @@ function createPerfectPixelTool(context) {
294
256
  },
295
257
  request: (operationId) =>
296
258
  api(
297
- `/api/perfect-pixel/apply-browser?${new URLSearchParams({ ...settings, client_operation_id: operationId })}`,
298
- { method: 'POST', headers: { 'Content-Type': 'image/png' }, body: blob },
259
+ '/api/perfect-pixel/apply',
260
+ { method: 'POST', headers: { 'Content-Type': 'application/json' },
261
+ body: JSON.stringify({ ...settings, client_operation_id: operationId }) },
299
262
  ),
300
263
  }
301
264
  }
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  import { state } from '../../core/state.js'
3
4
  import {
@@ -10,7 +11,7 @@ import {
10
11
  export async function applyScrollPreviews(updates, services) {
11
12
  const changes = updates.map(({ source, params }) => {
12
13
  const config = normalizeScrollPreviewConfig(params)
13
- if (!config) throw new Error(t('features.editing.scrollPreview.invalidParameters'))
14
+ if (!config) throw new Error(errorT('features.editing.scrollPreview.invalidParameters'))
14
15
  return {
15
16
  source,
16
17
  config,
@@ -120,7 +121,7 @@ export default {
120
121
  submitLabel: 'features.editing.scrollPreview.apply',
121
122
  async execute(params, item, services) {
122
123
  const source = state.layoutItems.find((entry) => entry.id === item.sourceId)
123
- if (!source) throw new Error(t('features.editing.scrollPreview.sourceMissing'))
124
+ if (!source) throw new Error(errorT('features.editing.scrollPreview.sourceMissing'))
124
125
  await applyScrollPreview(source, params, services)
125
126
  await services.replaceLocal(item, [])
126
127
  services.setSelection([source.id])
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  export default {
3
4
  render: (ui) => ui.stack(ui.field('padToSameSize'), ui.field('anchor')),
@@ -41,7 +42,7 @@ export default {
41
42
  body: { id: item.sourceId, anchor: p.anchor, pad_to_same_size: p.padToSameSize },
42
43
  checkResult(result) {
43
44
  if ((result.images || []).length < 2)
44
- throw new Error(t('features.editing.imageSplitting.text005'))
45
+ throw new Error(errorT('features.editing.imageSplitting.text005'))
45
46
  },
46
47
  }
47
48
  },
@@ -1,11 +1,11 @@
1
- import { t } from '../../language/index.js'
1
+ import { t, errorT } from '../../language/index.js'
2
2
  import { state } from '../../core/state.js'
3
3
 
4
4
  export async function sourceImageParameters(source, services) {
5
5
  const dimensions = await new Promise((resolve, reject) => {
6
6
  const image = new Image()
7
7
  image.onload = () => resolve({ w: image.naturalWidth, h: image.naturalHeight })
8
- image.onerror = () => reject(new Error(t('features.editing.imageProcessing.text009')))
8
+ image.onerror = () => reject(new Error(errorT('features.editing.imageProcessing.text009')))
9
9
  image.src = services.imageUrl(source.id)
10
10
  })
11
11
  state.imageDims[source.id] = dimensions
@@ -1,6 +1,6 @@
1
1
  import { frameSheetPreviewMeta } from '../../elements/image.js'
2
2
  import { ensureFeatureState, subscribeFeatureState } from '../../core/feature-state.js'
3
- import { t } from '../../language/index.js'
3
+ import { t, errorT, errorText } from '../../language/index.js'
4
4
  import { state } from '../../core/state.js'
5
5
  import { normalizeFrameEditor, frameEditorParentIds } from './frame-model.js'
6
6
 
@@ -547,7 +547,7 @@ function createFrameEditingTool(context) {
547
547
  const assetId = String(reference.id || '').trim()
548
548
  const image = state.images.find((candidate) => candidate.id === assetId)
549
549
  if (!image || image.kind !== 'image') {
550
- item.error = t('features.frameEditor.frameEditing.text005')
550
+ item.error = errorT('features.frameEditor.frameEditing.text005')
551
551
  renderCanvasItem(editorId)
552
552
  return
553
553
  }
@@ -555,7 +555,7 @@ function createFrameEditingTool(context) {
555
555
  if (editor.rows < 64) item.rows = editor.rows + 1
556
556
  else if (editor.columns < 64) item.columns = editor.columns + 1
557
557
  else {
558
- item.error = t('features.frameEditor.frameEditing.text006')
558
+ item.error = errorT('features.frameEditor.frameEditing.text006')
559
559
  renderCanvasItem(editorId)
560
560
  return
561
561
  }
@@ -566,7 +566,7 @@ function createFrameEditingTool(context) {
566
566
  item.error = null
567
567
  await commitFrameEditorChange(editorId)
568
568
  } catch {
569
- item.error = t('features.frameEditor.frameEditing.text007')
569
+ item.error = errorT('features.frameEditor.frameEditing.text007')
570
570
  renderCanvasItem(editorId)
571
571
  }
572
572
  }
@@ -679,7 +679,7 @@ function createFrameEditingTool(context) {
679
679
 
680
680
  const hint = document.createElement('p')
681
681
  hint.className = `frame-editor-hint${node.error ? ' is-error' : ''}`
682
- hint.textContent = node.error || t('features.frameEditor.frameEditing.text018')
682
+ hint.textContent = errorText(node.error) || t('features.frameEditor.frameEditing.text018')
683
683
 
684
684
  workspace.addEventListener('pointerdown', (event) => event.stopPropagation())
685
685
  container.append(workspace, hint)
@@ -1,3 +1,4 @@
1
+ import { errorT } from '../../language/index.js'
1
2
  import { t } from '../../language/index.js'
2
3
  import { state } from '../../core/state.js'
3
4
  import {
@@ -10,9 +11,10 @@ import {
10
11
 
11
12
  export async function detectFramePreviewCount(imageId, rows, columns, imageUrl) {
12
13
  const image = new Image()
14
+ image.crossOrigin = 'anonymous'
13
15
  await new Promise((resolve, reject) => {
14
16
  image.onload = resolve
15
- image.onerror = () => reject(new Error(t('features.frameEditor.frameEditingDialogs.text007')))
17
+ image.onerror = () => reject(new Error(errorT('features.frameEditor.frameEditingDialogs.text007')))
16
18
  image.src = imageUrl(imageId)
17
19
  })
18
20
  const canvas = document.createElement('canvas')
@@ -140,7 +142,7 @@ export default {
140
142
  submitLabel: 'html.text072',
141
143
  async execute(p, item, services) {
142
144
  const source = state.layoutItems.find((entry) => entry.id === item.sourceId)
143
- if (!source) throw new Error(t('inspector.framePreview.text011'))
145
+ if (!source) throw new Error(errorT('inspector.framePreview.text011'))
144
146
  await applyFramePreview(source, p, services)
145
147
  await services.replaceLocal(item, [])
146
148
  services.setSelection([source.id])