feat(mindspace): 悬浮聊天勾选资料联动、UI 与交互优化
勾选单个文件时 Agent 获知上下文并主动问候;打开悬浮窗使用新会话。 统一弹窗浅色主题、固定高度与输入区布局,支持点击外部收起。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,37 +1,42 @@
|
|||||||
# 103 发布 0630002 — 回退说明
|
# 103 发布 0630002 — 回退说明
|
||||||
|
|
||||||
**发布编号:** `20260630-013953-7906f3c`
|
**发布编号:** `20260630-020301-14f46cf`
|
||||||
**Git commit:** `7906f3c`(分支 `0630002`)
|
**Git commit:** `14f46cf`(分支 `0630002`)
|
||||||
**发布时间:** 2026-06-30
|
**发布时间:** 2026-06-30
|
||||||
|
|
||||||
## 本次内容
|
## 本次内容
|
||||||
|
|
||||||
- **修复删除无效**:MySQL `source_snapshot_json` 经 mysql2 返回为 object 时,`JSON.parse(object)` 抛错导致 `relative_path` 为空,工作区 HTML 未清理,列表同步后页面「复活」
|
- **待办提醒推送**:`schedule-reminder-worker` 扫描并投递单次 `h5_schedule_reminders`(微信服务号 + 站内通知)
|
||||||
- 新增 `parseJsonColumn()`,统一解析 JSON 列(`mindspace-pages.mjs`、`mindspace-page-sync.mjs`)
|
- **行事历 UI**:仅展示「提醒」;状态「已通知 / 待通知」;支持忽略、勾选、全选、批量删除
|
||||||
- 删除 purge 失败时增加 warn 日志
|
- **日程写入**:`startLocal` / `remindLocal` 强制本地时间解析,拒绝离谱 epoch 毫秒时间戳
|
||||||
|
- **API**:`POST /mindspace/v1/schedule/reminders/:id/ignore`、`POST /mindspace/v1/schedule/reminders/bulk-delete`
|
||||||
|
|
||||||
## 103 自动备份(发布脚本已创建)
|
## 103 自动备份(发布脚本已创建)
|
||||||
|
|
||||||
| 类型 | 路径(103 上) |
|
| 类型 | 路径(103 上) |
|
||||||
|------|----------------|
|
|------|----------------|
|
||||||
| 全量 live 备份 | `/Users/john/Project/backups/memind/memind-full-20260630-013953-7906f3c-before.tar.gz` |
|
| 全量 live 备份 | `/Users/john/Project/backups/memind/memind-full-20260630-020301-14f46cf-before.tar.gz` |
|
||||||
| 持久目录备份 | `/Users/john/Project/backups/memind/memind-persisted-20260630-013953-7906f3c-before.tar.gz` |
|
| 持久目录备份 | `/Users/john/Project/backups/memind/memind-persisted-20260630-020301-14f46cf-before.tar.gz` |
|
||||||
| 旧源码归档 | `/Users/john/Project/archives/Memind-source-before-20260630-013953-7906f3c` |
|
| 旧源码归档 | `/Users/john/Project/archives/Memind-source-before-20260630-020301-14f46cf` |
|
||||||
| 当前 live | `/Users/john/Project/Memind`(无源码 runtime) |
|
| 当前 live | `/Users/john/Project/Memind`(无源码 runtime) |
|
||||||
|
|
||||||
|
**上一版 live(本发布前):** `20260630-013953-7906f3c`(commit `7906f3c`)
|
||||||
|
|
||||||
## 健康验证
|
## 健康验证
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh 58.38.22.103 'curl -sf http://127.0.0.1:8081/api/status && echo'
|
ssh 58.38.22.103 'curl -sf http://127.0.0.1:8081/api/status && echo'
|
||||||
```
|
```
|
||||||
|
|
||||||
## 回退到上一版 runtime(0630001)
|
**2026-06-30 发版后验证:** `ok`;`.env` 中 `H5_SCHEDULE_ENABLED=1`、`H5_REMINDER_WORKER_ENABLED=1`。
|
||||||
|
|
||||||
|
## 回退到上一版 runtime(7906f3c)
|
||||||
|
|
||||||
在 **103** 上执行:
|
在 **103** 上执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
RELEASE_ID=20260630-013953-7906f3c
|
RELEASE_ID=20260630-020301-14f46cf
|
||||||
PREV_RELEASE=20260630-012244-66ca0b3
|
PREV_RELEASE=20260630-013953-7906f3c
|
||||||
APP_DIR=/Users/john/Project/Memind
|
APP_DIR=/Users/john/Project/Memind
|
||||||
ARCHIVE=/Users/john/Project/archives/Memind-source-before-${RELEASE_ID}
|
ARCHIVE=/Users/john/Project/archives/Memind-source-before-${RELEASE_ID}
|
||||||
PORTAL_LABEL=cn.tkmind.memind-portal
|
PORTAL_LABEL=cn.tkmind.memind-portal
|
||||||
@@ -41,7 +46,6 @@ launchctl bootout "${LAUNCHD_GUI}/${PORTAL_LABEL}" 2>/dev/null || true
|
|||||||
lsof -tiTCP:8081 -sTCP:LISTEN | xargs kill -9 2>/dev/null || true
|
lsof -tiTCP:8081 -sTCP:LISTEN | xargs kill -9 2>/dev/null || true
|
||||||
|
|
||||||
mv "${APP_DIR}" "${APP_DIR}.failed-runtime-${RELEASE_ID}"
|
mv "${APP_DIR}" "${APP_DIR}.failed-runtime-${RELEASE_ID}"
|
||||||
# 若需回到 0630001 runtime,从全量备份解压或保留的 runtime artifact 恢复
|
|
||||||
tar -xzf /Users/john/Project/backups/memind/memind-full-${PREV_RELEASE}-before.tar.gz -C /Users/john/Project
|
tar -xzf /Users/john/Project/backups/memind/memind-full-${PREV_RELEASE}-before.tar.gz -C /Users/john/Project
|
||||||
|
|
||||||
launchctl bootstrap "${LAUNCHD_GUI}" "${HOME}/Library/LaunchAgents/${PORTAL_LABEL}.plist"
|
launchctl bootstrap "${LAUNCHD_GUI}" "${HOME}/Library/LaunchAgents/${PORTAL_LABEL}.plist"
|
||||||
@@ -51,13 +55,17 @@ curl -sf http://127.0.0.1:8081/api/status
|
|||||||
|
|
||||||
## 从全量 tar 恢复(极端情况)
|
## 从全量 tar 恢复(极端情况)
|
||||||
|
|
||||||
|
从**本发布前**全量备份恢复(即回到 `7906f3c` 上线前的 live 快照):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Users/john/Project
|
cd /Users/john/Project
|
||||||
mv Memind "Memind.broken-$(date +%Y%m%d-%H%M%S)"
|
mv Memind "Memind.broken-$(date +%Y%m%d-%H%M%S)"
|
||||||
tar -xzf backups/memind/memind-full-20260630-013953-7906f3c-before.tar.gz
|
tar -xzf backups/memind/memind-full-20260630-020301-14f46cf-before.tar.gz
|
||||||
# 再启动 Portal LaunchAgent
|
# 再启动 Portal LaunchAgent
|
||||||
```
|
```
|
||||||
|
|
||||||
|
若 runtime 切换失败,发布脚本 remote 段内置 `trap rollback` 会自动尝试恢复。
|
||||||
|
|
||||||
## 本地复现 / 再发版
|
## 本地复现 / 再发版
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+103
-6
@@ -63,6 +63,75 @@ function resolvePageRecord(selectedPageId, pages, pageLive) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSelectedAsset(asset) {
|
||||||
|
if (!asset?.id) return null;
|
||||||
|
return {
|
||||||
|
id: asset.id,
|
||||||
|
displayName: asset.displayName ?? asset.filename ?? asset.id,
|
||||||
|
mimeType: asset.mimeType ?? 'application/octet-stream',
|
||||||
|
...(asset.filename ? { filename: asset.filename } : {}),
|
||||||
|
...(typeof asset.sizeBytes === 'number' ? { sizeBytes: asset.sizeBytes } : {}),
|
||||||
|
...(asset.categoryCode ? { categoryCode: asset.categoryCode } : {}),
|
||||||
|
...(asset.assetType ? { assetType: asset.assetType } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferAssetKindLabel(asset) {
|
||||||
|
const mime = String(asset?.mimeType ?? '').toLowerCase();
|
||||||
|
const name = String(asset?.displayName ?? asset?.filename ?? '').toLowerCase();
|
||||||
|
if (mime.startsWith('image/')) return '图片';
|
||||||
|
if (
|
||||||
|
mime.includes('spreadsheet') ||
|
||||||
|
mime.includes('csv') ||
|
||||||
|
name.endsWith('.xlsx') ||
|
||||||
|
name.endsWith('.xls') ||
|
||||||
|
name.endsWith('.csv')
|
||||||
|
) {
|
||||||
|
return '数据表';
|
||||||
|
}
|
||||||
|
if (mime.includes('pdf') || name.endsWith('.pdf')) return '报告';
|
||||||
|
if (mime.includes('word') || name.endsWith('.doc') || name.endsWith('.docx')) return '文档';
|
||||||
|
if (mime.startsWith('text/')) return '文本资料';
|
||||||
|
return '文件';
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSelectedAssetInstructions(context) {
|
||||||
|
const selected = context.selectedAssets ?? [];
|
||||||
|
if (selected.length === 0) return [];
|
||||||
|
|
||||||
|
const lines = ['【界面勾选资料(硬性)】'];
|
||||||
|
if (selected.length === 1) {
|
||||||
|
const asset = selected[0];
|
||||||
|
const kind = inferAssetKindLabel(asset);
|
||||||
|
lines.push(
|
||||||
|
`- 用户在界面勾选了 1 份${kind}:「${asset.displayName}」(id: ${asset.id},${asset.mimeType})`,
|
||||||
|
'- 用户说「这个 / 这份 / 这篇 / 这份报告 / 这个数据 / 帮我分析 / 帮我删除」等指代词时,**必须**指这份勾选资料,不得猜测其它文件。',
|
||||||
|
`- 读取/下载:GET /api/mindspace/v1/assets/${asset.id}/download`,
|
||||||
|
`- 删除:DELETE /api/mindspace/v1/assets/${asset.id}(执行删除前必须向用户确认)`,
|
||||||
|
);
|
||||||
|
if (context.h5ApiBase) {
|
||||||
|
lines.push(`- H5 API 基址:${context.h5ApiBase}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push(`- 用户在界面同时勾选了 ${selected.length} 份资料:`);
|
||||||
|
for (const asset of selected) {
|
||||||
|
lines.push(` · 「${asset.displayName}」(id: ${asset.id},${asset.mimeType})`);
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
|
'- 用户说「这个 / 这份」等指代词时,**必须先追问**要处理哪一份,不得擅自选一份执行。',
|
||||||
|
'- 不要主动对多选资料发起默认操作;等用户明确指定后再执行。',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSelectedAssetGreeting(asset) {
|
||||||
|
const kind = inferAssetKindLabel(asset);
|
||||||
|
const name = asset.displayName ?? asset.filename ?? '这份资料';
|
||||||
|
return `我看到你在界面里勾选了「${name}」。你是想查看或处理这份${kind}吗?可以直接说「帮我分析」「总结一下」或「帮我删除」——我会针对这份资料来操作。`;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildMindSpaceChatContext(input) {
|
export function buildMindSpaceChatContext(input) {
|
||||||
const {
|
const {
|
||||||
space,
|
space,
|
||||||
@@ -72,6 +141,7 @@ export function buildMindSpaceChatContext(input) {
|
|||||||
pages = [],
|
pages = [],
|
||||||
pageLive = null,
|
pageLive = null,
|
||||||
assets = [],
|
assets = [],
|
||||||
|
selectedAssets = [],
|
||||||
focusedAsset = null,
|
focusedAsset = null,
|
||||||
route,
|
route,
|
||||||
agentSessionId = null,
|
agentSessionId = null,
|
||||||
@@ -87,6 +157,14 @@ export function buildMindSpaceChatContext(input) {
|
|||||||
if (pageRecord && selectedPageId) view = 'page';
|
if (pageRecord && selectedPageId) view = 'page';
|
||||||
else if (selectedCategory) view = 'category';
|
else if (selectedCategory) view = 'category';
|
||||||
|
|
||||||
|
const normalizedSelected = selectedAssets
|
||||||
|
.map((asset) => normalizeSelectedAsset(asset))
|
||||||
|
.filter(Boolean);
|
||||||
|
const resolvedFocus =
|
||||||
|
normalizedSelected.length === 1
|
||||||
|
? normalizedSelected[0]
|
||||||
|
: normalizeSelectedAsset(focusedAsset);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
spaceId: space.id,
|
spaceId: space.id,
|
||||||
spaceName: space.name,
|
spaceName: space.name,
|
||||||
@@ -135,13 +213,14 @@ export function buildMindSpaceChatContext(input) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(focusedAsset
|
...(resolvedFocus
|
||||||
? {
|
? {
|
||||||
focusedAsset: {
|
focusedAsset: resolvedFocus,
|
||||||
id: focusedAsset.id,
|
}
|
||||||
displayName: focusedAsset.displayName,
|
: {}),
|
||||||
mimeType: focusedAsset.mimeType,
|
...(normalizedSelected.length
|
||||||
},
|
? {
|
||||||
|
selectedAssets: normalizedSelected,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
@@ -175,6 +254,14 @@ function buildLocationHint(context) {
|
|||||||
if (context.page) {
|
if (context.page) {
|
||||||
return `用户正在查看/编辑页面「${context.page.title}」(id: ${context.page.id})。指代「这个页面」「改这里」时默认指该页面。`;
|
return `用户正在查看/编辑页面「${context.page.title}」(id: ${context.page.id})。指代「这个页面」「改这里」时默认指该页面。`;
|
||||||
}
|
}
|
||||||
|
if (context.selectedAssets?.length === 1) {
|
||||||
|
const asset = context.selectedAssets[0];
|
||||||
|
const kind = inferAssetKindLabel(asset);
|
||||||
|
return `用户在界面勾选了${kind}「${asset.displayName}」(id: ${asset.id})。指代「这个文件」「这份报告」「这个数据」时**必须**指该勾选资料。`;
|
||||||
|
}
|
||||||
|
if (context.selectedAssets?.length > 1) {
|
||||||
|
return `用户在界面同时勾选了 ${context.selectedAssets.length} 份资料。指代「这个 / 这份」时必须先让用户说明要处理哪一份。`;
|
||||||
|
}
|
||||||
if (context.focusedAsset) {
|
if (context.focusedAsset) {
|
||||||
return `用户正在处理资料「${context.focusedAsset.displayName}」(id: ${context.focusedAsset.id})。指代「这个文件」「这份资料」时默认指该资产。`;
|
return `用户正在处理资料「${context.focusedAsset.displayName}」(id: ${context.focusedAsset.id})。指代「这个文件」「这份资料」时默认指该资产。`;
|
||||||
}
|
}
|
||||||
@@ -274,6 +361,8 @@ export function buildContextPrefix(context) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lines.push(...buildSelectedAssetInstructions(context));
|
||||||
|
|
||||||
if (context.homeCategories?.length) {
|
if (context.homeCategories?.length) {
|
||||||
lines.push('- 空间分类概览:');
|
lines.push('- 空间分类概览:');
|
||||||
for (const item of context.homeCategories) {
|
for (const item of context.homeCategories) {
|
||||||
@@ -304,6 +393,12 @@ export function formatContextChip(context) {
|
|||||||
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
||||||
return `编辑 · ${context.page.title}${version}`;
|
return `编辑 · ${context.page.title}${version}`;
|
||||||
}
|
}
|
||||||
|
if (context.selectedAssets?.length === 1) {
|
||||||
|
return `已选 · ${context.selectedAssets[0].displayName}`;
|
||||||
|
}
|
||||||
|
if (context.selectedAssets?.length > 1) {
|
||||||
|
return `已选 ${context.selectedAssets.length} 项资料`;
|
||||||
|
}
|
||||||
if (context.page) {
|
if (context.page) {
|
||||||
const version =
|
const version =
|
||||||
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
||||||
@@ -320,4 +415,6 @@ export function formatContextChip(context) {
|
|||||||
export const mindspaceChatContextInternals = {
|
export const mindspaceChatContextInternals = {
|
||||||
stripHtml,
|
stripHtml,
|
||||||
excerptContent,
|
excerptContent,
|
||||||
|
inferAssetKindLabel,
|
||||||
|
normalizeSelectedAsset,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import test from 'node:test';
|
|||||||
import {
|
import {
|
||||||
buildContextPrefix,
|
buildContextPrefix,
|
||||||
buildMindSpaceChatContext,
|
buildMindSpaceChatContext,
|
||||||
|
buildSelectedAssetGreeting,
|
||||||
formatContextChip,
|
formatContextChip,
|
||||||
mindspaceChatContextInternals,
|
mindspaceChatContextInternals,
|
||||||
} from './mindspace-chat-context.mjs';
|
} from './mindspace-chat-context.mjs';
|
||||||
@@ -169,3 +170,103 @@ test('excerptContent strips html and truncates long text', () => {
|
|||||||
`${'a'.repeat(9)}…`,
|
`${'a'.repeat(9)}…`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('buildMindSpaceChatContext exposes single selected asset as focused asset', () => {
|
||||||
|
const asset = {
|
||||||
|
id: 'asset-9',
|
||||||
|
displayName: 'Q1-report.pdf',
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
filename: 'Q1-report.pdf',
|
||||||
|
sizeBytes: 1200,
|
||||||
|
categoryCode: 'oa',
|
||||||
|
assetType: 'file',
|
||||||
|
};
|
||||||
|
const context = buildMindSpaceChatContext({
|
||||||
|
space: sampleSpace,
|
||||||
|
selectedCategory: sampleCategory,
|
||||||
|
selectedPageId: null,
|
||||||
|
pages: [],
|
||||||
|
assets: [asset],
|
||||||
|
selectedAssets: [asset],
|
||||||
|
route: '/space?category=oa',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(context.selectedAssets?.length, 1);
|
||||||
|
assert.equal(context.focusedAsset?.id, 'asset-9');
|
||||||
|
assert.equal(formatContextChip(context), '已选 · Q1-report.pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildContextPrefix binds deictic references to a single checked asset', () => {
|
||||||
|
const asset = {
|
||||||
|
id: 'asset-9',
|
||||||
|
displayName: 'sales.xlsx',
|
||||||
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
filename: 'sales.xlsx',
|
||||||
|
categoryCode: 'oa',
|
||||||
|
};
|
||||||
|
const prefix = buildContextPrefix(
|
||||||
|
buildMindSpaceChatContext({
|
||||||
|
space: sampleSpace,
|
||||||
|
selectedCategory: sampleCategory,
|
||||||
|
selectedPageId: null,
|
||||||
|
pages: [],
|
||||||
|
assets: [asset],
|
||||||
|
selectedAssets: [asset],
|
||||||
|
route: '/space?category=oa',
|
||||||
|
h5ApiBase: 'http://127.0.0.1:8081',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.match(prefix, /【界面勾选资料(硬性)】/);
|
||||||
|
assert.match(prefix, /sales\.xlsx(id: asset-9/);
|
||||||
|
assert.match(prefix, /DELETE \/api\/mindspace\/v1\/assets\/asset-9/);
|
||||||
|
assert.match(prefix, /指代「这个文件」「这份报告」「这个数据」时\*\*必须\*\*指该勾选资料/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildContextPrefix requires clarification when multiple assets are checked', () => {
|
||||||
|
const assets = [
|
||||||
|
{ id: 'asset-a', displayName: 'a.pdf', mimeType: 'application/pdf' },
|
||||||
|
{ id: 'asset-b', displayName: 'b.csv', mimeType: 'text/csv' },
|
||||||
|
];
|
||||||
|
const prefix = buildContextPrefix(
|
||||||
|
buildMindSpaceChatContext({
|
||||||
|
space: sampleSpace,
|
||||||
|
selectedCategory: sampleCategory,
|
||||||
|
selectedPageId: null,
|
||||||
|
pages: [],
|
||||||
|
assets,
|
||||||
|
selectedAssets: assets,
|
||||||
|
route: '/space?category=oa',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.match(prefix, /同时勾选了 2 份资料/);
|
||||||
|
assert.match(prefix, /必须先追问/);
|
||||||
|
assert.doesNotMatch(prefix, /当前聚焦资料/);
|
||||||
|
assert.equal(formatContextChip(buildMindSpaceChatContext({
|
||||||
|
space: sampleSpace,
|
||||||
|
selectedCategory: sampleCategory,
|
||||||
|
selectedPageId: null,
|
||||||
|
pages: [],
|
||||||
|
assets,
|
||||||
|
selectedAssets: assets,
|
||||||
|
route: '/space?category=oa',
|
||||||
|
})), '已选 2 项资料');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSelectedAssetGreeting adapts wording to asset kind', () => {
|
||||||
|
assert.match(
|
||||||
|
buildSelectedAssetGreeting({
|
||||||
|
displayName: 'sales.xlsx',
|
||||||
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
}),
|
||||||
|
/这份数据表/,
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
buildSelectedAssetGreeting({
|
||||||
|
displayName: 'review.pdf',
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
}),
|
||||||
|
/这份报告/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ export function ChatPanel({
|
|||||||
: chatState === 'waiting'
|
: chatState === 'waiting'
|
||||||
? '消息已收到,正在连接后台…'
|
? '消息已收到,正在连接后台…'
|
||||||
: compact
|
: compact
|
||||||
? '在这里继续和 Agent 对话…'
|
? '随时召唤你的小助手吧'
|
||||||
: randomPrompt;
|
: randomPrompt;
|
||||||
|
|
||||||
const mergeVoiceText = (spoken: string) => {
|
const mergeVoiceText = (spoken: string) => {
|
||||||
|
|||||||
@@ -1,9 +1,29 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useChat } from '../context/ChatProvider';
|
import { useChat } from '../context/ChatProvider';
|
||||||
import type { MindSpaceChatContext, MindSpaceSaveCategory, PortalUser } from '../types';
|
import { buildSelectedAssetGreeting } from '../utils/mindspaceChatContext';
|
||||||
|
import type { Message, MindSpaceChatContext, MindSpaceSaveCategory, PortalUser } from '../types';
|
||||||
import { SpaceChatFab } from './SpaceChatFab';
|
import { SpaceChatFab } from './SpaceChatFab';
|
||||||
import { SpaceChatPanel } from './SpaceChatPanel';
|
import { SpaceChatPanel } from './SpaceChatPanel';
|
||||||
|
|
||||||
|
const SELECTED_ASSET_GREETING_ID = 'mindspace-selected-asset-greeting';
|
||||||
|
|
||||||
|
function buildSelectedAssetGreetingMessage(
|
||||||
|
asset: NonNullable<MindSpaceChatContext['selectedAssets']>[number],
|
||||||
|
): Message {
|
||||||
|
const text = buildSelectedAssetGreeting(asset);
|
||||||
|
return {
|
||||||
|
id: SELECTED_ASSET_GREETING_ID,
|
||||||
|
role: 'assistant',
|
||||||
|
created: Math.floor(Date.now() / 1000),
|
||||||
|
content: [{ type: 'text', text }],
|
||||||
|
metadata: {
|
||||||
|
userVisible: true,
|
||||||
|
agentVisible: false,
|
||||||
|
displayText: text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function MindSpaceSpaceChat({
|
export function MindSpaceSpaceChat({
|
||||||
context,
|
context,
|
||||||
user,
|
user,
|
||||||
@@ -31,15 +51,64 @@ export function MindSpaceSpaceChat({
|
|||||||
hideOpenFullChat?: boolean;
|
hideOpenFullChat?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
const { chatState, pendingTool } = useChat();
|
const { chatState, pendingTool, newSession, switchSession, session, messages } = useChat();
|
||||||
const [internalOpen, setInternalOpen] = useState(false);
|
const [internalOpen, setInternalOpen] = useState(false);
|
||||||
const open = controlledOpen ?? internalOpen;
|
const open = controlledOpen ?? internalOpen;
|
||||||
const setOpen = onOpenChange ?? setInternalOpen;
|
const setOpen = onOpenChange ?? setInternalOpen;
|
||||||
|
const wasOpenRef = useRef(false);
|
||||||
|
const restoreSessionIdRef = useRef<string | null>(null);
|
||||||
|
const messagesRef = useRef(messages);
|
||||||
|
messagesRef.current = messages;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatBridge) {
|
||||||
|
wasOpenRef.current = open;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasOpen = wasOpenRef.current;
|
||||||
|
if (open && !wasOpen) {
|
||||||
|
restoreSessionIdRef.current = session?.id ?? null;
|
||||||
|
newSession();
|
||||||
|
} else if (!open && wasOpen) {
|
||||||
|
if (messagesRef.current.length === 0 && restoreSessionIdRef.current) {
|
||||||
|
switchSession(restoreSessionIdRef.current);
|
||||||
|
}
|
||||||
|
restoreSessionIdRef.current = null;
|
||||||
|
}
|
||||||
|
wasOpenRef.current = open;
|
||||||
|
}, [chatBridge, newSession, open, session?.id, switchSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatBridge) return;
|
||||||
|
return () => {
|
||||||
|
if (messagesRef.current.length === 0 && restoreSessionIdRef.current) {
|
||||||
|
switchSession(restoreSessionIdRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [chatBridge, switchSession]);
|
||||||
|
|
||||||
|
const prefillMessages = useMemo(() => {
|
||||||
|
if (chatBridge || !open) return [];
|
||||||
|
if (messages.some((message) => message.role === 'user')) return [];
|
||||||
|
const selected = context.selectedAssets ?? [];
|
||||||
|
if (selected.length !== 1) return [];
|
||||||
|
return [buildSelectedAssetGreetingMessage(selected[0])];
|
||||||
|
}, [chatBridge, context.selectedAssets, messages, open]);
|
||||||
|
|
||||||
const wrapperClass =
|
const wrapperClass =
|
||||||
variant === 'overlay' ? 'space-chat-stack space-chat-stack-overlay' : 'space-chat-stack';
|
variant === 'overlay' ? 'space-chat-stack space-chat-stack-overlay' : 'space-chat-stack';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClass}>
|
<div className={wrapperClass}>
|
||||||
|
{open ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="space-chat-backdrop"
|
||||||
|
aria-label="收起对话"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<SpaceChatFab
|
<SpaceChatFab
|
||||||
open={open}
|
open={open}
|
||||||
streaming={chatState === 'streaming'}
|
streaming={chatState === 'streaming'}
|
||||||
@@ -56,6 +125,7 @@ export function MindSpaceSpaceChat({
|
|||||||
chatBridge={chatBridge}
|
chatBridge={chatBridge}
|
||||||
hideOpenFullChat={hideOpenFullChat}
|
hideOpenFullChat={hideOpenFullChat}
|
||||||
title={title}
|
title={title}
|
||||||
|
prefillMessages={prefillMessages}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -667,6 +667,14 @@ export function MindSpaceView({
|
|||||||
return () => document.removeEventListener('pointerdown', handlePointerDown);
|
return () => document.removeEventListener('pointerdown', handlePointerDown);
|
||||||
}, [openAssetMenuId]);
|
}, [openAssetMenuId]);
|
||||||
|
|
||||||
|
const selectedAssetsForChat = useMemo(
|
||||||
|
() =>
|
||||||
|
selectedAssetIds
|
||||||
|
.map((id) => assets.find((asset) => asset.id === id))
|
||||||
|
.filter((asset): asset is MindSpaceAsset => Boolean(asset)),
|
||||||
|
[selectedAssetIds, assets],
|
||||||
|
);
|
||||||
|
|
||||||
const mindspaceChatContext = useMemo(() => {
|
const mindspaceChatContext = useMemo(() => {
|
||||||
if (!space) return null;
|
if (!space) return null;
|
||||||
return buildMindSpaceChatContext({
|
return buildMindSpaceChatContext({
|
||||||
@@ -677,7 +685,7 @@ export function MindSpaceView({
|
|||||||
pages,
|
pages,
|
||||||
pageLive: pageLiveContext,
|
pageLive: pageLiveContext,
|
||||||
assets,
|
assets,
|
||||||
focusedAsset: agentAsset,
|
selectedAssets: selectedAssetsForChat,
|
||||||
route: `${location.pathname}${location.search}`,
|
route: `${location.pathname}${location.search}`,
|
||||||
agentSessionId: session?.id ?? null,
|
agentSessionId: session?.id ?? null,
|
||||||
h5ApiBase,
|
h5ApiBase,
|
||||||
@@ -690,7 +698,7 @@ export function MindSpaceView({
|
|||||||
pages,
|
pages,
|
||||||
pageLiveContext,
|
pageLiveContext,
|
||||||
assets,
|
assets,
|
||||||
agentAsset,
|
selectedAssetsForChat,
|
||||||
location.pathname,
|
location.pathname,
|
||||||
location.search,
|
location.search,
|
||||||
session?.id,
|
session?.id,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useChat } from '../context/ChatProvider';
|
import { useChat } from '../context/ChatProvider';
|
||||||
import { INSUFFICIENT_BALANCE_NOTICE } from '../hooks/useTKMindChat';
|
import { INSUFFICIENT_BALANCE_NOTICE } from '../hooks/useTKMindChat';
|
||||||
import type { PageEditSubChatBridge } from '../hooks/usePageEditSubChat';
|
import type { PageEditSubChatBridge } from '../hooks/usePageEditSubChat';
|
||||||
import type { MindSpaceChatContext, MindSpaceSaveCategory, PortalUser } from '../types';
|
import type { MindSpaceChatContext, MindSpaceSaveCategory, PortalUser, Message } from '../types';
|
||||||
import { formatContextChip } from '../utils/mindspaceChatContext';
|
import { formatContextChip } from '../utils/mindspaceChatContext';
|
||||||
import { ChatPanel } from './ChatPanel';
|
import { ChatPanel } from './ChatPanel';
|
||||||
import { TKMindAvatar } from './TKMindAvatar';
|
import { TKMindAvatar } from './TKMindAvatar';
|
||||||
@@ -17,6 +17,7 @@ export function SpaceChatPanel({
|
|||||||
chatBridge,
|
chatBridge,
|
||||||
hideOpenFullChat = false,
|
hideOpenFullChat = false,
|
||||||
title = 'TKMind',
|
title = 'TKMind',
|
||||||
|
prefillMessages = [],
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
context: MindSpaceChatContext;
|
context: MindSpaceChatContext;
|
||||||
@@ -31,6 +32,7 @@ export function SpaceChatPanel({
|
|||||||
chatBridge?: PageEditSubChatBridge;
|
chatBridge?: PageEditSubChatBridge;
|
||||||
hideOpenFullChat?: boolean;
|
hideOpenFullChat?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
prefillMessages?: Message[];
|
||||||
}) {
|
}) {
|
||||||
const mainChat = useChat();
|
const mainChat = useChat();
|
||||||
const chat = chatBridge ?? mainChat;
|
const chat = chatBridge ?? mainChat;
|
||||||
@@ -59,6 +61,15 @@ export function SpaceChatPanel({
|
|||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [open, onClose]);
|
}, [open, onClose]);
|
||||||
|
|
||||||
|
const displayMessages = useMemo(() => {
|
||||||
|
if (!prefillMessages.length) return messages;
|
||||||
|
if (messages.some((message) => message.role === 'user')) return messages;
|
||||||
|
const extras = prefillMessages.filter(
|
||||||
|
(item) => !messages.some((message) => message.id && message.id === item.id),
|
||||||
|
);
|
||||||
|
return extras.length ? [...messages, ...extras] : messages;
|
||||||
|
}, [messages, prefillMessages]);
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
const contextLabel = formatContextChip(context);
|
const contextLabel = formatContextChip(context);
|
||||||
@@ -119,7 +130,7 @@ export function SpaceChatPanel({
|
|||||||
<ChatPanel
|
<ChatPanel
|
||||||
variant="compact"
|
variant="compact"
|
||||||
user={user}
|
user={user}
|
||||||
messages={messages}
|
messages={displayMessages}
|
||||||
chatState={chatState}
|
chatState={chatState}
|
||||||
pendingTool={pendingTool}
|
pendingTool={pendingTool}
|
||||||
session={session}
|
session={session}
|
||||||
@@ -132,7 +143,6 @@ export function SpaceChatPanel({
|
|||||||
onStop={stop}
|
onStop={stop}
|
||||||
onApproveTool={approveTool}
|
onApproveTool={approveTool}
|
||||||
onPageSaved={onPageSaved}
|
onPageSaved={onPageSaved}
|
||||||
onClose={onClose}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+271
-18
@@ -8769,11 +8769,12 @@ body,
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
color: inherit;
|
color: #18211d;
|
||||||
background: #fffaf0;
|
background:
|
||||||
|
linear-gradient(145deg, rgba(255, 252, 244, 0.98), rgba(229, 237, 226, 0.96));
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 10px 30px rgba(24, 33, 29, 0.18),
|
0 10px 30px rgba(24, 33, 29, 0.16),
|
||||||
0 0 0 1px rgba(47, 111, 87, 0.18);
|
0 0 0 1px rgba(47, 111, 87, 0.16);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||||
}
|
}
|
||||||
@@ -8782,6 +8783,16 @@ body,
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-chat-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 899;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.space-chat-fab.is-streaming {
|
.space-chat-fab.is-streaming {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 10px 30px rgba(47, 111, 87, 0.28),
|
0 10px 30px rgba(47, 111, 87, 0.28),
|
||||||
@@ -8815,15 +8826,59 @@ body,
|
|||||||
right: 24px;
|
right: 24px;
|
||||||
bottom: 92px;
|
bottom: 92px;
|
||||||
z-index: 900;
|
z-index: 900;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
flex-direction: column;
|
||||||
width: min(480px, calc(100vw - 32px));
|
width: min(480px, calc(100vw - 32px));
|
||||||
height: min(620px, calc(100vh - 120px));
|
height: min(620px, calc(100vh - 120px));
|
||||||
|
max-height: min(620px, calc(100vh - 120px));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid rgba(24, 33, 29, 0.12);
|
border: 1px solid rgba(24, 33, 29, 0.14);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
background: rgba(255, 252, 244, 0.98);
|
color: #18211d;
|
||||||
box-shadow: 0 24px 60px rgba(24, 33, 29, 0.18);
|
background:
|
||||||
|
linear-gradient(160deg, rgba(255, 252, 244, 0.98) 0%, rgba(244, 240, 231, 0.98) 52%, rgba(229, 237, 226, 0.94) 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 24px 60px rgba(24, 33, 29, 0.16),
|
||||||
|
0 0 0 1px rgba(47, 111, 87, 0.08);
|
||||||
|
|
||||||
|
--ms-ink: #18211d;
|
||||||
|
--ms-muted: #6d7771;
|
||||||
|
--ms-soft: #52605a;
|
||||||
|
--ms-green: #2f6f57;
|
||||||
|
--ms-green-deep: #245742;
|
||||||
|
--ms-cream: #fffaf0;
|
||||||
|
--ms-paper: rgba(255, 252, 244, 0.96);
|
||||||
|
--ms-line: rgba(24, 33, 29, 0.12);
|
||||||
|
--ms-line-soft: rgba(24, 33, 29, 0.08);
|
||||||
|
|
||||||
|
--color-bg-base: #f4f0e7;
|
||||||
|
--color-bg-surface: rgba(255, 255, 255, 0.82);
|
||||||
|
--color-bg-elevated: var(--ms-paper);
|
||||||
|
--color-bg-active: rgba(47, 111, 87, 0.12);
|
||||||
|
--color-bg-code: rgba(24, 33, 29, 0.06);
|
||||||
|
--color-bg-warning: rgba(238, 176, 78, 0.14);
|
||||||
|
--color-bg-error: rgba(214, 69, 69, 0.1);
|
||||||
|
--color-bg-info: rgba(47, 111, 87, 0.1);
|
||||||
|
--color-bg-hover: rgba(24, 33, 29, 0.06);
|
||||||
|
|
||||||
|
--color-text-primary: var(--ms-ink);
|
||||||
|
--color-text-secondary: var(--ms-soft);
|
||||||
|
--color-text-muted: var(--ms-muted);
|
||||||
|
--color-text-faint: #8a958f;
|
||||||
|
--color-text-meta: #8a958f;
|
||||||
|
--color-text-link: var(--ms-green);
|
||||||
|
--color-text-error: #8a1f1f;
|
||||||
|
--color-text-info: var(--ms-green-deep);
|
||||||
|
|
||||||
|
--color-border: var(--ms-line);
|
||||||
|
--color-border-input: rgba(24, 33, 29, 0.16);
|
||||||
|
--color-border-strong: rgba(47, 111, 87, 0.28);
|
||||||
|
--color-border-warning: rgba(238, 176, 78, 0.35);
|
||||||
|
--color-border-input-focus: rgba(47, 111, 87, 0.42);
|
||||||
|
|
||||||
|
--color-accent: var(--ms-green);
|
||||||
|
--color-accent-user: var(--ms-green-deep);
|
||||||
|
--color-danger: #c4453d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-header {
|
.space-chat-panel-header {
|
||||||
@@ -8831,8 +8886,11 @@ body,
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border-bottom: 1px solid rgba(24, 33, 29, 0.08);
|
border-bottom: 1px solid var(--ms-line-soft);
|
||||||
|
background: rgba(255, 252, 244, 0.72);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-brand {
|
.space-chat-panel-brand {
|
||||||
@@ -8845,12 +8903,13 @@ body,
|
|||||||
.space-chat-panel-brand strong {
|
.space-chat-panel-brand strong {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--ms-ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-context-chip {
|
.space-chat-context-chip {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #6d7771;
|
color: var(--ms-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -8868,11 +8927,29 @@ body,
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
color: #52605a;
|
color: var(--ms-soft);
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
background: rgba(24, 33, 29, 0.06);
|
background: rgba(24, 33, 29, 0.06);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-close:hover {
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
background: rgba(47, 111, 87, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-actions .ghost-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
color: var(--ms-soft);
|
||||||
|
border: 1px solid rgba(24, 33, 29, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-actions .ghost-btn:hover:not(:disabled) {
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
border-color: rgba(47, 111, 87, 0.24);
|
||||||
|
background: rgba(47, 111, 87, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-error {
|
.space-chat-panel-error {
|
||||||
@@ -8880,6 +8957,7 @@ body,
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
color: #8a1f1f;
|
color: #8a1f1f;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -8887,8 +8965,11 @@ body,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-body {
|
.space-chat-panel-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 12px 14px 0;
|
padding: 12px 14px 0;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-body .msg-content {
|
.space-chat-panel-body .msg-content {
|
||||||
@@ -8910,27 +8991,141 @@ body,
|
|||||||
|
|
||||||
.space-chat-panel-body .empty-state-compact {
|
.space-chat-panel-body .empty-state-compact {
|
||||||
padding: 28px 12px;
|
padding: 28px 12px;
|
||||||
|
color: var(--ms-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-body .empty-state-compact h2 {
|
.space-chat-panel-body .empty-state-compact h2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
color: var(--ms-ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-body .empty-state-compact p {
|
.space-chat-panel-body .empty-state-compact p {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: var(--ms-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-bubble-assistant {
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
color: var(--ms-ink);
|
||||||
|
border-color: rgba(24, 33, 29, 0.1);
|
||||||
|
box-shadow: 0 2px 8px rgba(24, 33, 29, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-bubble-assistant::before {
|
||||||
|
border-right-color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-bubble-user {
|
||||||
|
background: var(--ms-green);
|
||||||
|
color: var(--ms-cream);
|
||||||
|
box-shadow: 0 2px 10px rgba(47, 111, 87, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-bubble-user::before {
|
||||||
|
border-left-color: var(--ms-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .thinking {
|
||||||
|
color: var(--ms-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-footer {
|
.space-chat-panel-footer {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 12px 16px 16px;
|
padding: 12px 16px 16px;
|
||||||
border-top: 1px solid rgba(24, 33, 29, 0.08);
|
border-top: 1px solid var(--ms-line-soft);
|
||||||
background: rgba(255, 252, 244, 0.96);
|
background: rgba(255, 252, 244, 0.82);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-input-shell {
|
||||||
|
border: 1px solid rgba(24, 33, 29, 0.14);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.88);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-input-shell:focus-within {
|
||||||
|
border-color: rgba(47, 111, 87, 0.35);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.65),
|
||||||
|
0 0 0 3px rgba(47, 111, 87, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer textarea.input.chat-input-field {
|
||||||
|
color: var(--ms-ink);
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer textarea.input.chat-input-field::placeholder {
|
||||||
|
color: rgba(109, 119, 113, 0.85);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer textarea.input.chat-input-field:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-image-upload-trigger {
|
||||||
|
color: var(--ms-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-image-upload-trigger:hover:not(:disabled) {
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
background: rgba(47, 111, 87, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .voice-btn {
|
||||||
|
color: var(--ms-soft);
|
||||||
|
border-color: rgba(24, 33, 29, 0.14);
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .voice-btn:hover:not(:disabled) {
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
border-color: rgba(47, 111, 87, 0.24);
|
||||||
|
background: rgba(47, 111, 87, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-skill-trigger {
|
||||||
|
border-color: rgba(24, 33, 29, 0.14);
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
color: var(--ms-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .chat-skill-trigger:hover:not(:disabled) {
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
border-color: rgba(47, 111, 87, 0.24);
|
||||||
|
background: rgba(47, 111, 87, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .send-btn {
|
||||||
|
min-width: 72px;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: var(--ms-cream);
|
||||||
|
background: var(--ms-green);
|
||||||
|
box-shadow: 0 8px 18px rgba(47, 111, 87, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .send-btn:hover:not(:disabled) {
|
||||||
|
background: var(--ms-green-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel-footer .danger-btn {
|
||||||
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-footer .chat-input-row .ghost-btn {
|
.space-chat-panel-footer .chat-input-row .ghost-btn {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: var(--ms-soft);
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
border: 1px solid rgba(24, 33, 29, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-dismiss {
|
.space-chat-panel-dismiss {
|
||||||
@@ -8938,7 +9133,7 @@ body,
|
|||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
color: #6d7771;
|
color: var(--ms-muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background: rgba(24, 33, 29, 0.05);
|
background: rgba(24, 33, 29, 0.05);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -8946,13 +9141,66 @@ body,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.space-chat-panel-dismiss:hover {
|
.space-chat-panel-dismiss:hover {
|
||||||
color: #52605a;
|
color: var(--ms-soft);
|
||||||
background: rgba(24, 33, 29, 0.09);
|
background: rgba(24, 33, 29, 0.09);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-confirm-compact {
|
.space-chat-panel .tool-confirm-compact {
|
||||||
margin: 0 16px 8px;
|
margin: 0 14px 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
color: var(--ms-ink);
|
||||||
|
border-color: rgba(238, 176, 78, 0.35);
|
||||||
|
background: rgba(238, 176, 78, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .tool-confirm-compact .tool-confirm-text {
|
||||||
|
color: var(--ms-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .tool-confirm-compact .tool-confirm-actions button {
|
||||||
|
background: var(--ms-green);
|
||||||
|
color: var(--ms-cream);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .tool-confirm-compact .tool-confirm-actions .danger {
|
||||||
|
background: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-copy {
|
||||||
|
background: rgba(24, 33, 29, 0.06);
|
||||||
|
color: var(--ms-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-copy:hover {
|
||||||
|
background: rgba(47, 111, 87, 0.12);
|
||||||
|
color: var(--ms-green-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .msg-save-page,
|
||||||
|
.space-chat-panel .msg-public-share-link {
|
||||||
|
background: rgba(255, 255, 255, 0.88);
|
||||||
|
border-color: rgba(24, 33, 29, 0.12);
|
||||||
|
color: var(--ms-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .chat-skill-menu {
|
||||||
|
border-color: rgba(24, 33, 29, 0.12);
|
||||||
|
background: rgba(255, 252, 244, 0.98);
|
||||||
|
box-shadow: 0 12px 32px rgba(24, 33, 29, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .chat-skill-menu-item {
|
||||||
|
color: var(--ms-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .chat-skill-menu-item:hover {
|
||||||
|
background: rgba(47, 111, 87, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-chat-panel .chat-history-loader {
|
||||||
|
border-color: rgba(24, 33, 29, 0.1);
|
||||||
|
color: var(--ms-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
@@ -8966,6 +9214,7 @@ body,
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: min(75vh, calc(100vh - 48px));
|
height: min(75vh, calc(100vh - 48px));
|
||||||
|
max-height: min(75vh, calc(100vh - 48px));
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
border-radius: 24px 24px 0 0;
|
border-radius: 24px 24px 0 0;
|
||||||
@@ -9065,6 +9314,10 @@ body,
|
|||||||
z-index: 1010;
|
z-index: 1010;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-chat-stack-overlay .space-chat-backdrop {
|
||||||
|
z-index: 1009;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.mindspace-page-fullscreen-preview-toolbar {
|
.mindspace-page-fullscreen-preview-toolbar {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
@@ -71,7 +71,20 @@ export type MindSpaceChatContext = {
|
|||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
|
filename?: string;
|
||||||
|
sizeBytes?: number;
|
||||||
|
categoryCode?: MindSpaceCategory['code'];
|
||||||
|
assetType?: string;
|
||||||
};
|
};
|
||||||
|
selectedAssets?: Array<{
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
mimeType: string;
|
||||||
|
filename?: string;
|
||||||
|
sizeBytes?: number;
|
||||||
|
categoryCode?: MindSpaceCategory['code'];
|
||||||
|
assetType?: string;
|
||||||
|
}>;
|
||||||
homeCategories?: Array<{
|
homeCategories?: Array<{
|
||||||
code: MindSpaceCategory['code'];
|
code: MindSpaceCategory['code'];
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type { MindSpaceChatContext } from '../types';
|
|||||||
export {
|
export {
|
||||||
buildContextPrefix,
|
buildContextPrefix,
|
||||||
buildMindSpaceChatContext,
|
buildMindSpaceChatContext,
|
||||||
|
buildSelectedAssetGreeting,
|
||||||
formatContextChip,
|
formatContextChip,
|
||||||
} from '../../mindspace-chat-context.mjs';
|
} from '../../mindspace-chat-context.mjs';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ export type BuildMindSpaceChatContextInput = {
|
|||||||
content: string;
|
content: string;
|
||||||
} | null;
|
} | null;
|
||||||
assets?: MindSpaceAsset[];
|
assets?: MindSpaceAsset[];
|
||||||
|
selectedAssets?: MindSpaceAsset[];
|
||||||
focusedAsset?: MindSpaceAsset | null;
|
focusedAsset?: MindSpaceAsset | null;
|
||||||
route: string;
|
route: string;
|
||||||
agentSessionId?: string | null;
|
agentSessionId?: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user