Add image-designer skill, align billing to DeepSeek ×1, and enrich Plaza demo.
Introduce AI image generation with chat shortcut and agent API, improve MindSpace chat-to-page save resolution, and seed Plaza covers with production deploy scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -49,12 +49,12 @@ H5_ADMIN_PASSWORD=change-me-admin
|
||||
H5_ACCESS_PASSWORD=change-me
|
||||
|
||||
# 计费(Phase 2,金额单位均为人民币分)
|
||||
# 默认:DeepSeek V4 Flash 公开价 × 3(随 H5_USD_CNY_RATE 换算);可用下方变量覆盖
|
||||
# 默认:DeepSeek V4 Flash 公开价 × 1(随 H5_USD_CNY_RATE 换算);可用下方变量覆盖
|
||||
# H5_USE_BACKEND_COST=0
|
||||
# H5_USD_CNY_RATE=7.2
|
||||
# 汇率 7.2 时默认约:输入 0.302 分/1k(¥0.00302/1k)、输出 0.605 分/1k(¥0.00605/1k)
|
||||
# H5_BILL_INPUT_CENTS_PER_1K=0.302
|
||||
# H5_BILL_OUTPUT_CENTS_PER_1K=0.605
|
||||
# 汇率 7.2 时默认约:输入 0.101 分/1k(¥0.00101/1k)、输出 0.202 分/1k(¥0.00202/1k)
|
||||
# H5_BILL_INPUT_CENTS_PER_1K=0.101
|
||||
# H5_BILL_OUTPUT_CENTS_PER_1K=0.202
|
||||
# H5_MIN_BILL_CENTS=1
|
||||
# 仅调试:H5_USE_BACKEND_COST=1 + H5_USD_CNY_RATE=7.2(上游 USD × 汇率 → 人民币分)
|
||||
|
||||
@@ -112,6 +112,17 @@ H5_ACCESS_PASSWORD=change-me
|
||||
# H5_ASR_MAX_BYTES=5242880
|
||||
# H5_ASR_TIMEOUT_MS=45000
|
||||
|
||||
# AI 图片生成(image-designer 技能;OpenAI 兼容 /v1/images/generations)
|
||||
# H5_IMAGE_GEN_ENABLED=1
|
||||
# H5_IMAGE_GEN_API_URL=https://api.openai.com/v1/images/generations
|
||||
# H5_IMAGE_GEN_API_KEY=sk-...
|
||||
# H5_IMAGE_GEN_MODEL=dall-e-3
|
||||
# H5_IMAGE_GEN_DEFAULT_SIZE=1024x1024
|
||||
# H5_IMAGE_GEN_DEFAULT_STYLE=vivid
|
||||
# H5_IMAGE_GEN_DEFAULT_QUALITY=standard
|
||||
# H5_IMAGE_GEN_BILL_CENTS=30
|
||||
# 未配置 H5_IMAGE_GEN_API_* 时,回退使用管理后台已选 LLM 的 API Key(需支持 images/generations)
|
||||
|
||||
# 前端构建时注入(Vite,需 VITE_ 前缀)
|
||||
# 工作目录:新建会话时使用,必填
|
||||
VITE_TKMIND_WORKING_DIR=/Users/john/PycharmProjects/tkmind
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// DeepSeek V4 Flash 公开价($/1M tokens,cache-miss 输入);见 api-docs.deepseek.com
|
||||
export const DEEPSEEK_FLASH_INPUT_USD_PER_1M = 0.14;
|
||||
export const DEEPSEEK_FLASH_OUTPUT_USD_PER_1M = 0.28;
|
||||
export const DEEPSEEK_BILLING_MARKUP = 3;
|
||||
export const DEEPSEEK_BILLING_MARKUP = 1;
|
||||
|
||||
function deepSeekFlashCentsPer1k(usdPer1M, usdCnyRate) {
|
||||
return (usdPer1M / 1000) * usdCnyRate * 100 * DEEPSEEK_BILLING_MARKUP;
|
||||
|
||||
@@ -14,7 +14,7 @@ const config = {
|
||||
minBillCents: 1,
|
||||
};
|
||||
|
||||
test('loadBillingConfig defaults to DeepSeek Flash × 3 at USD/CNY 7.2', () => {
|
||||
test('loadBillingConfig defaults to DeepSeek Flash × 1 at USD/CNY 7.2', () => {
|
||||
const savedInput = process.env.H5_BILL_INPUT_CENTS_PER_1K;
|
||||
const savedOutput = process.env.H5_BILL_OUTPUT_CENTS_PER_1K;
|
||||
const savedRate = process.env.H5_USD_CNY_RATE;
|
||||
@@ -23,8 +23,8 @@ test('loadBillingConfig defaults to DeepSeek Flash × 3 at USD/CNY 7.2', () => {
|
||||
process.env.H5_USD_CNY_RATE = '7.2';
|
||||
try {
|
||||
const billing = loadBillingConfig();
|
||||
assert.ok(Math.abs(billing.inputCentsPer1k - 0.3024) < 1e-9);
|
||||
assert.ok(Math.abs(billing.outputCentsPer1k - 0.6048) < 1e-9);
|
||||
assert.ok(Math.abs(billing.inputCentsPer1k - 0.1008) < 1e-9);
|
||||
assert.ok(Math.abs(billing.outputCentsPer1k - 0.2016) < 1e-9);
|
||||
} finally {
|
||||
if (savedInput == null) delete process.env.H5_BILL_INPUT_CENTS_PER_1K;
|
||||
else process.env.H5_BILL_INPUT_CENTS_PER_1K = savedInput;
|
||||
@@ -35,12 +35,12 @@ test('loadBillingConfig defaults to DeepSeek Flash × 3 at USD/CNY 7.2', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('computeDeltaCostCents bills DeepSeek Flash × 3 defaults with min charge', () => {
|
||||
test('computeDeltaCostCents bills DeepSeek Flash × 1 defaults with min charge', () => {
|
||||
const billing = {
|
||||
useBackendCost: false,
|
||||
usdCnyRate: 7.2,
|
||||
inputCentsPer1k: 0.3024,
|
||||
outputCentsPer1k: 0.6048,
|
||||
inputCentsPer1k: 0.1008,
|
||||
outputCentsPer1k: 0.2016,
|
||||
minBillCents: 1,
|
||||
};
|
||||
const previous = { lastInputTokens: 0, lastOutputTokens: 0 };
|
||||
|
||||
@@ -60,6 +60,14 @@ export const CHAT_SKILL_DEFINITIONS = [
|
||||
requiresPublish: true,
|
||||
promptKey: 'generate-page',
|
||||
},
|
||||
{
|
||||
id: 'generate-image',
|
||||
label: '生成图片',
|
||||
icon: 'image',
|
||||
skillName: 'image-designer',
|
||||
prefillOnly: true,
|
||||
promptKey: 'generate-image',
|
||||
},
|
||||
];
|
||||
|
||||
export function buildChatSkillPrompt(promptKey, skillName) {
|
||||
@@ -78,6 +86,8 @@ export function buildChatSkillPrompt(promptKey, skillName) {
|
||||
return '请深入分析以下内容,给出结构化解读:背景、关键发现、风险或机会、以及建议下一步:';
|
||||
case 'generate-page':
|
||||
return `请使用 ${skillName ?? PUBLISH_SKILL_NAME} 技能:在我的专属 MindSpace 发布目录生成静态 HTML 页面,并给出可公网访问的完整链接。`;
|
||||
case 'generate-image':
|
||||
return `请使用 ${skillName ?? 'image-designer'} 技能:以高级视觉设计师身份,根据我的需求生成高质量图片,保存到工作区并给出可点击预览链接。我的需求是:`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ test('filterChatSkills shows generate-page when publish is allowed', () => {
|
||||
test('buildChatSkillPrompt includes skill name for platform skills', () => {
|
||||
assert.match(buildChatSkillPrompt('web', 'web'), /请使用 web 技能/);
|
||||
assert.match(buildChatSkillPrompt('generate-page'), /static-page-publish/);
|
||||
assert.match(buildChatSkillPrompt('generate-image', 'image-designer'), /image-designer/);
|
||||
});
|
||||
|
||||
test('prefillOnly is set for open-ended chat skills', () => {
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { Agent, fetch as undiciFetch } from 'undici';
|
||||
import {
|
||||
decryptSecret,
|
||||
normalizeApiUrl,
|
||||
resolveChatCompletionsUrl,
|
||||
} from './llm-providers.mjs';
|
||||
import { buildPublicUrl, resolvePublishKey } from './user-publish.mjs';
|
||||
import { isPathInsideUserWorkspace, resolveUserWorkspaceRoot } from './user-space.mjs';
|
||||
|
||||
const insecureDispatcher = new Agent({
|
||||
connect: { rejectUnauthorized: false },
|
||||
});
|
||||
|
||||
const ALLOWED_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp']);
|
||||
const ALLOWED_SIZES = new Set(['1024x1024', '1024x1792', '1792x1024', '512x512', '768x768']);
|
||||
const ALLOWED_STYLES = new Set(['vivid', 'natural']);
|
||||
const ALLOWED_QUALITIES = new Set(['standard', 'hd']);
|
||||
|
||||
export function loadImageGenConfig(env = process.env) {
|
||||
return {
|
||||
enabled: env.H5_IMAGE_GEN_ENABLED !== '0',
|
||||
apiUrl: String(env.H5_IMAGE_GEN_API_URL ?? '').trim(),
|
||||
apiKey: String(env.H5_IMAGE_GEN_API_KEY ?? '').trim(),
|
||||
model: String(env.H5_IMAGE_GEN_MODEL ?? 'dall-e-3').trim() || 'dall-e-3',
|
||||
defaultSize: String(env.H5_IMAGE_GEN_DEFAULT_SIZE ?? '1024x1024').trim() || '1024x1024',
|
||||
defaultStyle: String(env.H5_IMAGE_GEN_DEFAULT_STYLE ?? 'vivid').trim() || 'vivid',
|
||||
defaultQuality: String(env.H5_IMAGE_GEN_DEFAULT_QUALITY ?? 'standard').trim() || 'standard',
|
||||
billCents: Number(env.H5_IMAGE_GEN_BILL_CENTS ?? 30),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveImagesGenerationsUrl(apiUrl) {
|
||||
const configured = normalizeApiUrl(apiUrl);
|
||||
if (!configured) return '';
|
||||
if (configured.endsWith('/images/generations')) return configured;
|
||||
if (configured.endsWith('/chat/completions')) {
|
||||
return configured.replace(/\/chat\/completions$/, '/images/generations');
|
||||
}
|
||||
if (configured.endsWith('/v1')) return `${configured}/images/generations`;
|
||||
return `${configured}/v1/images/generations`;
|
||||
}
|
||||
|
||||
function resolveEncryptionKey(explicitKey) {
|
||||
return (
|
||||
explicitKey ??
|
||||
process.env.H5_SETTINGS_ENCRYPTION_KEY ??
|
||||
process.env.TKMIND_SERVER__SECRET_KEY ??
|
||||
'local-dev-secret'
|
||||
);
|
||||
}
|
||||
|
||||
function slugifyFilename(value) {
|
||||
return String(value ?? '')
|
||||
.normalize('NFKC')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 48);
|
||||
}
|
||||
|
||||
export function normalizeImageOutputPath(rawPath, prompt) {
|
||||
const cleaned = String(rawPath ?? '')
|
||||
.normalize('NFKC')
|
||||
.trim()
|
||||
.replace(/^\.?\/+/, '')
|
||||
.replace(/\\/g, '/');
|
||||
const fallback = `assets/${slugifyFilename(prompt) || 'generated-image'}.png`;
|
||||
const relative = cleaned || fallback;
|
||||
if (
|
||||
!relative ||
|
||||
relative.includes('..') ||
|
||||
relative.startsWith('/') ||
|
||||
relative.includes('\0')
|
||||
) {
|
||||
throw Object.assign(new Error('输出路径无效'), { code: 'invalid_output_path' });
|
||||
}
|
||||
const ext = path.extname(relative).toLowerCase();
|
||||
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
||||
throw Object.assign(new Error('仅支持 png / jpg / webp 输出'), { code: 'invalid_output_path' });
|
||||
}
|
||||
return relative;
|
||||
}
|
||||
|
||||
export function resolveWorkspaceOutputPath(workspaceRoot, relativePath) {
|
||||
const absolute = path.resolve(workspaceRoot, relativePath);
|
||||
if (!isPathInsideUserWorkspace(workspaceRoot, absolute)) {
|
||||
throw Object.assign(new Error('输出路径必须位于当前用户工作区内'), { code: 'invalid_output_path' });
|
||||
}
|
||||
return absolute;
|
||||
}
|
||||
|
||||
export function composeDesignerPrompt({ prompt, styleNotes, aspectRatio, brandColors, subject }) {
|
||||
const parts = [
|
||||
'Professional commercial visual design, high-end art direction, polished composition, clean lighting, production-ready quality.',
|
||||
];
|
||||
if (subject) parts.push(`Subject: ${subject}.`);
|
||||
if (styleNotes) parts.push(`Style direction: ${styleNotes}.`);
|
||||
if (aspectRatio) parts.push(`Aspect ratio intent: ${aspectRatio}.`);
|
||||
if (brandColors) parts.push(`Brand palette: ${brandColors}.`);
|
||||
parts.push(`Creative brief: ${String(prompt ?? '').trim()}`);
|
||||
parts.push('Avoid text overlays, watermarks, logos, distorted anatomy, blurry details, or cluttered backgrounds unless explicitly requested.');
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
function parseImagePayload(data) {
|
||||
const item = data?.data?.[0];
|
||||
if (!item) return null;
|
||||
if (typeof item.b64_json === 'string' && item.b64_json.trim()) {
|
||||
return { kind: 'base64', value: item.b64_json.trim(), revisedPrompt: item.revised_prompt ?? null };
|
||||
}
|
||||
if (typeof item.url === 'string' && item.url.trim()) {
|
||||
return { kind: 'url', value: item.url.trim(), revisedPrompt: item.revised_prompt ?? null };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loadProviderCredentials(pool, encryptionKey) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT api_url, api_key_ciphertext, api_key_iv, api_key_tag, relay_provider
|
||||
FROM h5_llm_provider_keys
|
||||
WHERE is_selected = 1 AND status = 'active'
|
||||
LIMIT 1`,
|
||||
);
|
||||
const row = rows[0];
|
||||
if (!row) {
|
||||
throw Object.assign(new Error('请先在管理后台配置并启用 LLM'), { code: 'image_gen_not_configured' });
|
||||
}
|
||||
let apiKey;
|
||||
try {
|
||||
apiKey = decryptSecret(
|
||||
{
|
||||
ciphertext: row.api_key_ciphertext,
|
||||
iv: row.api_key_iv,
|
||||
tag: row.api_key_tag,
|
||||
},
|
||||
resolveEncryptionKey(encryptionKey),
|
||||
);
|
||||
} catch {
|
||||
throw Object.assign(new Error('LLM 密钥解密失败,请重新配置'), { code: 'image_gen_not_configured' });
|
||||
}
|
||||
return {
|
||||
apiUrl: normalizeApiUrl(row.api_url),
|
||||
apiKey,
|
||||
relayProvider: row.relay_provider ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveProvider(pool, encryptionKey) {
|
||||
const config = loadImageGenConfig();
|
||||
if (config.apiUrl && config.apiKey) {
|
||||
return {
|
||||
url: resolveImagesGenerationsUrl(config.apiUrl),
|
||||
apiKey: config.apiKey,
|
||||
model: config.model,
|
||||
relayProvider: null,
|
||||
};
|
||||
}
|
||||
const provider = await loadProviderCredentials(pool, encryptionKey);
|
||||
const chatUrl = resolveChatCompletionsUrl(provider.apiUrl);
|
||||
return {
|
||||
url: resolveImagesGenerationsUrl(chatUrl || provider.apiUrl),
|
||||
apiKey: provider.apiKey,
|
||||
model: config.model,
|
||||
relayProvider: provider.relayProvider,
|
||||
};
|
||||
}
|
||||
|
||||
async function downloadImageBuffer(payload) {
|
||||
if (payload.kind === 'base64') {
|
||||
return Buffer.from(payload.value, 'base64');
|
||||
}
|
||||
const response = await undiciFetch(payload.value, {
|
||||
dispatcher: payload.value.startsWith('https://') ? insecureDispatcher : undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw Object.assign(new Error(`下载生成图片失败 (${response.status})`), { code: 'image_gen_failed' });
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return Buffer.from(arrayBuffer);
|
||||
}
|
||||
|
||||
function mimeTypeForPath(relativePath) {
|
||||
switch (path.extname(relativePath).toLowerCase()) {
|
||||
case '.jpg':
|
||||
case '.jpeg':
|
||||
return 'image/jpeg';
|
||||
case '.webp':
|
||||
return 'image/webp';
|
||||
default:
|
||||
return 'image/png';
|
||||
}
|
||||
}
|
||||
|
||||
export function buildImageGenerateCurlExample({ h5ApiBase, sessionId = '<当前会话ID>' } = {}) {
|
||||
const base = String(h5ApiBase ?? 'http://127.0.0.1:8081').replace(/\/$/, '');
|
||||
return [
|
||||
'```bash',
|
||||
`curl -sS -X POST '${base}/api/agent/generate_image' \\`,
|
||||
" -H 'Content-Type: application/json' \\",
|
||||
` -d '{"session_id":"${sessionId}","prompt":"A premium travel poster for Malaysia, cinematic sunset, editorial photography","output_path":"assets/malaysia-hero.png","style_notes":"modern editorial, warm palette","aspect_ratio":"3:4 portrait"}'`,
|
||||
'```',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function createImageGenerationService({
|
||||
pool,
|
||||
h5Root,
|
||||
encryptionKey,
|
||||
resolveUserIdForAgentSession,
|
||||
publicBaseUrl,
|
||||
}) {
|
||||
async function generateForAgentSession(input = {}) {
|
||||
const config = loadImageGenConfig();
|
||||
if (!config.enabled) {
|
||||
throw Object.assign(new Error('图片生成未启用'), { code: 'image_gen_unavailable' });
|
||||
}
|
||||
|
||||
const sessionId = String(input.sessionId ?? input.session_id ?? '').trim();
|
||||
const prompt = String(input.prompt ?? '').trim();
|
||||
if (!sessionId) {
|
||||
throw Object.assign(new Error('缺少 session_id'), { code: 'invalid_request' });
|
||||
}
|
||||
if (!prompt) {
|
||||
throw Object.assign(new Error('缺少 prompt'), { code: 'invalid_request' });
|
||||
}
|
||||
if (prompt.length > 4000) {
|
||||
throw Object.assign(new Error('prompt 过长'), { code: 'invalid_request' });
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForAgentSession(sessionId);
|
||||
if (!userId) {
|
||||
throw Object.assign(new Error('无效的 Agent 会话'), { code: 'forbidden' });
|
||||
}
|
||||
|
||||
const user = { id: userId };
|
||||
|
||||
const relativePath = normalizeImageOutputPath(input.outputPath ?? input.output_path, prompt);
|
||||
const workspaceRoot = resolveUserWorkspaceRoot(h5Root, user);
|
||||
const absolutePath = resolveWorkspaceOutputPath(workspaceRoot, relativePath);
|
||||
|
||||
const size = ALLOWED_SIZES.has(String(input.size ?? '').trim())
|
||||
? String(input.size).trim()
|
||||
: config.defaultSize;
|
||||
const style = ALLOWED_STYLES.has(String(input.style ?? '').trim())
|
||||
? String(input.style).trim()
|
||||
: config.defaultStyle;
|
||||
const quality = ALLOWED_QUALITIES.has(String(input.quality ?? '').trim())
|
||||
? String(input.quality).trim()
|
||||
: config.defaultQuality;
|
||||
|
||||
const designerPrompt = composeDesignerPrompt({
|
||||
prompt,
|
||||
styleNotes: input.styleNotes ?? input.style_notes,
|
||||
aspectRatio: input.aspectRatio ?? input.aspect_ratio,
|
||||
brandColors: input.brandColors ?? input.brand_colors,
|
||||
subject: input.subject,
|
||||
});
|
||||
|
||||
const provider = await resolveProvider(pool, encryptionKey);
|
||||
if (!provider.url) {
|
||||
throw Object.assign(new Error('图片生成 API 地址无效'), { code: 'image_gen_not_configured' });
|
||||
}
|
||||
|
||||
let upstream;
|
||||
try {
|
||||
upstream = await undiciFetch(provider.url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${provider.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: provider.model,
|
||||
prompt: designerPrompt,
|
||||
n: 1,
|
||||
size,
|
||||
style,
|
||||
quality,
|
||||
response_format: 'b64_json',
|
||||
...(provider.relayProvider ? { provider: provider.relayProvider } : {}),
|
||||
}),
|
||||
dispatcher: provider.url.startsWith('https://') ? insecureDispatcher : undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
throw Object.assign(
|
||||
new Error(`图片生成失败:${error instanceof Error ? error.message : '网络错误'}`),
|
||||
{ code: 'image_gen_failed' },
|
||||
);
|
||||
}
|
||||
|
||||
const text = await upstream.text().catch(() => '');
|
||||
if (!upstream.ok) {
|
||||
throw Object.assign(new Error(`图片生成失败 (${upstream.status})`), {
|
||||
code: 'image_gen_failed',
|
||||
details: text.slice(0, 500),
|
||||
});
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
throw Object.assign(new Error('图片生成响应不是 JSON'), { code: 'image_gen_failed' });
|
||||
}
|
||||
|
||||
const payload = parseImagePayload(data);
|
||||
if (!payload) {
|
||||
throw Object.assign(new Error('图片生成 API 未返回图片'), { code: 'image_gen_failed' });
|
||||
}
|
||||
|
||||
const buffer = await downloadImageBuffer(payload);
|
||||
if (buffer.length === 0) {
|
||||
throw Object.assign(new Error('图片内容为空'), { code: 'image_gen_failed' });
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await fs.writeFile(absolutePath, buffer);
|
||||
|
||||
const publishKey = resolvePublishKey(user);
|
||||
const publicUrl = buildPublicUrl(publicBaseUrl, publishKey, relativePath);
|
||||
|
||||
return {
|
||||
relativePath,
|
||||
absolutePath,
|
||||
publicUrl,
|
||||
mimeType: mimeTypeForPath(relativePath),
|
||||
size,
|
||||
style,
|
||||
quality,
|
||||
revisedPrompt: payload.revisedPrompt,
|
||||
prompt,
|
||||
designerPrompt,
|
||||
};
|
||||
}
|
||||
|
||||
return { generateForAgentSession };
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
buildImageGenerateCurlExample,
|
||||
composeDesignerPrompt,
|
||||
loadImageGenConfig,
|
||||
normalizeImageOutputPath,
|
||||
resolveImagesGenerationsUrl,
|
||||
} from './image-generation.mjs';
|
||||
|
||||
test('resolveImagesGenerationsUrl derives from chat completions base', () => {
|
||||
assert.equal(
|
||||
resolveImagesGenerationsUrl('https://api.openai.com/v1/chat/completions'),
|
||||
'https://api.openai.com/v1/images/generations',
|
||||
);
|
||||
assert.equal(
|
||||
resolveImagesGenerationsUrl('https://api.openai.com/v1/images/generations'),
|
||||
'https://api.openai.com/v1/images/generations',
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizeImageOutputPath defaults to assets slug png', () => {
|
||||
assert.equal(
|
||||
normalizeImageOutputPath('', '马来西亚旅行海报'),
|
||||
'assets/马来西亚旅行海报.png',
|
||||
);
|
||||
assert.equal(normalizeImageOutputPath('public/hero.webp', 'x'), 'public/hero.webp');
|
||||
});
|
||||
|
||||
test('normalizeImageOutputPath rejects traversal and unsupported extensions', () => {
|
||||
assert.throws(
|
||||
() => normalizeImageOutputPath('../secret.png', 'x'),
|
||||
(error) => error.code === 'invalid_output_path',
|
||||
);
|
||||
assert.throws(
|
||||
() => normalizeImageOutputPath('assets/hero.svg', 'x'),
|
||||
(error) => error.code === 'invalid_output_path',
|
||||
);
|
||||
});
|
||||
|
||||
test('composeDesignerPrompt merges creative brief and guardrails', () => {
|
||||
const prompt = composeDesignerPrompt({
|
||||
prompt: 'premium coffee brand poster',
|
||||
styleNotes: 'minimal, warm beige palette',
|
||||
aspectRatio: '3:4',
|
||||
brandColors: '#c8a27d',
|
||||
subject: 'latte art',
|
||||
});
|
||||
assert.match(prompt, /premium coffee brand poster/);
|
||||
assert.match(prompt, /minimal, warm beige palette/);
|
||||
assert.match(prompt, /Avoid text overlays/);
|
||||
});
|
||||
|
||||
test('buildImageGenerateCurlExample includes agent endpoint', () => {
|
||||
const example = buildImageGenerateCurlExample({
|
||||
h5ApiBase: 'http://127.0.0.1:8081',
|
||||
sessionId: 'sess-1',
|
||||
});
|
||||
assert.match(example, /\/api\/agent\/generate_image/);
|
||||
assert.match(example, /sess-1/);
|
||||
});
|
||||
|
||||
test('loadImageGenConfig defaults', () => {
|
||||
const config = loadImageGenConfig({});
|
||||
assert.equal(config.enabled, true);
|
||||
assert.equal(config.model, 'dall-e-3');
|
||||
assert.equal(config.defaultSize, '1024x1024');
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { PUBLISH_ROOT_DIR, PUBLISH_KEY_UUID } from './user-publish.mjs';
|
||||
import {
|
||||
migrateUserPublishDir,
|
||||
PUBLISH_ROOT_DIR,
|
||||
PUBLISH_KEY_UUID,
|
||||
resolveLegacyPublishDir,
|
||||
} from './user-publish.mjs';
|
||||
import { workspaceThumbnailRelativePath } from './mindspace-workspace-thumbnails.mjs';
|
||||
|
||||
const URL_PATTERN =
|
||||
@@ -23,11 +28,10 @@ export function extractStaticPageLinks(content, { userId, username } = {}) {
|
||||
for (const match of text.matchAll(URL_PATTERN)) {
|
||||
const owner = decodePathSegment(match[1]).toLowerCase();
|
||||
const relativePath = decodePathSegment(match[2]);
|
||||
if (normalizedUserId) {
|
||||
if (owner !== normalizedUserId) continue;
|
||||
} else if (normalizedUsername && owner !== normalizedUsername) {
|
||||
continue;
|
||||
}
|
||||
const ownedByUser =
|
||||
(normalizedUserId && owner === normalizedUserId) ||
|
||||
(normalizedUsername && owner === normalizedUsername);
|
||||
if ((normalizedUserId || normalizedUsername) && !ownedByUser) continue;
|
||||
const key = `${owner}/${relativePath}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
@@ -114,7 +118,7 @@ async function walkPublishHtmlByBasename(publishRoot, basename, maxDepth = 6, de
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function findPublishHtml(h5Root, userId, relativePath) {
|
||||
async function findPublishHtmlInRoot(publishRoot, relativePath, { readFile = fs.readFile } = {}) {
|
||||
const normalized = String(relativePath ?? '').replace(/^\/+/, '');
|
||||
const basename = path.basename(normalized);
|
||||
const candidates = [
|
||||
@@ -124,22 +128,59 @@ export async function findPublishHtml(h5Root, userId, relativePath) {
|
||||
].filter((value, index, list) => value && list.indexOf(value) === index);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const absolute = path.resolve(publishRoot, candidate);
|
||||
if (absolute !== publishRoot && !absolute.startsWith(`${publishRoot}${path.sep}`)) continue;
|
||||
if (!absolute.toLowerCase().endsWith('.html')) continue;
|
||||
try {
|
||||
return await readPublishHtml(h5Root, userId, candidate);
|
||||
const content = await readFile(absolute, 'utf8');
|
||||
if (!content.trim()) continue;
|
||||
const resolvedRelativePath = path.relative(publishRoot, absolute).split(path.sep).join('/');
|
||||
return {
|
||||
absolute,
|
||||
content,
|
||||
relativePath: resolvedRelativePath,
|
||||
filename: path.basename(resolvedRelativePath),
|
||||
};
|
||||
} catch {
|
||||
// try next candidate
|
||||
}
|
||||
}
|
||||
|
||||
const publishRoot = path.resolve(
|
||||
h5Root,
|
||||
PUBLISH_ROOT_DIR,
|
||||
String(userId ?? '').trim().toLowerCase(),
|
||||
);
|
||||
const absolute = await walkPublishHtmlByBasename(publishRoot, basename);
|
||||
if (absolute) {
|
||||
const resolvedRelativePath = path.relative(publishRoot, absolute).split(path.sep).join('/');
|
||||
return readPublishHtml(h5Root, userId, resolvedRelativePath);
|
||||
const content = await readFile(absolute, 'utf8');
|
||||
if (!content.trim()) {
|
||||
throw Object.assign(new Error('页面内容为空'), { code: 'empty_page_content' });
|
||||
}
|
||||
return {
|
||||
absolute,
|
||||
content,
|
||||
relativePath: resolvedRelativePath,
|
||||
filename: path.basename(resolvedRelativePath),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function findPublishHtml(h5Root, userId, relativePath, { username } = {}) {
|
||||
const key = String(userId ?? '').trim().toLowerCase();
|
||||
if (username && key) {
|
||||
migrateUserPublishDir(h5Root, { id: key, username });
|
||||
}
|
||||
|
||||
const publishRoot = path.resolve(h5Root, PUBLISH_ROOT_DIR, key);
|
||||
const primary = await findPublishHtmlInRoot(publishRoot, relativePath);
|
||||
if (primary) return primary;
|
||||
|
||||
const legacyRoot = username ? resolveLegacyPublishDir(h5Root, { username }) : null;
|
||||
if (legacyRoot && path.resolve(legacyRoot) !== publishRoot) {
|
||||
const legacy = await findPublishHtmlInRoot(legacyRoot, relativePath);
|
||||
if (legacy) {
|
||||
migrateUserPublishDir(h5Root, { id: key, username });
|
||||
return readPublishHtml(h5Root, userId, legacy.relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
throw Object.assign(new Error('无法读取链接页面内容'), { code: 'static_page_not_found' });
|
||||
@@ -215,8 +256,9 @@ function dedupeDuplicatedFilename(filename) {
|
||||
if (half >= 2 && base.slice(0, half) === base.slice(half)) {
|
||||
base = base.slice(0, half);
|
||||
}
|
||||
base = base.replace(/(.+?)\1+/g, '$1');
|
||||
base = base.replace(/(.{4,}?)\1+/g, '$1');
|
||||
base = base.replace(/-v-v(\d+)/g, '-v$1');
|
||||
base = base.replace(/-v(\d)\1+$/g, '-v$1');
|
||||
return `${base}.html`;
|
||||
}
|
||||
|
||||
@@ -288,23 +330,123 @@ export function analyzeChatMessageForSave({
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveStaticHtmlContent(analysis) {
|
||||
function uniqueRelativePaths(relativePath) {
|
||||
const basename = path.basename(String(relativePath ?? ''));
|
||||
return [
|
||||
String(relativePath ?? '').replace(/^\/+/, ''),
|
||||
String(relativePath ?? '')
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/^public\//, ''),
|
||||
`public/${basename}`,
|
||||
basename,
|
||||
].filter((value, index, list) => value && list.indexOf(value) === index);
|
||||
}
|
||||
|
||||
function buildPublishHtmlFetchUrls({ userId, link, publicBaseUrl }) {
|
||||
const urls = new Set();
|
||||
const base = String(publicBaseUrl ?? '').replace(/\/$/, '');
|
||||
if (base && link) {
|
||||
for (const rel of uniqueRelativePaths(link.relativePath)) {
|
||||
const asset = buildWorkspaceAssetUrl(userId, rel);
|
||||
if (asset) urls.add(`${base}${asset}`);
|
||||
}
|
||||
}
|
||||
if (link?.publicUrl) urls.add(link.publicUrl);
|
||||
return [...urls];
|
||||
}
|
||||
|
||||
async function mirrorFetchedPublishHtml(h5Root, userId, relativePath, content, { username } = {}) {
|
||||
try {
|
||||
if (username) {
|
||||
migrateUserPublishDir(h5Root, { id: userId, username });
|
||||
}
|
||||
const absolute = resolvePublishHtmlAbsolutePath(h5Root, userId, relativePath);
|
||||
await fs.mkdir(path.dirname(absolute), { recursive: true });
|
||||
await fs.writeFile(absolute, content, 'utf8');
|
||||
return absolute;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPublishHtmlFallback(
|
||||
analysis,
|
||||
{ publicBaseUrl, fetchImpl = fetch } = {},
|
||||
) {
|
||||
if (analysis.contentMode !== 'static_html' || !analysis.selectedLink) return null;
|
||||
const candidatePaths = uniqueRelativePaths(analysis.selectedLink.relativePath);
|
||||
const urls = buildPublishHtmlFetchUrls({
|
||||
userId: analysis.userId,
|
||||
link: analysis.selectedLink,
|
||||
publicBaseUrl,
|
||||
});
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await fetchImpl(url, { redirect: 'follow' });
|
||||
if (!response.ok) continue;
|
||||
const contentType = response.headers.get('content-type') ?? '';
|
||||
if (contentType.includes('json')) continue;
|
||||
const content = await response.text();
|
||||
if (!content.trim() || !/<html/i.test(content)) continue;
|
||||
let relativePath = candidatePaths[0];
|
||||
for (const rel of candidatePaths) {
|
||||
if (url.includes(encodeURIComponent(rel)) || url.endsWith(`/${rel}`) || url.endsWith(rel)) {
|
||||
relativePath = rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const absolute = await mirrorFetchedPublishHtml(
|
||||
analysis.h5Root,
|
||||
analysis.userId,
|
||||
relativePath,
|
||||
content,
|
||||
{ username: analysis.username },
|
||||
);
|
||||
const title = titleFromHtml(content);
|
||||
const summary = summaryFromHtml(content);
|
||||
return {
|
||||
absolute:
|
||||
absolute ??
|
||||
resolvePublishHtmlAbsolutePath(analysis.h5Root, analysis.userId, relativePath),
|
||||
content,
|
||||
relativePath,
|
||||
filename: path.basename(relativePath),
|
||||
suggestedTitle: title || analysis.suggestedTitle,
|
||||
suggestedSummary: summary || analysis.suggestedSummary,
|
||||
publicUrl: analysis.selectedLink.publicUrl,
|
||||
};
|
||||
} catch {
|
||||
// try next url
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function resolveStaticHtmlContent(analysis, options = {}) {
|
||||
if (analysis.contentMode !== 'static_html' || !analysis.selectedLink) {
|
||||
return null;
|
||||
}
|
||||
const loaded = await findPublishHtml(
|
||||
analysis.h5Root,
|
||||
analysis.userId,
|
||||
analysis.selectedLink.relativePath,
|
||||
);
|
||||
const title = titleFromHtml(loaded.content);
|
||||
const summary = summaryFromHtml(loaded.content);
|
||||
return {
|
||||
...loaded,
|
||||
suggestedTitle: title || analysis.suggestedTitle,
|
||||
suggestedSummary: summary || analysis.suggestedSummary,
|
||||
publicUrl: analysis.selectedLink.publicUrl,
|
||||
};
|
||||
try {
|
||||
const loaded = await findPublishHtml(
|
||||
analysis.h5Root,
|
||||
analysis.userId,
|
||||
analysis.selectedLink.relativePath,
|
||||
{ username: analysis.username },
|
||||
);
|
||||
const title = titleFromHtml(loaded.content);
|
||||
const summary = summaryFromHtml(loaded.content);
|
||||
return {
|
||||
...loaded,
|
||||
suggestedTitle: title || analysis.suggestedTitle,
|
||||
suggestedSummary: summary || analysis.suggestedSummary,
|
||||
publicUrl: analysis.selectedLink.publicUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.code !== 'static_page_not_found') throw error;
|
||||
const fetched = await fetchPublishHtmlFallback(analysis, options);
|
||||
if (fetched) return fetched;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveChatSaveAnalysis({
|
||||
@@ -313,6 +455,8 @@ export async function resolveChatSaveAnalysis({
|
||||
username,
|
||||
h5Root,
|
||||
selectedLinkIndex = 0,
|
||||
publicBaseUrl,
|
||||
fetchImpl,
|
||||
}) {
|
||||
const sanitized = sanitizeMessageContentForSave(content);
|
||||
let analysis = analyzeChatMessageForSave({
|
||||
@@ -323,9 +467,11 @@ export async function resolveChatSaveAnalysis({
|
||||
selectedLinkIndex,
|
||||
});
|
||||
|
||||
const resolveOptions = { publicBaseUrl, fetchImpl };
|
||||
|
||||
if (analysis.contentMode === 'static_html' && analysis.selectedLink) {
|
||||
try {
|
||||
const resolvedHtml = await resolveStaticHtmlContent(analysis);
|
||||
const resolvedHtml = await resolveStaticHtmlContent(analysis, resolveOptions);
|
||||
return { analysis, resolvedHtml };
|
||||
} catch {
|
||||
// fall through to filename hints
|
||||
@@ -334,7 +480,7 @@ export async function resolveChatSaveAnalysis({
|
||||
|
||||
for (const hint of extractHtmlFilenameHints(sanitized)) {
|
||||
try {
|
||||
const loaded = await findPublishHtml(h5Root, userId, hint);
|
||||
const loaded = await findPublishHtml(h5Root, userId, hint, { username });
|
||||
const syntheticLink = {
|
||||
publicUrl:
|
||||
buildWorkspaceAssetUrl(userId, loaded.relativePath) ??
|
||||
@@ -373,5 +519,12 @@ export async function resolveChatSaveAnalysis({
|
||||
}
|
||||
}
|
||||
|
||||
if (analysis.contentMode === 'static_html' && analysis.selectedLink) {
|
||||
const fetched = await fetchPublishHtmlFallback(analysis, resolveOptions);
|
||||
if (fetched) {
|
||||
return { analysis, resolvedHtml: fetched };
|
||||
}
|
||||
}
|
||||
|
||||
return { analysis, resolvedHtml: null };
|
||||
}
|
||||
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
buildWorkspaceBaseHref,
|
||||
buildWorkspaceThumbnailUrl,
|
||||
extractStaticPageLinks,
|
||||
extractHtmlFilenameHints,
|
||||
findPublishHtml,
|
||||
injectHtmlBaseHref,
|
||||
resolveChatSaveAnalysis,
|
||||
sanitizeMessageContentForSave,
|
||||
fetchPublishHtmlFallback,
|
||||
} from './mindspace-chat-save.mjs';
|
||||
|
||||
const USER_ID = 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e';
|
||||
@@ -42,6 +44,13 @@ test('extractStaticPageLinks accepts legacy username urls when username provided
|
||||
assert.equal(links.length, 1);
|
||||
});
|
||||
|
||||
test('extractStaticPageLinks accepts legacy username urls when userId and username provided', () => {
|
||||
const content = 'https://goo.tkmind.cn/MindSpace/john/report.html';
|
||||
const links = extractStaticPageLinks(content, { userId: USER_ID, username: 'john' });
|
||||
assert.equal(links.length, 1);
|
||||
assert.equal(links[0].relativePath, 'report.html');
|
||||
});
|
||||
|
||||
test('analyzeChatMessageForSave prefers static html mode', () => {
|
||||
const analysis = analyzeChatMessageForSave({
|
||||
content: `链接 https://g2.tkmind.cn/MindSpace/${USER_ID}/report.html`,
|
||||
@@ -96,6 +105,42 @@ test('buildChatSavePreviewUrls use local api routes', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('findPublishHtml resolves html stored only in legacy username directory', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-chat-save-legacy-'));
|
||||
const legacyDir = path.join(root, 'MindSpace', 'john', 'public');
|
||||
await fs.mkdir(legacyDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(legacyDir, 'legacy-only.html'),
|
||||
'<!DOCTYPE html><html><head><title>Legacy</title></head><body>legacy</body></html>',
|
||||
'utf8',
|
||||
);
|
||||
const loaded = await findPublishHtml(root, USER_ID, 'public/legacy-only.html', { username: 'john' });
|
||||
assert.equal(loaded.relativePath, 'public/legacy-only.html');
|
||||
assert.match(loaded.content, /Legacy/);
|
||||
const migrated = path.join(root, 'MindSpace', USER_ID, 'public', 'legacy-only.html');
|
||||
assert.ok(await fs.stat(migrated).then(() => true).catch(() => false));
|
||||
});
|
||||
|
||||
test('resolveChatSaveAnalysis resolves uuid link when html exists only in legacy username dir', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-chat-save-uuid-link-'));
|
||||
const legacyDir = path.join(root, 'MindSpace', 'john', 'public');
|
||||
await fs.mkdir(legacyDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(legacyDir, 'report.html'),
|
||||
'<!DOCTYPE html><html><head><title>Report</title></head><body>ok</body></html>',
|
||||
'utf8',
|
||||
);
|
||||
const content = `链接 https://g2.tkmind.cn/MindSpace/${USER_ID}/public/report.html`;
|
||||
const result = await resolveChatSaveAnalysis({
|
||||
content,
|
||||
userId: USER_ID,
|
||||
username: 'john',
|
||||
h5Root: root,
|
||||
});
|
||||
assert.equal(result.analysis.contentMode, 'static_html');
|
||||
assert.equal(result.resolvedHtml?.relativePath, 'public/report.html');
|
||||
});
|
||||
|
||||
test('findPublishHtml resolves nested public html by basename', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-chat-save-'));
|
||||
const publishDir = path.join(root, 'MindSpace', USER_ID, 'public');
|
||||
@@ -116,6 +161,11 @@ test('injectHtmlBaseHref adds base tag for relative assets', () => {
|
||||
assert.match(next, /<base href="\/MindSpace\//);
|
||||
});
|
||||
|
||||
test('extractHtmlFilenameHints preserves normal hyphenated filenames', () => {
|
||||
const hints = extractHtmlFilenameHints('已生成 training-registration.html');
|
||||
assert.deepEqual(hints, ['training-registration.html']);
|
||||
});
|
||||
|
||||
test('sanitizeMessageContentForSave repairs duplicated streaming urls', () => {
|
||||
const raw =
|
||||
'httpshttps://://gg22.t.tkmkmindind.cn.cn/M/MindSpace/a6fb1e97/public/public/w/welcomeelcome-v-v22.html.html';
|
||||
@@ -144,3 +194,28 @@ test('resolveChatSaveAnalysis falls back to local html filename hints', async ()
|
||||
assert.equal(result.analysis.contentMode, 'static_html');
|
||||
assert.equal(result.resolvedHtml?.relativePath, 'public/welcome-v2.html');
|
||||
});
|
||||
|
||||
test('fetchPublishHtmlFallback mirrors remote html into local MindSpace when disk is missing', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-chat-save-fetch-'));
|
||||
const html =
|
||||
'<!DOCTYPE html><html><head><title>Remote</title></head><body>from remote</body></html>';
|
||||
const analysis = analyzeChatMessageForSave({
|
||||
content: `https://g2.tkmind.cn/MindSpace/${USER_ID}/public/remote-page.html`,
|
||||
userId: USER_ID,
|
||||
username: 'john',
|
||||
h5Root: root,
|
||||
});
|
||||
const fetched = await fetchPublishHtmlFallback(analysis, {
|
||||
publicBaseUrl: 'http://127.0.0.1:8081',
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
headers: { get: () => 'text/html' },
|
||||
text: async () => html,
|
||||
}),
|
||||
});
|
||||
assert.ok(fetched);
|
||||
assert.equal(fetched.relativePath, 'public/remote-page.html');
|
||||
assert.match(fetched.content, /from remote/);
|
||||
const local = path.join(root, 'MindSpace', USER_ID, 'public', 'remote-page.html');
|
||||
assert.match(await fs.readFile(local, 'utf8'), /from remote/);
|
||||
});
|
||||
|
||||
@@ -440,7 +440,7 @@ export function createPageService(pool, options = {}) {
|
||||
? await resolveWorkspacePublishDir(userId)
|
||||
: null;
|
||||
if (workspacePublishDir && workspaceHtmlRelativePath) {
|
||||
await ensureWorkspaceHtmlThumbnail(
|
||||
void ensureWorkspaceHtmlThumbnail(
|
||||
workspacePublishDir,
|
||||
workspaceHtmlRelativePath,
|
||||
normalized.content,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"name": "tkmind-h5",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"argon2": "^0.44.0",
|
||||
"debug": "^4.4.3",
|
||||
"express": "^4.21.2",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
@@ -17,6 +18,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"redis": "^4.7.1",
|
||||
"undici": "^6.26.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -310,6 +312,12 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@epic-web/invariant": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
@@ -802,6 +810,80 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@phc/format": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
||||
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
|
||||
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2",
|
||||
"generic-pool": "3.9.0",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz",
|
||||
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz",
|
||||
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz",
|
||||
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz",
|
||||
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
@@ -901,9 +983,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -918,9 +997,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -935,9 +1011,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -952,9 +1025,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -969,9 +1039,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -986,9 +1053,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1003,9 +1067,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1020,9 +1081,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1037,9 +1095,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1054,9 +1109,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1071,9 +1123,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1088,9 +1137,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1105,9 +1151,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1363,6 +1406,22 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/argon2": {
|
||||
"version": "0.44.0",
|
||||
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz",
|
||||
"integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@phc/format": "^1.0.0",
|
||||
"cross-env": "^10.0.0",
|
||||
"node-addon-api": "^8.5.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
@@ -1568,6 +1627,15 @@
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -1629,6 +1697,37 @@
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
|
||||
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@epic-web/invariant": "^1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "dist/bin/cross-env.js",
|
||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -2044,6 +2143,15 @@
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
||||
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@@ -2287,6 +2395,12 @@
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -2538,6 +2652,26 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz",
|
||||
"integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.47",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz",
|
||||
@@ -2626,6 +2760,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||
@@ -2840,6 +2983,23 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz",
|
||||
"integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.2.0",
|
||||
"@redis/client": "1.6.1",
|
||||
"@redis/graph": "1.1.1",
|
||||
"@redis/json": "1.0.7",
|
||||
"@redis/search": "1.2.0",
|
||||
"@redis/time-series": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -3020,6 +3180,27 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
@@ -3437,6 +3618,21 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"check:plaza": "node scripts/check-plaza-local.mjs",
|
||||
"setup:plaza-local": "node scripts/setup-plaza-local.mjs",
|
||||
"setup:plaza-tunnel": "bash scripts/install-plaza-tunnel.sh",
|
||||
"setup:prod-services": "bash scripts/install-prod-services.sh",
|
||||
"setup:plaza-tls": "node scripts/plaza-local-tls.mjs",
|
||||
"dev:vite": "vite",
|
||||
"dev:server": "node server.mjs",
|
||||
@@ -25,7 +26,7 @@
|
||||
"seed:plaza-covers": "node scripts/seed-plaza-covers.mjs",
|
||||
"enrich:plaza": "node scripts/enrich-plaza-posts.mjs",
|
||||
"build": "vite build",
|
||||
"test": "node --test auth.test.mjs asr-proxy.test.mjs billing.test.mjs billing-recharge.test.mjs wechat-pay.test.mjs wechat-oauth.test.mjs capabilities.test.mjs policies.test.mjs chat-skills.test.mjs user-publish.test.mjs user-memory-profile.test.mjs skills-registry.test.mjs mindspace.test.mjs mindspace-scan.test.mjs mindspace-assets.test.mjs mindspace-pages.test.mjs mindspace-publications.test.mjs mindspace-chat-save.test.mjs mindspace-chat-context.test.mjs mindspace-content-scan.test.mjs mindspace-html-localize.test.mjs mindspace-visual-editor.test.mjs mindspace-cleanup.test.mjs mindspace-thumbnails.test.mjs mindspace-workspace-thumbnails.test.mjs mindspace-workspace-sync.test.mjs mindspace-asset-preview.test.mjs mindspace-agent-jobs.test.mjs mindspace-agent-runner.test.mjs message-stream.test.mjs plaza-posts.test.mjs plaza-interactions.test.mjs plaza-algorithm.test.mjs plaza-seo.test.mjs plaza-ops.test.mjs user-auth.test.mjs llm-providers.test.mjs",
|
||||
"test": "node --test auth.test.mjs asr-proxy.test.mjs billing.test.mjs billing-recharge.test.mjs wechat-pay.test.mjs wechat-oauth.test.mjs capabilities.test.mjs policies.test.mjs chat-skills.test.mjs user-publish.test.mjs user-memory-profile.test.mjs skills-registry.test.mjs mindspace.test.mjs mindspace-scan.test.mjs mindspace-assets.test.mjs mindspace-pages.test.mjs mindspace-publications.test.mjs mindspace-chat-save.test.mjs mindspace-chat-context.test.mjs mindspace-content-scan.test.mjs mindspace-html-localize.test.mjs mindspace-visual-editor.test.mjs mindspace-cleanup.test.mjs mindspace-thumbnails.test.mjs mindspace-workspace-thumbnails.test.mjs mindspace-workspace-sync.test.mjs mindspace-asset-preview.test.mjs mindspace-agent-jobs.test.mjs mindspace-agent-runner.test.mjs message-stream.test.mjs plaza-posts.test.mjs plaza-interactions.test.mjs plaza-algorithm.test.mjs plaza-seo.test.mjs plaza-ops.test.mjs user-auth.test.mjs llm-providers.test.mjs image-generation.test.mjs",
|
||||
"test:mindspace-e2e": "node scripts/mindspace-e2e.mjs",
|
||||
"test:mindspace-pages-e2e": "node scripts/mindspace-pages-e2e.mjs",
|
||||
"test:mindspace-publications-e2e": "node scripts/mindspace-publications-e2e.mjs",
|
||||
@@ -41,6 +42,7 @@
|
||||
"deploy:plaza-105": "bash scripts/deploy-plaza-105.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"argon2": "^0.44.0",
|
||||
"debug": "^4.4.3",
|
||||
"express": "^4.21.2",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
|
||||
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
argon2:
|
||||
specifier: ^0.44.0
|
||||
version: 0.44.0
|
||||
debug:
|
||||
specifier: ^4.4.3
|
||||
version: 4.4.3
|
||||
@@ -146,6 +149,9 @@ packages:
|
||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@epic-web/invariant@1.0.0':
|
||||
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -318,6 +324,10 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@phc/format@1.0.0':
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@redis/bloom@1.2.0':
|
||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||
peerDependencies:
|
||||
@@ -541,6 +551,10 @@ packages:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
argon2@0.44.0:
|
||||
resolution: {integrity: sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
array-flatten@1.1.1:
|
||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||
|
||||
@@ -625,6 +639,15 @@ packages:
|
||||
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cross-env@10.1.0:
|
||||
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
@@ -855,6 +878,9 @@ packages:
|
||||
is-property@1.0.2:
|
||||
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -943,6 +969,14 @@ packages:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
node-addon-api@8.8.0:
|
||||
resolution: {integrity: sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
|
||||
node-releases@2.0.47:
|
||||
resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -975,6 +1009,10 @@ packages:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-to-regexp@0.1.13:
|
||||
resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==}
|
||||
|
||||
@@ -1096,6 +1134,14 @@ packages:
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1225,6 +1271,11 @@ packages:
|
||||
which-module@2.0.1:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1372,6 +1423,8 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.29.7
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
|
||||
'@epic-web/invariant@1.0.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
optional: true
|
||||
|
||||
@@ -1469,6 +1522,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@redis/bloom@1.2.0(@redis/client@1.6.1)':
|
||||
dependencies:
|
||||
'@redis/client': 1.6.1
|
||||
@@ -1640,6 +1695,13 @@ snapshots:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
argon2@0.44.0:
|
||||
dependencies:
|
||||
'@phc/format': 1.0.0
|
||||
cross-env: 10.1.0
|
||||
node-addon-api: 8.8.0
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
array-flatten@1.1.1: {}
|
||||
|
||||
aws-ssl-profiles@1.1.2: {}
|
||||
@@ -1723,6 +1785,17 @@ snapshots:
|
||||
|
||||
cookie@1.1.1: {}
|
||||
|
||||
cross-env@10.1.0:
|
||||
dependencies:
|
||||
'@epic-web/invariant': 1.0.0
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
debug@2.6.9:
|
||||
@@ -1980,6 +2053,8 @@ snapshots:
|
||||
|
||||
is-property@1.0.2: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
@@ -2045,6 +2120,10 @@ snapshots:
|
||||
|
||||
negotiator@0.6.3: {}
|
||||
|
||||
node-addon-api@8.8.0: {}
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
|
||||
node-releases@2.0.47: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
@@ -2067,6 +2146,8 @@ snapshots:
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-to-regexp@0.1.13: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -2217,6 +2298,12 @@ snapshots:
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -2311,6 +2398,10 @@ snapshots:
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
allowBuilds:
|
||||
argon2: set this to true or false
|
||||
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 569 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 569 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 672 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 672 KiB |
|
After Width: | Height: | Size: 458 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 672 KiB |
|
After Width: | Height: | Size: 458 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 672 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 613 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 613 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 583 KiB |
|
After Width: | Height: | Size: 317 KiB |