fix(miniapp): add missing uuid and message-display utils
Commit the utility modules required by api.js, messages.js, and chat page
so WeChat DevTools can resolve require('./uuid') at runtime.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+53
-14
@@ -1,4 +1,5 @@
|
||||
const SESSION_KEY = 'tkmind_session';
|
||||
const { createUuid } = require('./uuid');
|
||||
|
||||
let apiBaseUrl = '';
|
||||
let cookie = '';
|
||||
@@ -15,6 +16,36 @@ function getMiniProgramAppId() {
|
||||
}
|
||||
}
|
||||
|
||||
function isTouristMode() {
|
||||
const appId = getMiniProgramAppId();
|
||||
return !appId || appId === 'touristappid';
|
||||
}
|
||||
|
||||
function getApiBaseUrl() {
|
||||
return apiBaseUrl;
|
||||
}
|
||||
|
||||
function formatRequestError(res, url) {
|
||||
const status = Number(res.statusCode || 0);
|
||||
const code = res.data?.error?.code || '';
|
||||
const serverMessage = res.data?.message || res.data?.error?.message || '';
|
||||
if (status === 403 && code === 'csrf_failed') {
|
||||
if (/m\.tkmind\.cn/i.test(apiBaseUrl)) {
|
||||
return '线上 Portal 尚未部署小程序登录接口(403 来源校验失败)。请先用账号密码登录,或改用本地 Portal 联调。';
|
||||
}
|
||||
return serverMessage || '请求被服务器拒绝(来源校验失败)';
|
||||
}
|
||||
if (status === 404) {
|
||||
return '登录接口不存在,请重启本地 Portal(pnpm dev)后再试';
|
||||
}
|
||||
if (serverMessage) return serverMessage;
|
||||
if (status === 503) return '服务暂不可用,请稍后重试或使用账号密码登录';
|
||||
if (status === 405) {
|
||||
return `接口不支持当前请求方法,请检查 API_BASE_URL 是否指向 Portal:${url}`;
|
||||
}
|
||||
return `请求失败 (${status || 'network'})`;
|
||||
}
|
||||
|
||||
function getStoredSession() {
|
||||
const session = wx.getStorageSync(SESSION_KEY);
|
||||
if (session && typeof session === 'object') {
|
||||
@@ -60,7 +91,7 @@ function request(path, options = {}) {
|
||||
wx.request({
|
||||
url,
|
||||
method: options.method || 'GET',
|
||||
data: options.body || undefined,
|
||||
data: options.body == null ? undefined : options.body,
|
||||
header,
|
||||
timeout: options.timeout || 20000,
|
||||
success(res) {
|
||||
@@ -75,12 +106,7 @@ function request(path, options = {}) {
|
||||
resolve(res.data);
|
||||
return;
|
||||
}
|
||||
const message =
|
||||
res.data?.message ||
|
||||
res.data?.error?.message ||
|
||||
(status === 405
|
||||
? `接口不支持当前请求方法,请检查 API_BASE_URL 是否指向 Portal 服务:${url}`
|
||||
: `请求失败 (${status || 'network'})`);
|
||||
const message = formatRequestError(res, url);
|
||||
const error = new Error(message);
|
||||
error.status = status;
|
||||
error.body = res.data;
|
||||
@@ -133,9 +159,10 @@ function wxLogin() {
|
||||
}
|
||||
|
||||
async function loginWithWechatCode(loginPath) {
|
||||
const appId = getMiniProgramAppId();
|
||||
if (!appId || appId === 'touristappid') {
|
||||
throw new Error('当前是游客模式,微信一键登录需要在 project.config.json 配置真实小程序 AppID。');
|
||||
if (isTouristMode()) {
|
||||
throw new Error(
|
||||
'当前是游客模式(无 AppID 关联)。请在微信开发者工具用 AppID wx3e79ecd530c88da4 重新导入项目,并确认登录 DevTools 的微信号已是该小程序管理员。'
|
||||
);
|
||||
}
|
||||
const code = await wxLogin();
|
||||
const result = await portalRequest(loginPath, {
|
||||
@@ -164,16 +191,25 @@ async function loadSession(sessionId) {
|
||||
return apiRequest(`/sessions/${encodeURIComponent(sessionId)}`);
|
||||
}
|
||||
|
||||
async function createAgentRun(input) {
|
||||
return apiRequest('/agent/runs', {
|
||||
async function createAgentRun(input = {}) {
|
||||
const body = {
|
||||
...input,
|
||||
request_id: input.request_id || createUuid(),
|
||||
};
|
||||
if (!body.session_id) {
|
||||
delete body.session_id;
|
||||
}
|
||||
const result = await apiRequest('/agent/runs', {
|
||||
method: 'POST',
|
||||
body: input,
|
||||
body,
|
||||
timeout: 60000
|
||||
});
|
||||
return result?.run ?? result;
|
||||
}
|
||||
|
||||
async function getAgentRun(runId) {
|
||||
return apiRequest(`/agent/runs/${encodeURIComponent(runId)}`);
|
||||
const result = await apiRequest(`/agent/runs/${encodeURIComponent(runId)}`);
|
||||
return result?.run ?? result;
|
||||
}
|
||||
|
||||
async function listMindSpacePages(options = {}) {
|
||||
@@ -186,6 +222,9 @@ async function listMindSpacePages(options = {}) {
|
||||
|
||||
module.exports = {
|
||||
setApiBaseUrl,
|
||||
getApiBaseUrl,
|
||||
getMiniProgramAppId,
|
||||
isTouristMode,
|
||||
getStoredSession,
|
||||
saveSession,
|
||||
clearSession,
|
||||
|
||||
+22
-3
@@ -1,7 +1,26 @@
|
||||
module.exports = {
|
||||
API_BASE_URL: 'https://m.tkmind.cn',
|
||||
// Production default for miniapp release / 提审.
|
||||
// Local Portal debug: set LOCAL_DEV = true, or copy config.local.example.js → config.local.js
|
||||
const LOCAL_DEV = false;
|
||||
|
||||
const defaults = {
|
||||
API_BASE_URL: LOCAL_DEV ? 'http://127.0.0.1:8081' : 'https://m.tkmind.cn',
|
||||
MINIAPP_LOGIN_PATH: '/auth/wechat-miniapp/login',
|
||||
SELECTED_SESSION_KEY: 'tkmind_selected_session_id',
|
||||
AGENT_RUN_POLL_MS: 1200,
|
||||
AGENT_RUN_MAX_POLLS: 120
|
||||
AGENT_RUN_MAX_POLLS: 120,
|
||||
};
|
||||
|
||||
let localOverrides = {};
|
||||
try {
|
||||
localOverrides = require('./config.local.js');
|
||||
} catch {
|
||||
// Optional gitignored overrides in utils/config.local.js
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...defaults,
|
||||
...localOverrides,
|
||||
...(localOverrides.API_BASE_URL
|
||||
? { API_BASE_URL: String(localOverrides.API_BASE_URL).replace(/\/$/, '') }
|
||||
: {}),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copy to config.local.js (gitignored) to force local Portal while preparing a release.
|
||||
// Remove or rename config.local.js to use production https://m.tkmind.cn from config.js.
|
||||
module.exports = {
|
||||
API_BASE_URL: 'http://127.0.0.1:8081',
|
||||
};
|
||||
@@ -0,0 +1,336 @@
|
||||
const TASK_ROUTING_HINT_RE = /^【TKMind 路由提示】[\s\S]*?\n\n/;
|
||||
const MEMIND_TASK_ORCHESTRATION_RE = /^【Memind 任务编排】[\s\S]*?(?:用户任务:|用户任务:)\s*/u;
|
||||
const MINDSPACE_CONTEXT_RE = /^\[MindSpace 上下文\][\s\S]*?\n\n/;
|
||||
const USER_IDENTITY_BLOCK_RE = /^\[用户身份\][\s\S]*?\n\n/;
|
||||
const IMAGE_URL_LINES_RE = /\n*\[图片\d+]: [^\n]+/g;
|
||||
const FILE_ATTACHMENT_LINES_RE = /\n*\[文件\d+: [^\]]+\]: [^\n]+/g;
|
||||
const ASSISTANT_DELIVERABLE_RE =
|
||||
/\[[^\]]+\]\(https?:\/\/[^)]+\)|https?:\/\/(?:m\.)?[^/\s]*tkmind\.(?:cn|ai)\/(?:MindSpace|u)\/|\bpublic\/[A-Za-z0-9._-]+\.html\b/i;
|
||||
const INTERNAL_ASSISTANT_MARKERS = [
|
||||
/data-mindspace-page-tag/i,
|
||||
/mindspace-cover/i,
|
||||
/\bload_skill\b/i,
|
||||
/static-page-publish/i,
|
||||
/\.agents\/skills/i,
|
||||
/SKILL\.md/i,
|
||||
/注意到技能/u,
|
||||
/技能更新/u,
|
||||
/页脚要用.*platform-brand/u,
|
||||
];
|
||||
const URL_PATTERN =
|
||||
/https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi;
|
||||
const PUBLICATION_URL_PATTERN =
|
||||
/https?:\/\/[^\s<>"')\]]+\/u\/([a-z0-9._-]+)\/pages\/([^\s<>"')\]]+)/gi;
|
||||
const MARKDOWN_LINK_RE = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
|
||||
const COMBINED_LINK_RE =
|
||||
/\[([^\]]+)\]\((https?:\/\/[^)]+)\)|(https?:\/\/[^\s<>"')\]]+)|`?(public\/[A-Za-z0-9._-]+\.html)`?|(\/?MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/(public\/[A-Za-z0-9._-]+\.html))/gi;
|
||||
const RELATIVE_PUBLIC_HTML_RE = /\b(public\/[A-Za-z0-9._-]+\.html)\b/gi;
|
||||
|
||||
function stripImageUrlLines(text) {
|
||||
return String(text ?? '')
|
||||
.replace(IMAGE_URL_LINES_RE, '')
|
||||
.replace(FILE_ATTACHMENT_LINES_RE, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function stripUserFacingPrefixes(text) {
|
||||
let next = String(text ?? '');
|
||||
next = next.replace(USER_IDENTITY_BLOCK_RE, '').trimStart();
|
||||
next = next.replace(TASK_ROUTING_HINT_RE, '').trimStart();
|
||||
next = next.replace(MEMIND_TASK_ORCHESTRATION_RE, '').trimStart();
|
||||
while (MINDSPACE_CONTEXT_RE.test(next)) {
|
||||
next = next.replace(MINDSPACE_CONTEXT_RE, '').trimStart();
|
||||
}
|
||||
return next.trim();
|
||||
}
|
||||
|
||||
function deriveAssistantFacingText(text) {
|
||||
const trimmed = String(text ?? '').trim();
|
||||
if (!trimmed) return '';
|
||||
if (ASSISTANT_DELIVERABLE_RE.test(trimmed)) return trimmed;
|
||||
if (INTERNAL_ASSISTANT_MARKERS.some((pattern) => pattern.test(trimmed))) return '';
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function decodeSegment(segment) {
|
||||
try {
|
||||
return decodeURIComponent(segment);
|
||||
} catch {
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWorkspaceRelativePath(relativePath) {
|
||||
const parts = String(relativePath ?? '')
|
||||
.replace(/^\/+/, '')
|
||||
.split('/')
|
||||
.filter((part) => part && part !== '.' && part !== '..');
|
||||
if (parts.length === 0) return '';
|
||||
if (parts[0]?.toLowerCase() === 'public') return parts.join('/');
|
||||
if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`;
|
||||
return parts.join('/');
|
||||
}
|
||||
|
||||
function resolveWorkspacePublicUrl(relativePath, publishKey, apiBaseUrl) {
|
||||
const normalized = normalizeWorkspaceRelativePath(relativePath);
|
||||
if (!publishKey || !normalized.startsWith('public/') || !/\.html$/i.test(normalized)) {
|
||||
return null;
|
||||
}
|
||||
const base = String(apiBaseUrl || '').replace(/\/$/, '');
|
||||
if (!base) return null;
|
||||
const encodedPath = normalized.split('/').map((part) => encodeURIComponent(part)).join('/');
|
||||
return `${base}/MindSpace/${encodeURIComponent(publishKey)}/${encodedPath}`;
|
||||
}
|
||||
|
||||
function buildPageLinkFromRelativePath(relativePath, publishKey, apiBaseUrl) {
|
||||
const normalized = normalizeWorkspaceRelativePath(relativePath);
|
||||
const publicUrl = resolveWorkspacePublicUrl(normalized, publishKey, apiBaseUrl);
|
||||
if (!publicUrl) return null;
|
||||
const filename = normalized.split('/').pop() || normalized;
|
||||
return {
|
||||
publicUrl,
|
||||
filename,
|
||||
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
|
||||
relativePath: normalized,
|
||||
};
|
||||
}
|
||||
|
||||
function extractRelativePublicPageLinks(content, publishKey, apiBaseUrl) {
|
||||
if (!publishKey || !apiBaseUrl) return [];
|
||||
const links = [];
|
||||
const seen = new Set();
|
||||
for (const match of String(content ?? '').matchAll(RELATIVE_PUBLIC_HTML_RE)) {
|
||||
const link = buildPageLinkFromRelativePath(match[1], publishKey, apiBaseUrl);
|
||||
if (!link || seen.has(link.publicUrl)) continue;
|
||||
seen.add(link.publicUrl);
|
||||
links.push(link);
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function mergePageLinks(...groups) {
|
||||
const links = [];
|
||||
const seen = new Set();
|
||||
for (const group of groups) {
|
||||
for (const link of group || []) {
|
||||
if (!link?.publicUrl || seen.has(link.publicUrl)) continue;
|
||||
seen.add(link.publicUrl);
|
||||
links.push(link);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function normalizeStaticHtmlRelativePath(relativePath) {
|
||||
const parts = String(relativePath ?? '')
|
||||
.replace(/^\/+/, '')
|
||||
.split('/')
|
||||
.filter((part) => part && part !== '.' && part !== '..');
|
||||
if (parts.length === 0) return '';
|
||||
if (parts[0]?.toLowerCase() === 'public') return ['public', ...parts.slice(1)].join('/');
|
||||
if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`;
|
||||
return parts.join('/');
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function encodeUrlPath(relativePath) {
|
||||
return relativePath
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.map((part) => encodeURIComponent(part))
|
||||
.join('/');
|
||||
}
|
||||
|
||||
function canonicalizeStaticPageUrl(publicUrl, originalRelativePath) {
|
||||
const canonicalRelativePath = normalizeStaticHtmlRelativePath(originalRelativePath);
|
||||
const originalClean = originalRelativePath.replace(/^\/+/, '');
|
||||
if (!canonicalRelativePath || canonicalRelativePath === originalClean) return publicUrl;
|
||||
const suffix = escapeRegExp(encodeUrlPath(originalClean));
|
||||
return publicUrl.replace(new RegExp(`${suffix}$`), encodeUrlPath(canonicalRelativePath));
|
||||
}
|
||||
|
||||
function extractStaticPageLinks(content) {
|
||||
const links = [];
|
||||
const seen = new Set();
|
||||
const source = String(content ?? '');
|
||||
for (const match of source.matchAll(URL_PATTERN)) {
|
||||
const relativePath = decodeSegment(match[2]);
|
||||
const filename = relativePath.split('/').pop() || relativePath;
|
||||
const publicUrl = canonicalizeStaticPageUrl(match[0], relativePath);
|
||||
if (seen.has(publicUrl)) continue;
|
||||
seen.add(publicUrl);
|
||||
links.push({
|
||||
publicUrl,
|
||||
filename,
|
||||
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
|
||||
});
|
||||
}
|
||||
for (const match of source.matchAll(PUBLICATION_URL_PATTERN)) {
|
||||
const slug = decodeSegment(match[2]).replace(/\/$/, '');
|
||||
const publicUrl = match[0].replace(/\/$/, '');
|
||||
if (seen.has(publicUrl)) continue;
|
||||
seen.add(publicUrl);
|
||||
const filename = slug.split('/').pop() || slug;
|
||||
links.push({
|
||||
publicUrl,
|
||||
filename,
|
||||
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
|
||||
});
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function normalizeUrlForMatch(url) {
|
||||
return String(url ?? '').replace(/\/$/, '').trim();
|
||||
}
|
||||
|
||||
function cleanupOrphanMarkdownDecorations(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/(?:\*\*){1,}/g, '')
|
||||
.replace(/^[👉🔗\s\-•*::]+$/gm, '')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function stripPageDeliverableLinks(text, pageLinks) {
|
||||
if (!pageLinks?.length) return String(text ?? '').trim();
|
||||
const urlSet = new Set(pageLinks.map((link) => normalizeUrlForMatch(link.publicUrl)));
|
||||
let next = String(text ?? '');
|
||||
const markdownWithDecorationRe =
|
||||
/[👉🔗\s]*(?:\*\*)?\[([^\]]+)\]\((https?:\/\/[^)]+)\)(?:\*\*)?/g;
|
||||
next = next.replace(markdownWithDecorationRe, (full, _label, url) =>
|
||||
urlSet.has(normalizeUrlForMatch(url)) ? '' : full,
|
||||
);
|
||||
for (const url of urlSet) {
|
||||
next = next.replace(new RegExp(escapeRegExp(url) + '\\/?', 'gi'), '');
|
||||
}
|
||||
return cleanupOrphanMarkdownDecorations(next);
|
||||
}
|
||||
|
||||
function parseMessageParts(text, options = {}) {
|
||||
const { publishKey = '', apiBaseUrl = '' } = options;
|
||||
const input = String(text ?? '');
|
||||
if (!input) return [];
|
||||
const parts = [];
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
COMBINED_LINK_RE.lastIndex = 0;
|
||||
while ((match = COMBINED_LINK_RE.exec(input)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push({ type: 'text', text: input.slice(lastIndex, match.index) });
|
||||
}
|
||||
if (match[1] && match[2]) {
|
||||
parts.push({ type: 'link', text: match[1], url: match[2] });
|
||||
} else if (match[3]) {
|
||||
parts.push({ type: 'link', text: match[3], url: match[3] });
|
||||
} else if (match[4]) {
|
||||
const relativePath = match[4].replace(/`/g, '');
|
||||
const link = buildPageLinkFromRelativePath(relativePath, publishKey, apiBaseUrl);
|
||||
parts.push({
|
||||
type: 'link',
|
||||
text: link?.title || relativePath,
|
||||
url: link?.publicUrl || relativePath,
|
||||
});
|
||||
} else if (match[5] && match[6]) {
|
||||
const encodedPath = match[5].replace(/^\/+/, '');
|
||||
const publicUrl = resolveWorkspacePublicUrl(
|
||||
encodedPath.slice(encodedPath.indexOf('public/')),
|
||||
match[6],
|
||||
apiBaseUrl,
|
||||
) || (apiBaseUrl ? `${String(apiBaseUrl).replace(/\/$/, '')}/${encodedPath}` : match[5]);
|
||||
parts.push({
|
||||
type: 'link',
|
||||
text: buildPageLinkFromRelativePath(
|
||||
encodedPath.slice(encodedPath.indexOf('public/')),
|
||||
match[6],
|
||||
apiBaseUrl,
|
||||
)?.title || encodedPath.split('/').pop(),
|
||||
url: publicUrl,
|
||||
});
|
||||
}
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
if (lastIndex < input.length) {
|
||||
parts.push({ type: 'text', text: input.slice(lastIndex) });
|
||||
}
|
||||
return parts.length ? parts : [{ type: 'text', text: input }];
|
||||
}
|
||||
|
||||
function getRawMessageText(message) {
|
||||
if (!message) return '';
|
||||
if (typeof message.text === 'string') return message.text;
|
||||
if (Array.isArray(message.content)) {
|
||||
return message.content
|
||||
.filter((part) => part && part.type === 'text')
|
||||
.map((part) => part.text || '')
|
||||
.join('\n');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getDisplayText(message) {
|
||||
const rawText = getRawMessageText(message);
|
||||
if (message?.role === 'user') {
|
||||
if (message.metadata?.displayText != null) {
|
||||
return stripImageUrlLines(message.metadata.displayText);
|
||||
}
|
||||
return stripImageUrlLines(stripUserFacingPrefixes(rawText));
|
||||
}
|
||||
if (message?.metadata?.displayText != null) {
|
||||
return stripImageUrlLines(deriveAssistantFacingText(message.metadata.displayText));
|
||||
}
|
||||
return stripImageUrlLines(deriveAssistantFacingText(rawText));
|
||||
}
|
||||
|
||||
function buildMessageView(message, options = {}) {
|
||||
const { publishKey = '', apiBaseUrl = '' } = options;
|
||||
const rawText = getRawMessageText(message);
|
||||
const displayText = getDisplayText(message);
|
||||
const sourceText = displayText || rawText;
|
||||
const pageLinks = mergePageLinks(
|
||||
extractStaticPageLinks(sourceText),
|
||||
extractRelativePublicPageLinks(sourceText, publishKey, apiBaseUrl),
|
||||
);
|
||||
let text = displayText;
|
||||
if (!text && pageLinks.length > 0) {
|
||||
text = '页面已生成,可点击下方链接查看。';
|
||||
}
|
||||
if (!text.trim()) return null;
|
||||
|
||||
const markdownTitleByUrl = {};
|
||||
for (const match of String(sourceText).matchAll(MARKDOWN_LINK_RE)) {
|
||||
markdownTitleByUrl[match[2]] = match[1];
|
||||
}
|
||||
const enrichedPageLinks = pageLinks.map((link) => ({
|
||||
...link,
|
||||
title: markdownTitleByUrl[link.publicUrl] || link.title,
|
||||
}));
|
||||
const renderText = stripPageDeliverableLinks(text, enrichedPageLinks);
|
||||
const pageLinkUrls = new Set(enrichedPageLinks.map((link) => normalizeUrlForMatch(link.publicUrl)));
|
||||
const parts = parseMessageParts(renderText, { publishKey, apiBaseUrl }).filter(
|
||||
(part) =>
|
||||
part.type !== 'link' || !pageLinkUrls.has(normalizeUrlForMatch(part.url)),
|
||||
);
|
||||
|
||||
return {
|
||||
id: message.id || `${message.role}_${message.created || Date.now()}`,
|
||||
role: message.role || 'assistant',
|
||||
text: renderText,
|
||||
copyText: renderText,
|
||||
parts,
|
||||
pageLinks: enrichedPageLinks,
|
||||
created: message.created || 0,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildMessageView,
|
||||
extractStaticPageLinks,
|
||||
extractRelativePublicPageLinks,
|
||||
getDisplayText,
|
||||
parseMessageParts,
|
||||
resolveWorkspacePublicUrl,
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
const { createUuid } = require('./uuid');
|
||||
const { buildMessageView } = require('./message-display');
|
||||
|
||||
function createUserMessage(text) {
|
||||
return {
|
||||
id: `mp_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
||||
id: createUuid(),
|
||||
role: 'user',
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
content: [{ type: 'text', text }],
|
||||
@@ -25,13 +28,10 @@ function messageText(message) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizeMessages(messages = []) {
|
||||
return messages.map((message) => ({
|
||||
id: message.id || `${message.role}_${message.created || Date.now()}`,
|
||||
role: message.role || 'assistant',
|
||||
text: messageText(message),
|
||||
created: message.created || 0
|
||||
}));
|
||||
function normalizeMessages(messages = [], options = {}) {
|
||||
return messages
|
||||
.map((message) => buildMessageView(message, options))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
function createUuid() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
const bytes = new Uint8Array(16);
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
||||
crypto.getRandomValues(bytes);
|
||||
} else {
|
||||
for (let i = 0; i < 16; i += 1) {
|
||||
bytes[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
}
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createUuid,
|
||||
};
|
||||
Reference in New Issue
Block a user