Compare commits
7 Commits
c73f12f044
...
e3b54e8301
| Author | SHA1 | Date |
|---|---|---|
|
|
e3b54e8301 | |
|
|
9758ae8a3a | |
|
|
3d5006af48 | |
|
|
4dbe930775 | |
|
|
dad3888d6c | |
|
|
e3063d94c4 | |
|
|
ad709767ea |
|
|
@ -3,7 +3,7 @@ gsd_state_version: 1.0
|
|||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: Executing Phase 06
|
||||
last_updated: "2026-04-15T09:58:48Z"
|
||||
last_updated: "2026-04-16T06:58:00Z"
|
||||
progress:
|
||||
total_phases: 6
|
||||
completed_phases: 6
|
||||
|
|
@ -51,5 +51,6 @@ See: .planning/PROJECT.md (updated 2026-04-07)
|
|||
| # | Description | Date | Commit | Directory |
|
||||
|---|-------------|------|--------|-----------|
|
||||
| 260415-owq | 归档当前git diff为Phase 06验收后补丁:检查改动、更新06-UAT/06-VERIFICATION/06-SUMMARY(必要时)与STATE,再做原子提交 | 2026-04-15 | atomic | [260415-owq-git-diff-phase-06-06-uat-06-verification](./quick/260415-owq-git-diff-phase-06-06-uat-06-verification/) |
|
||||
| 260416-koe | 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证) | 2026-04-16 | pending | [260416-koe-phase-06](./quick/260416-koe-phase-06/) |
|
||||
|
||||
Last activity: 2026-04-15 - Completed quick task 260415-owq: 归档当前git diff为Phase 06验收后补丁
|
||||
Last activity: 2026-04-16 - Completed quick task 260416-koe: 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
quick_id: 260416-koe
|
||||
type: quick
|
||||
description: 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证)
|
||||
created: 2026-04-16
|
||||
---
|
||||
|
||||
# Quick Plan 260416-koe
|
||||
|
||||
## Task 1: 归档本次 Phase 06 语义修复改动
|
||||
files:
|
||||
- backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py
|
||||
- backend/tests/test_uploads_middleware_core_logic.py
|
||||
action: 将当前已完成的“当前轮 mention 优先解析指代词”修复作为 Phase 06 补丁归档对象记录进 quick 任务。
|
||||
verify:
|
||||
- 不执行自动验证(用户已人工验收通过)
|
||||
done: 归档对象与改动边界清晰可追溯。
|
||||
|
||||
## Task 2: 生成归档摘要文档
|
||||
files:
|
||||
- .planning/quick/260416-koe-phase-06/260416-koe-SUMMARY.md
|
||||
action: 记录修复目标、改动点与验收结论,明确“免验证”决策来源。
|
||||
verify:
|
||||
- SUMMARY 内容覆盖修复思路与关键文件
|
||||
done: 归档说明完整。
|
||||
|
||||
## Task 3: 更新 STATE 快速任务登记
|
||||
files:
|
||||
- .planning/STATE.md
|
||||
action: 在 Quick Tasks Completed 表追加本次归档任务,并更新 Last activity。
|
||||
verify:
|
||||
- 表格新增 260416-koe 行
|
||||
- Last activity 更新到 2026-04-16
|
||||
done: GSD 状态可见本次归档记录。
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
quick_id: 260416-koe
|
||||
description: 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证)
|
||||
completed: 2026-04-16
|
||||
status: completed
|
||||
verification: skipped_by_request
|
||||
---
|
||||
|
||||
# Quick Task 260416-koe Summary
|
||||
|
||||
## What was archived
|
||||
|
||||
- 上传中间件补充“当前轮 mention 优先”语义:当用户使用“这张图/这个文件/this image”等明确指代时,优先绑定当前消息提及文件。
|
||||
- 仅在“当前消息本身提及多个文件”时才建议澄清,降低历史文件干扰。
|
||||
- 增补回归测试,覆盖当前轮 mention 指代优先的上下文注入行为。
|
||||
|
||||
## Acceptance
|
||||
|
||||
- 本次归档按用户指令执行:无需再次验证。
|
||||
- 验收结论来源:用户确认“已验收通过”。
|
||||
|
||||
## Output artifacts
|
||||
|
||||
- backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py
|
||||
- backend/tests/test_uploads_middleware_core_logic.py
|
||||
- .planning/quick/260416-koe-phase-06/260416-koe-PLAN.md
|
||||
- .planning/quick/260416-koe-phase-06/260416-koe-SUMMARY.md
|
||||
- .planning/STATE.md
|
||||
|
||||
## Commit
|
||||
|
||||
- pending (由用户决定提交时机)
|
||||
|
|
@ -187,17 +187,49 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
|
|||
file["sent_source_label"] = "mention"
|
||||
return ordered
|
||||
|
||||
def _create_sent_files_summary(self, sent_files: list[dict]) -> str:
|
||||
def _create_sent_files_summary(
|
||||
self,
|
||||
sent_files: list[dict],
|
||||
current_turn_mentions: list[dict] | None = None,
|
||||
) -> str:
|
||||
"""Create policy block describing unified 'sent files' semantics."""
|
||||
current_turn_mentions = current_turn_mentions or []
|
||||
lines = [
|
||||
"<sent_files_semantics>",
|
||||
"Conversation attachment semantics:",
|
||||
"- Treat uploaded files and mentioned files as one unified concept of files the user has sent.",
|
||||
"- For questions like 'what files did I send' or 'how many files did I send', use the conversation-level union of uploaded + mentioned files.",
|
||||
"- Count unique files by path (deduplicated).",
|
||||
"",
|
||||
"Conversation-level sent files (deduplicated):",
|
||||
]
|
||||
if current_turn_mentions:
|
||||
lines.extend(
|
||||
[
|
||||
"- Current-turn mention priority: if the user says deictic references like 'this image/file' (e.g. '这张图', '这个文件'), bind to files mentioned in the current message first.",
|
||||
"- Only ask for clarification when the current message itself mentions multiple files.",
|
||||
"",
|
||||
"Current message mentioned files (highest priority for deictic references):",
|
||||
]
|
||||
)
|
||||
for file in current_turn_mentions:
|
||||
size_kb = file["size"] / 1024
|
||||
size_str = f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
|
||||
lines.append(
|
||||
f"- {file['filename']} ({size_str}, source: mention)"
|
||||
)
|
||||
lines.append(f" Path: {file['path']}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"Conversation-level sent files (deduplicated):",
|
||||
]
|
||||
)
|
||||
else:
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"Conversation-level sent files (deduplicated):",
|
||||
]
|
||||
)
|
||||
if sent_files:
|
||||
for file in sent_files:
|
||||
size_kb = file["size"] / 1024
|
||||
|
|
@ -364,6 +396,7 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
|
|||
# Get newly uploaded files from the current message's additional_kwargs.files
|
||||
new_files = self._files_from_kwargs(last_message, uploads_dir) or []
|
||||
mention_files = self._mentioned_files_from_messages(messages)
|
||||
current_turn_mentions = self._mentioned_files_from_kwargs(last_message)
|
||||
|
||||
# Collect historical files from the uploads directory (all except the new ones)
|
||||
new_filenames = {f["filename"] for f in new_files}
|
||||
|
|
@ -402,7 +435,7 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
|
|||
# Create context message(s) and prepend to the last human message content.
|
||||
message_parts = [
|
||||
self._create_files_message(new_files, historical_files),
|
||||
self._create_sent_files_summary(sent_files),
|
||||
self._create_sent_files_summary(sent_files, current_turn_mentions),
|
||||
]
|
||||
if mention_files:
|
||||
message_parts.append(self._create_mentions_message(mention_files))
|
||||
|
|
|
|||
|
|
@ -363,6 +363,34 @@ class TestBeforeAgent:
|
|||
assert "history.png" in content
|
||||
assert "source: mention" in content
|
||||
|
||||
def test_current_turn_mention_priority_is_injected_for_deictic_reference(self, tmp_path):
|
||||
mw = _middleware(tmp_path)
|
||||
uploads_dir = _uploads_dir(tmp_path)
|
||||
(uploads_dir / "old-a.jpg").write_bytes(b"a")
|
||||
(uploads_dir / "old-b.jpg").write_bytes(b"b")
|
||||
|
||||
current = _human(
|
||||
"念出这张图片的文件名",
|
||||
files=[
|
||||
{
|
||||
"filename": "target.jpg",
|
||||
"size": 0,
|
||||
"path": "/mnt/user-data/uploads/target.jpg",
|
||||
"status": "uploaded",
|
||||
"ref_kind": "mention",
|
||||
"ref_source": "upload",
|
||||
}
|
||||
],
|
||||
)
|
||||
result = mw.before_agent(self._state(current), _runtime())
|
||||
|
||||
assert result is not None
|
||||
content = result["messages"][-1].content
|
||||
assert "Current-turn mention priority" in content
|
||||
assert "this image/file" in content
|
||||
assert "Current message mentioned files (highest priority for deictic references):" in content
|
||||
assert "target.jpg (0.0 KB, source: mention)" in content
|
||||
|
||||
def test_mentioned_files_do_not_enter_uploaded_files_state(self, tmp_path):
|
||||
mw = _middleware(tmp_path)
|
||||
msg = _human(
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
"@langchain/langgraph-sdk": "^1.5.3",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
|
|
@ -84,6 +85,7 @@
|
|||
"nextra-theme-docs": "^4.6.1",
|
||||
"ogl": "^1.0.11",
|
||||
"pdfjs-dist": "^5.6.205",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable-panels": "^4.4.1",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ importers:
|
|||
'@radix-ui/react-collapsible':
|
||||
specifier: ^1.1.12
|
||||
version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-context-menu':
|
||||
specifier: ^2.2.16
|
||||
version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -200,6 +203,9 @@ importers:
|
|||
pdfjs-dist:
|
||||
specifier: ^5.6.205
|
||||
version: 5.6.205
|
||||
radix-ui:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react:
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.4
|
||||
|
|
@ -1077,6 +1083,45 @@ packages:
|
|||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-accessible-icon@1.1.7':
|
||||
resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-accordion@1.2.12':
|
||||
resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.15':
|
||||
resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
peerDependencies:
|
||||
|
|
@ -1090,6 +1135,32 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-aspect-ratio@1.1.7':
|
||||
resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-avatar@1.1.10':
|
||||
resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-avatar@1.1.11':
|
||||
resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
|
||||
peerDependencies:
|
||||
|
|
@ -1103,6 +1174,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.3':
|
||||
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.12':
|
||||
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
|
||||
peerDependencies:
|
||||
|
|
@ -1138,6 +1222,19 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context-menu@2.2.16':
|
||||
resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.1.2':
|
||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||
peerDependencies:
|
||||
|
|
@ -1226,6 +1323,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-form@0.1.8':
|
||||
resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-hover-card@1.1.15':
|
||||
resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==}
|
||||
peerDependencies:
|
||||
|
|
@ -1253,6 +1363,19 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-label@2.1.7':
|
||||
resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-menu@2.1.16':
|
||||
resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
|
||||
peerDependencies:
|
||||
|
|
@ -1266,6 +1389,71 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-menubar@1.1.16':
|
||||
resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-navigation-menu@1.2.14':
|
||||
resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-one-time-password-field@0.1.8':
|
||||
resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-password-toggle-field@0.1.3':
|
||||
resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popover@1.1.15':
|
||||
resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.8':
|
||||
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||
peerDependencies:
|
||||
|
|
@ -1331,6 +1519,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-progress@1.1.7':
|
||||
resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-progress@1.1.8':
|
||||
resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==}
|
||||
peerDependencies:
|
||||
|
|
@ -1344,6 +1545,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-radio-group@1.3.8':
|
||||
resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11':
|
||||
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||
peerDependencies:
|
||||
|
|
@ -1383,6 +1597,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-separator@1.1.7':
|
||||
resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-separator@1.1.8':
|
||||
resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
|
||||
peerDependencies:
|
||||
|
|
@ -1396,6 +1623,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slider@1.3.6':
|
||||
resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.3':
|
||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||
peerDependencies:
|
||||
|
|
@ -1440,6 +1680,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toast@1.2.15':
|
||||
resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11':
|
||||
resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
|
||||
peerDependencies:
|
||||
|
|
@ -1466,6 +1719,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toolbar@1.1.11':
|
||||
resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8':
|
||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||
peerDependencies:
|
||||
|
|
@ -4535,6 +4801,19 @@ packages:
|
|||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
radix-ui@1.4.3:
|
||||
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
raf@3.4.1:
|
||||
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
|
||||
|
||||
|
|
@ -6273,6 +6552,46 @@ snapshots:
|
|||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -6282,6 +6601,28 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.3(@types/react@19.2.13)(react@19.2.4)
|
||||
|
|
@ -6295,6 +6636,22 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -6329,6 +6686,20 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
|
||||
'@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-context@1.1.2(@types/react@19.2.13)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
|
@ -6414,6 +6785,20 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -6442,6 +6827,15 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
|
||||
'@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -6468,6 +6862,105 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.1
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-remove-scroll: 2.7.2(@types/react@19.2.13)(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -6524,6 +7017,16 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.3(@types/react@19.2.13)(react@19.2.4)
|
||||
|
|
@ -6534,6 +7037,24 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -6597,6 +7118,15 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
|
@ -6606,6 +7136,25 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.1
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-slot@1.2.3(@types/react@19.2.13)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
|
|
@ -6651,6 +7200,26 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -6677,6 +7246,21 @@ snapshots:
|
|||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
|
@ -10347,6 +10931,69 @@ snapshots:
|
|||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4)
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.13
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.13)
|
||||
|
||||
raf@3.4.1:
|
||||
dependencies:
|
||||
performance-now: 2.1.0
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export default function ChatPage() {
|
|||
const currentSlogan = motivationSlogans[
|
||||
sloganIndex % motivationSlogans.length
|
||||
] ?? {
|
||||
text: "来,一起学习工作吧",
|
||||
text: t.chatPage.defaultSlogan,
|
||||
color: "#333333",
|
||||
};
|
||||
const tickerCharacterList = useMemo(() => {
|
||||
|
|
@ -133,7 +133,7 @@ export default function ChatPage() {
|
|||
if (!safeThreadId) {
|
||||
if (!warnedMissingThreadIdRef.current) {
|
||||
warnedMissingThreadIdRef.current = true;
|
||||
toast.error("缺少 thread_id,无法创建会话");
|
||||
toast.error(t.chatPage.missingThreadIdForCreate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -156,9 +156,15 @@ export default function ChatPage() {
|
|||
})
|
||||
.catch(() => {
|
||||
initializedThreadRef.current = null;
|
||||
toast.error("会话创建失败,请稍后重试");
|
||||
toast.error(t.chatPage.createSessionFailed);
|
||||
});
|
||||
}, [apiClient, isNewThread, safeThreadId]);
|
||||
}, [
|
||||
apiClient,
|
||||
isNewThread,
|
||||
safeThreadId,
|
||||
t.chatPage.createSessionFailed,
|
||||
t.chatPage.missingThreadIdForCreate,
|
||||
]);
|
||||
|
||||
// 监听宿主页 selectedSkill 消息
|
||||
const {
|
||||
|
|
@ -183,7 +189,7 @@ export default function ChatPage() {
|
|||
},
|
||||
onFinish: (state) => {
|
||||
if (document.hidden || !document.hasFocus()) {
|
||||
let body = "Conversation finished";
|
||||
let body = t.chatPage.conversationFinished;
|
||||
const lastMessage = state.messages.at(-1);
|
||||
if (lastMessage) {
|
||||
const textContent = textOfMessage(lastMessage);
|
||||
|
|
@ -235,12 +241,13 @@ export default function ChatPage() {
|
|||
? thread.values.title
|
||||
: t.pages.untitled;
|
||||
if (thread.isThreadLoading) {
|
||||
document.title = `Loading... - ${t.pages.appName}`;
|
||||
document.title = `${t.common.loading} - ${t.pages.appName}`;
|
||||
} else {
|
||||
document.title = `${pageTitle} - ${t.pages.appName}`;
|
||||
}
|
||||
}, [
|
||||
isNewThread,
|
||||
t.common.loading,
|
||||
t.pages.newChat,
|
||||
t.pages.untitled,
|
||||
t.pages.appName,
|
||||
|
|
@ -283,7 +290,7 @@ export default function ChatPage() {
|
|||
return;
|
||||
}
|
||||
if (isNewThread && !safeThreadId) {
|
||||
toast.error("缺少 thread_id,无法发送消息");
|
||||
toast.error(t.chatPage.missingThreadIdForSend);
|
||||
return;
|
||||
}
|
||||
setHasSubmitted(true);
|
||||
|
|
@ -299,6 +306,7 @@ export default function ChatPage() {
|
|||
safeThreadId,
|
||||
sendMessage,
|
||||
showWelcomeStyle,
|
||||
t.chatPage.missingThreadIdForSend,
|
||||
],
|
||||
);
|
||||
const handleStop = useCallback(async () => {
|
||||
|
|
@ -407,7 +415,7 @@ export default function ChatPage() {
|
|||
/> */}
|
||||
|
||||
{artifacts?.length > 0 && !artifactsOpen && (
|
||||
<Tooltip content="点击可查看生成的文件结果">
|
||||
<Tooltip content={t.chatPage.viewArtifactsTooltip}>
|
||||
<Button
|
||||
data-testid="artifacts-open-button"
|
||||
className="text-[#150033] hover:text-[#150033]/80"
|
||||
|
|
@ -487,8 +495,8 @@ export default function ChatPage() {
|
|||
{thread.values.artifacts?.length === 0 ? (
|
||||
<ConversationEmptyState
|
||||
icon={<FilesIcon />}
|
||||
title="No artifact selected"
|
||||
description="Select an artifact to view its details"
|
||||
title={t.chatPage.noArtifactSelectedTitle}
|
||||
description={t.chatPage.noArtifactSelectedDescription}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center">
|
||||
|
|
@ -594,10 +602,10 @@ export default function ChatPage() {
|
|||
<DevDialog open={showExitDialog} onOpenChange={setShowExitDialog}>
|
||||
<DevDialogContent>
|
||||
<DevDialogHeader>
|
||||
<DevDialogTitle>提示</DevDialogTitle>
|
||||
<DevDialogTitle>{t.chatPage.exitDialogTitle}</DevDialogTitle>
|
||||
</DevDialogHeader>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
历史记录每七天自动删除,现在将返回欢迎页,是否继续?
|
||||
{t.chatPage.exitDialogDescription}
|
||||
</p>
|
||||
<DevDialogFooter>
|
||||
<Button
|
||||
|
|
@ -605,7 +613,7 @@ export default function ChatPage() {
|
|||
variant="ghost"
|
||||
onClick={() => setShowExitDialog(false)}
|
||||
>
|
||||
取消
|
||||
{t.common.cancel}
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white"
|
||||
|
|
@ -631,7 +639,7 @@ export default function ChatPage() {
|
|||
);
|
||||
}}
|
||||
>
|
||||
确定
|
||||
{t.chatPage.exitDialogConfirm}
|
||||
</Button>
|
||||
</DevDialogFooter>
|
||||
</DevDialogContent>
|
||||
|
|
@ -647,11 +655,11 @@ export default function ChatPage() {
|
|||
<DevDialogContent>
|
||||
<DevDialogHeader>
|
||||
<DevDialogTitle>
|
||||
⚠️ {selectedSkillError?.title ?? "技能加载失败"}
|
||||
⚠️ {selectedSkillError?.title ?? t.chatPage.selectedSkillLoadFailed}
|
||||
</DevDialogTitle>
|
||||
</DevDialogHeader>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{selectedSkillError?.message ?? "发生了未知错误,请稍后重试。"}
|
||||
{selectedSkillError?.message ?? t.chatPage.unknownErrorRetry}
|
||||
</p>
|
||||
<DevDialogFooter singleColumn>
|
||||
<Button
|
||||
|
|
@ -659,7 +667,7 @@ export default function ChatPage() {
|
|||
variant="ghost"
|
||||
onClick={clearSelectedSkillError}
|
||||
>
|
||||
关闭
|
||||
{t.common.close}
|
||||
</Button>
|
||||
</DevDialogFooter>
|
||||
</DevDialogContent>
|
||||
|
|
|
|||
|
|
@ -1153,19 +1153,19 @@ export const PromptInputSubmit = ({
|
|||
|
||||
let Icon = <ArrowUpIcon className="size-4" />;
|
||||
|
||||
let text: string = "发送";
|
||||
let text: string = t.inputBox.submit;
|
||||
|
||||
if (status === "submitted") {
|
||||
Icon = <Loader2Icon className="size-4 animate-spin" />;
|
||||
text = "生成中...";
|
||||
text = t.inputBox.submitting;
|
||||
} else if (status === "streaming") {
|
||||
Icon = <SquareIcon className="size-4" />;
|
||||
text = "停止";
|
||||
text = t.inputBox.stop;
|
||||
} else if (status === "error") {
|
||||
// 没有报错状态,先用error状态代替
|
||||
Icon = <XIcon className="size-4" />;
|
||||
// MARK: 这里后端没有返回错误信息,先写死一个文本
|
||||
text = "发送";
|
||||
text = t.inputBox.submit;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
import { ContextMenu as ContextMenuPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function ContextMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioGroup
|
||||
data-slot="context-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Item
|
||||
data-slot="context-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
data-slot="context-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
data-slot="context-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
data-slot="context-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium text-foreground data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
data-slot="context-menu-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
}
|
||||
|
|
@ -210,8 +210,18 @@ export function ArtifactFileDetail({
|
|||
artifactUrl,
|
||||
fileName,
|
||||
kind: artifactPreviewKind,
|
||||
pdfPreviewMessage: t.artifactPreview.pdfPreviewFailed,
|
||||
unsupportedTypeMessage: t.artifactPreview.unsupportedType,
|
||||
openInNewTabLabel: t.artifactPreview.openInNewTab,
|
||||
});
|
||||
}, [artifactUrl, fileName, artifactPreviewKind]);
|
||||
}, [
|
||||
artifactUrl,
|
||||
fileName,
|
||||
artifactPreviewKind,
|
||||
t.artifactPreview.openInNewTab,
|
||||
t.artifactPreview.pdfPreviewFailed,
|
||||
t.artifactPreview.unsupportedType,
|
||||
]);
|
||||
// Native PDF iframe rendering is intentionally disabled; PDFs are rendered via pdf.js.
|
||||
const artifactViewerSrc = useMemo(() => {
|
||||
return undefined;
|
||||
|
|
@ -954,6 +964,7 @@ function ArtifactPdfPreview({
|
|||
artifactUrl: string;
|
||||
fileName: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
|
|
@ -1033,7 +1044,7 @@ function ArtifactPdfPreview({
|
|||
} catch (err) {
|
||||
console.error("Failed to render pdf preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法预览该 PDF 文件,请下载后查看。");
|
||||
setError(t.artifactPreview.pdfPreviewFailed);
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
|
|
@ -1047,7 +1058,7 @@ function ArtifactPdfPreview({
|
|||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [artifactUrl]);
|
||||
}, [artifactUrl, t.artifactPreview.pdfPreviewFailed]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
@ -1061,7 +1072,7 @@ function ArtifactPdfPreview({
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
在新标签页打开
|
||||
{t.artifactPreview.openInNewTab}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1071,7 +1082,9 @@ function ArtifactPdfPreview({
|
|||
return (
|
||||
<div className={cn("relative overflow-auto bg-[#f8f9fb] p-4", className)}>
|
||||
<div className="mb-3 text-center text-xs text-[#667085]">
|
||||
{pageCount > 0 ? `${fileName} · ${pageCount} page(s)` : fileName}
|
||||
{pageCount > 0
|
||||
? t.artifactPreview.pageCountLabel(fileName, pageCount)
|
||||
: fileName}
|
||||
</div>
|
||||
<div ref={containerRef} />
|
||||
{isLoading && (
|
||||
|
|
@ -1094,6 +1107,7 @@ function ArtifactOfficePreview({
|
|||
artifactUrl: string;
|
||||
fileName: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [sheetNames, setSheetNames] = useState<string[]>([]);
|
||||
|
|
@ -1138,7 +1152,7 @@ function ArtifactOfficePreview({
|
|||
} catch (err) {
|
||||
console.error("Failed to render docx preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法预览该 DOCX 文件。");
|
||||
setError(t.artifactPreview.docxPreviewFailed);
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
|
|
@ -1151,7 +1165,7 @@ function ArtifactOfficePreview({
|
|||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [artifactUrl, canRenderDocx]);
|
||||
}, [artifactUrl, canRenderDocx, t.artifactPreview.docxPreviewFailed]);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
|
|
@ -1186,7 +1200,7 @@ function ArtifactOfficePreview({
|
|||
} catch (err) {
|
||||
console.error("Failed to render xlsx preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法预览该 Excel 文件。");
|
||||
setError(t.artifactPreview.excelPreviewFailed);
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
|
|
@ -1199,7 +1213,7 @@ function ArtifactOfficePreview({
|
|||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [artifactUrl, canRenderXlsx]);
|
||||
}, [artifactUrl, canRenderXlsx, t.artifactPreview.excelPreviewFailed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canRenderXlsx || !activeSheet || !workbookRef.current) {
|
||||
|
|
@ -1213,9 +1227,9 @@ function ArtifactOfficePreview({
|
|||
setXlsxRows(rows);
|
||||
} catch (err) {
|
||||
console.error("Failed to switch xlsx sheet:", err);
|
||||
setError("切换工作表失败。");
|
||||
setError(t.artifactPreview.switchSheetFailed);
|
||||
}
|
||||
}, [activeSheet, canRenderXlsx]);
|
||||
}, [activeSheet, canRenderXlsx, t.artifactPreview.switchSheetFailed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canRenderXlsx || !xlsxGridContainerRef.current) {
|
||||
|
|
@ -1247,7 +1261,7 @@ function ArtifactOfficePreview({
|
|||
} catch (err) {
|
||||
console.error("Failed to render RevoGrid preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法渲染 Excel 网格预览。");
|
||||
setError(t.artifactPreview.excelGridPreviewFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1257,14 +1271,19 @@ function ArtifactOfficePreview({
|
|||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [canRenderXlsx, xlsxColumns, xlsxRows]);
|
||||
}, [
|
||||
canRenderXlsx,
|
||||
xlsxColumns,
|
||||
xlsxRows,
|
||||
t.artifactPreview.excelGridPreviewFailed,
|
||||
]);
|
||||
useEffect(() => {
|
||||
if (!canRenderPptx) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(false);
|
||||
setError("请下载ppt文件以获得最佳效果");
|
||||
}, [canRenderPptx]);
|
||||
setError(t.artifactPreview.pptxDownloadHint);
|
||||
}, [canRenderPptx, t.artifactPreview.pptxDownloadHint]);
|
||||
|
||||
return (
|
||||
<div className={cn("relative h-full overflow-hidden bg-white", className)}>
|
||||
|
|
@ -1328,6 +1347,7 @@ function ArtifactPreviewFallback({
|
|||
fileName: string;
|
||||
artifactUrl: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className="absolute inset-0 z-20 grid place-content-center bg-white p-6 text-center">
|
||||
<p className="text-foreground mb-2 text-sm font-medium">{fileName}</p>
|
||||
|
|
@ -1338,7 +1358,7 @@ function ArtifactPreviewFallback({
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
点击下载
|
||||
{t.artifactPreview.clickToDownload}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1459,13 +1479,22 @@ function buildArtifactViewerSrcDoc({
|
|||
artifactUrl,
|
||||
fileName,
|
||||
kind,
|
||||
pdfPreviewMessage,
|
||||
unsupportedTypeMessage,
|
||||
openInNewTabLabel,
|
||||
}: {
|
||||
artifactUrl: string;
|
||||
fileName: string;
|
||||
kind: ArtifactPreviewKind;
|
||||
pdfPreviewMessage: string;
|
||||
unsupportedTypeMessage: string;
|
||||
openInNewTabLabel: string;
|
||||
}) {
|
||||
const safeUrl = escapeHtml(artifactUrl);
|
||||
const safeName = escapeHtml(fileName);
|
||||
const safePdfPreviewMessage = escapeHtml(pdfPreviewMessage);
|
||||
const safeUnsupportedTypeMessage = escapeHtml(unsupportedTypeMessage);
|
||||
const safeOpenInNewTabLabel = escapeHtml(openInNewTabLabel);
|
||||
|
||||
const content = (() => {
|
||||
if (kind === "image") {
|
||||
|
|
@ -1480,8 +1509,8 @@ function buildArtifactViewerSrcDoc({
|
|||
if (kind === "pdf") {
|
||||
return `<div class="fallback">
|
||||
<p class="title">${safeName}</p>
|
||||
<p class="desc">PDF preview is temporarily disabled. Please download the file to view it.</p>
|
||||
<a class="link" href="${safeUrl}" target="_blank" rel="noopener noreferrer">Open in new tab</a>
|
||||
<p class="desc">${safePdfPreviewMessage}</p>
|
||||
<a class="link" href="${safeUrl}" target="_blank" rel="noopener noreferrer">${safeOpenInNewTabLabel}</a>
|
||||
</div>`;
|
||||
}
|
||||
if (kind === "html") {
|
||||
|
|
@ -1489,8 +1518,8 @@ function buildArtifactViewerSrcDoc({
|
|||
}
|
||||
return `<div class="fallback">
|
||||
<p class="title">${safeName}</p>
|
||||
<p class="desc">This file type is not previewable in the custom viewer.</p>
|
||||
<a class="link" href="${safeUrl}" target="_blank" rel="noopener noreferrer">Open in new tab</a>
|
||||
<p class="desc">${safeUnsupportedTypeMessage}</p>
|
||||
<a class="link" href="${safeUrl}" target="_blank" rel="noopener noreferrer">${safeOpenInNewTabLabel}</a>
|
||||
</div>`;
|
||||
})();
|
||||
|
||||
|
|
@ -1622,6 +1651,7 @@ export const ArtifactZoomSelector = ({
|
|||
className,
|
||||
...props
|
||||
}: ArtifactZoomSelectorProps) => {
|
||||
const { t } = useI18n();
|
||||
const handleZoomIn = () => {
|
||||
const currentIndex = ZOOM_LEVELS.indexOf(value);
|
||||
const nextValue = ZOOM_LEVELS[currentIndex + 1];
|
||||
|
|
@ -1660,7 +1690,7 @@ export const ArtifactZoomSelector = ({
|
|||
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
|
||||
"dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
||||
)}
|
||||
aria-label="放大"
|
||||
aria-label={t.artifactPreview.zoomIn}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -1700,7 +1730,7 @@ export const ArtifactZoomSelector = ({
|
|||
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
|
||||
"dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
||||
)}
|
||||
aria-label="缩小"
|
||||
aria-label={t.artifactPreview.zoomOut}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
|
@ -10,9 +10,16 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { installSkill } from "@/core/skills/api";
|
||||
import { dispatchMentionReference } from "@/core/threads/reference-events";
|
||||
import {
|
||||
getFileExtensionDisplayName,
|
||||
getFileIcon,
|
||||
|
|
@ -78,69 +85,86 @@ export function ArtifactFileList({
|
|||
data-testid="artifact-file-list"
|
||||
>
|
||||
{files.map((file) => (
|
||||
<Card
|
||||
key={file}
|
||||
className="relative cursor-pointer p-4"
|
||||
data-testid="artifact-file-card"
|
||||
onClick={() => handleClick(file)}
|
||||
>
|
||||
<CardHeader className="pr-2 pl-1">
|
||||
<CardTitle className="relative overflow-hidden pl-10">
|
||||
<div
|
||||
className="text-sm font-normal text-ellipsis whitespace-nowrap"
|
||||
title={getFileName(file)}
|
||||
>
|
||||
{truncateMiddle(getFileName(file), 50)}
|
||||
</div>
|
||||
</CardTitle>
|
||||
<div className="absolute top-5 left-4">
|
||||
{getFileIcon(file, "size-9 stroke-[1px] stroke-[#333333]")}
|
||||
</div>
|
||||
<CardDescription className="pl-10 text-xs">
|
||||
{getFileExtensionDisplayName(file)} file
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
{file.endsWith(".skill") && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
disabled={!threadId || installingFile === file}
|
||||
onClick={(e) => handleInstallSkill(e, file)}
|
||||
>
|
||||
{installingFile === file ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : (
|
||||
<PackageIcon className="size-4" />
|
||||
)}
|
||||
{t.common.install}
|
||||
</Button>
|
||||
)}
|
||||
{threadId ? (
|
||||
<a
|
||||
href={urlOfArtifact({
|
||||
filepath: file,
|
||||
threadId,
|
||||
download: true,
|
||||
})}
|
||||
target="_blank"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button variant="ghost"
|
||||
className="h-full! text-[var(--muted-foreground)]! hover:bg-transparent! hover:text-[#333333]!"
|
||||
|
||||
<ContextMenu key={file}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<Card
|
||||
className="relative cursor-pointer p-4"
|
||||
data-testid="artifact-file-card"
|
||||
onClick={() => handleClick(file)}
|
||||
>
|
||||
<CardHeader className="pr-2 pl-1">
|
||||
<CardTitle className="relative overflow-hidden pl-10">
|
||||
<div
|
||||
className="text-sm font-normal text-ellipsis whitespace-nowrap"
|
||||
title={getFileName(file)}
|
||||
>
|
||||
<DownloadIcon className="size-4" />
|
||||
{t.common.download}
|
||||
</Button>
|
||||
</a>
|
||||
) : (
|
||||
<Button variant="ghost" disabled>
|
||||
<DownloadIcon className="size-4" />
|
||||
{t.common.download}
|
||||
</Button>
|
||||
)}
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
{truncateMiddle(getFileName(file), 50)}
|
||||
</div>
|
||||
</CardTitle>
|
||||
<div className="absolute top-5 left-4">
|
||||
{getFileIcon(file, "size-9 stroke-[1px] stroke-[#333333]")}
|
||||
</div>
|
||||
<CardDescription className="pl-10 text-xs">
|
||||
{getFileExtensionDisplayName(file)} file
|
||||
</CardDescription>
|
||||
<CardAction>
|
||||
{file.endsWith(".skill") && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
disabled={!threadId || installingFile === file}
|
||||
onClick={(e) => handleInstallSkill(e, file)}
|
||||
>
|
||||
{installingFile === file ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : (
|
||||
<PackageIcon className="size-4" />
|
||||
)}
|
||||
{t.common.install}
|
||||
</Button>
|
||||
)}
|
||||
{threadId ? (
|
||||
<a
|
||||
href={urlOfArtifact({
|
||||
filepath: file,
|
||||
threadId,
|
||||
download: true,
|
||||
})}
|
||||
target="_blank"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-full! text-[var(--muted-foreground)]! hover:bg-transparent! hover:text-[#333333]!"
|
||||
>
|
||||
<DownloadIcon className="size-4" />
|
||||
{t.common.download}
|
||||
</Button>
|
||||
</a>
|
||||
) : (
|
||||
<Button variant="ghost" disabled>
|
||||
<DownloadIcon className="size-4" />
|
||||
{t.common.download}
|
||||
</Button>
|
||||
)}
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="min-w-[120px] p-1">
|
||||
<ContextMenuItem
|
||||
onSelect={() => {
|
||||
dispatchMentionReference({
|
||||
threadId,
|
||||
filename: getFileName(file),
|
||||
path: file,
|
||||
ref_source: "artifact",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t.common.reference}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const ArtifactTrigger = () => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<Tooltip content="Show artifacts of this conversation">
|
||||
<Tooltip content={t.artifactPreview.showArtifactsTooltip}>
|
||||
<Button
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { env } from "@/env";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ const ChatBox: React.FC<{
|
|||
children: React.ReactNode;
|
||||
threadId: string | undefined;
|
||||
}> = ({ children, threadId }) => {
|
||||
const { t } = useI18n();
|
||||
const { thread } = useThread();
|
||||
const pathname = usePathname();
|
||||
const threadIdRef = useRef(threadId);
|
||||
|
|
@ -152,13 +154,13 @@ const ChatBox: React.FC<{
|
|||
{thread.values.artifacts?.length === 0 ? (
|
||||
<ConversationEmptyState
|
||||
icon={<FilesIcon />}
|
||||
title="No artifact selected"
|
||||
description="Select an artifact to view its details"
|
||||
title={t.chatPage.noArtifactSelectedTitle}
|
||||
description={t.chatPage.noArtifactSelectedDescription}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4 pt-8">
|
||||
<header className="shrink-0">
|
||||
<h2 className="text-lg font-medium">Artifacts</h2>
|
||||
<h2 className="text-lg font-medium">{t.common.artifacts}</h2>
|
||||
</header>
|
||||
<main className="min-h-0 grow">
|
||||
<ArtifactFileList
|
||||
|
|
|
|||
|
|
@ -66,16 +66,20 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tag } from "@/components/ui/tag";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import type { SelectedSkillPayloadItem } from "@/core/i18n/locales/types";
|
||||
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import {
|
||||
MENTION_REFERENCE_EVENT,
|
||||
type MentionReferenceEventDetail,
|
||||
} from "@/core/threads/reference-events";
|
||||
import type { AgentThreadContext } from "@/core/threads";
|
||||
import { useUploadedFiles } from "@/core/uploads/hooks";
|
||||
import { useIframeSkill } from "@/hooks/use-iframe-skill";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
|
||||
import {
|
||||
ModelSelector,
|
||||
|
|
@ -95,11 +99,6 @@ import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.
|
|||
|
||||
const MAX_REFERENCES_PER_MESSAGE = 10;
|
||||
|
||||
const REFERENCE_SOURCE_LABELS = {
|
||||
artifact: "生成文件",
|
||||
upload: "上传附件",
|
||||
} as const;
|
||||
|
||||
type MentionCandidate = {
|
||||
key: string;
|
||||
filename: string;
|
||||
|
|
@ -108,8 +107,33 @@ type MentionCandidate = {
|
|||
ref_source: "artifact" | "upload";
|
||||
ref_kind: "mention";
|
||||
typeLabel: string;
|
||||
isImage: boolean;
|
||||
previewUrl?: string;
|
||||
};
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set([
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"webp",
|
||||
"gif",
|
||||
"bmp",
|
||||
"svg",
|
||||
"avif",
|
||||
]);
|
||||
|
||||
function isImageFilename(filename: string): boolean {
|
||||
const parts = filename.toLowerCase().split(".");
|
||||
if (parts.length < 2) return false;
|
||||
return IMAGE_EXTENSIONS.has(parts[parts.length - 1] ?? "");
|
||||
}
|
||||
|
||||
function fileExtensionLabel(filename: string): string {
|
||||
const parts = filename.split(".");
|
||||
if (parts.length < 2) return "FILE";
|
||||
return (parts[parts.length - 1] ?? "FILE").toUpperCase().slice(0, 4);
|
||||
}
|
||||
|
||||
function getPathTail(path: string | undefined): string {
|
||||
if (!path) return "";
|
||||
const segments = path.split("/").filter(Boolean);
|
||||
|
|
@ -171,6 +195,13 @@ export function InputBox({
|
|||
onStop?: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const referenceSourceLabels = useMemo(
|
||||
() => ({
|
||||
artifact: t.inputBox.referenceSourceArtifact,
|
||||
upload: t.inputBox.referenceSourceUpload,
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
const { thread } = useThread();
|
||||
const searchParams = useSearchParams();
|
||||
const iframeSkill = useIframeSkill({ threadId: threadIdFromProps });
|
||||
|
|
@ -251,7 +282,14 @@ export function InputBox({
|
|||
pathTail: getPathTail(path),
|
||||
ref_source: "artifact" as const,
|
||||
ref_kind: "mention" as const,
|
||||
typeLabel: REFERENCE_SOURCE_LABELS.artifact,
|
||||
typeLabel: referenceSourceLabels.artifact,
|
||||
isImage: isImageFilename(filename),
|
||||
previewUrl: threadId
|
||||
? urlOfArtifact({
|
||||
filepath: path,
|
||||
threadId,
|
||||
})
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -263,7 +301,9 @@ export function InputBox({
|
|||
pathTail: getPathTail(file.virtual_path),
|
||||
ref_source: "upload" as const,
|
||||
ref_kind: "mention" as const,
|
||||
typeLabel: REFERENCE_SOURCE_LABELS.upload,
|
||||
typeLabel: referenceSourceLabels.upload,
|
||||
isImage: isImageFilename(file.filename),
|
||||
previewUrl: file.artifact_url,
|
||||
})) ?? [];
|
||||
|
||||
const deduped = new Map<string, MentionCandidate>();
|
||||
|
|
@ -271,7 +311,13 @@ export function InputBox({
|
|||
deduped.set(candidate.key, candidate);
|
||||
});
|
||||
return [...deduped.values()];
|
||||
}, [thread.values.artifacts, uploadedFilesData?.files]);
|
||||
}, [
|
||||
referenceSourceLabels.artifact,
|
||||
referenceSourceLabels.upload,
|
||||
thread.values.artifacts,
|
||||
uploadedFilesData?.files,
|
||||
threadId,
|
||||
]);
|
||||
|
||||
const filteredMentionCandidates = useMemo(() => {
|
||||
const query = mentionQuery.trim().toLowerCase();
|
||||
|
|
@ -333,28 +379,32 @@ export function InputBox({
|
|||
form?.requestSubmit();
|
||||
}, []);
|
||||
|
||||
const addMentionReference = useCallback((reference: PromptInputReference) => {
|
||||
setReferences((prev) => {
|
||||
const exists = prev.some(
|
||||
(item) =>
|
||||
item.ref_source === reference.ref_source &&
|
||||
item.path === reference.path &&
|
||||
item.filename === reference.filename,
|
||||
);
|
||||
if (exists) {
|
||||
return prev;
|
||||
}
|
||||
if (prev.length >= MAX_REFERENCES_PER_MESSAGE) {
|
||||
toast.error(t.inputBox.maxReferencesReached);
|
||||
return prev;
|
||||
}
|
||||
return prev.concat(reference);
|
||||
});
|
||||
}, [t.inputBox.maxReferencesReached]);
|
||||
|
||||
const selectMentionCandidate = useCallback(
|
||||
(candidate: MentionCandidate) => {
|
||||
setReferences((prev) => {
|
||||
const exists = prev.some(
|
||||
(item) =>
|
||||
item.ref_source === candidate.ref_source &&
|
||||
item.path === candidate.path &&
|
||||
item.filename === candidate.filename,
|
||||
);
|
||||
if (exists) {
|
||||
return prev;
|
||||
}
|
||||
if (prev.length >= MAX_REFERENCES_PER_MESSAGE) {
|
||||
toast.error("单条消息最多引用 10 个文件");
|
||||
return prev;
|
||||
}
|
||||
return prev.concat({
|
||||
filename: candidate.filename,
|
||||
path: candidate.path,
|
||||
ref_kind: "mention",
|
||||
ref_source: candidate.ref_source,
|
||||
});
|
||||
addMentionReference({
|
||||
filename: candidate.filename,
|
||||
path: candidate.path,
|
||||
ref_kind: "mention",
|
||||
ref_source: candidate.ref_source,
|
||||
});
|
||||
|
||||
const current = textInput.value ?? "";
|
||||
|
|
@ -375,9 +425,33 @@ export function InputBox({
|
|||
setMentionRange(null);
|
||||
setIsFocused(true);
|
||||
},
|
||||
[mentionRange, textInput],
|
||||
[addMentionReference, mentionRange, textInput],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onMentionReference = (event: Event) => {
|
||||
const detail = (event as CustomEvent<MentionReferenceEventDetail>).detail;
|
||||
if (!detail || detail.threadId !== threadIdFromProps) {
|
||||
return;
|
||||
}
|
||||
addMentionReference({
|
||||
filename: detail.filename,
|
||||
path: detail.path,
|
||||
ref_kind: "mention",
|
||||
ref_source: detail.ref_source,
|
||||
});
|
||||
setIsFocused(true);
|
||||
requestAnimationFrame(() => {
|
||||
textareaRef.current?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener(MENTION_REFERENCE_EVENT, onMentionReference);
|
||||
return () => {
|
||||
window.removeEventListener(MENTION_REFERENCE_EVENT, onMentionReference);
|
||||
};
|
||||
}, [addMentionReference, threadIdFromProps]);
|
||||
|
||||
const handleTextareaChange = useCallback(
|
||||
(event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = event.currentTarget.value;
|
||||
|
|
@ -613,18 +687,18 @@ export function InputBox({
|
|||
align="start"
|
||||
side="top"
|
||||
sideOffset={8}
|
||||
className="w-[min(32rem,var(--radix-dropdown-menu-trigger-width)+28rem)] p-2"
|
||||
className="w-[min(32rem,var(--radix-dropdown-menu-trigger-width)+28rem)] max-h-[578px] overflow-y-visible p-[20px]"
|
||||
data-testid="mention-candidate-panel"
|
||||
onCloseAutoFocus={(event) => {
|
||||
event.preventDefault();
|
||||
textareaRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 py-1 text-xs text-muted-foreground">
|
||||
添加引用
|
||||
<DropdownMenuLabel className="p-0 text-[14px] text-[#333333]">
|
||||
{t.inputBox.addReference}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSeparator className="mx-0 mt-[20px] mb-0" />
|
||||
<DropdownMenuGroup className="flex pt-[20px] px-0 max-h-[480px] flex-col gap-[10px] overflow-y-auto">
|
||||
{filteredMentionCandidates.slice(0, 20).map((candidate, index) => {
|
||||
const detail = [candidate.typeLabel, candidate.pathTail]
|
||||
.filter(Boolean)
|
||||
|
|
@ -647,6 +721,17 @@ export function InputBox({
|
|||
selectMentionCandidate(candidate);
|
||||
}}
|
||||
>
|
||||
{candidate.isImage && candidate.previewUrl ? (
|
||||
<img
|
||||
src={candidate.previewUrl}
|
||||
alt={candidate.filename}
|
||||
className="h-10 w-10 shrink-0 rounded-md border object-cover object-top"
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-muted text-muted-foreground flex h-10 w-10 shrink-0 items-center justify-center rounded-md border text-[10px] font-semibold">
|
||||
{fileExtensionLabel(candidate.filename)}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-medium">
|
||||
{candidate.filename}
|
||||
|
|
@ -765,7 +850,7 @@ export function InputBox({
|
|||
<div className="flex items-center gap-2">
|
||||
{followupsLoading ? (
|
||||
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-2 text-xs backdrop-blur-sm">
|
||||
加载中...
|
||||
{t.inputBox.followupLoading}
|
||||
</div>
|
||||
) : (
|
||||
<Suggestions className="min-h-16 w-fit items-start">
|
||||
|
|
@ -795,19 +880,21 @@ export function InputBox({
|
|||
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>提示</DialogTitle>
|
||||
<DialogTitle>{t.inputBox.followupConfirmTitle}</DialogTitle>
|
||||
<DialogDescription>
|
||||
请确认要如何处理当前的追加建议内容?
|
||||
{t.inputBox.followupConfirmDescription}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setConfirmOpen(false)}>
|
||||
取消
|
||||
{t.common.cancel}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={confirmAppendAndSend}>
|
||||
追加内容
|
||||
{t.inputBox.followupConfirmAppend}
|
||||
</Button>
|
||||
<Button onClick={confirmReplaceAndSend}>
|
||||
{t.inputBox.followupConfirmReplace}
|
||||
</Button>
|
||||
<Button onClick={confirmReplaceAndSend}>替换发送</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
@ -1088,6 +1175,11 @@ function AttachmentPreviewBar({
|
|||
threadId: string;
|
||||
onRemoveReference: (reference: PromptInputReference) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const referenceSourceLabels = {
|
||||
artifact: t.inputBox.referenceSourceArtifact,
|
||||
upload: t.inputBox.referenceSourceUpload,
|
||||
} as const;
|
||||
const attachments = usePromptInputAttachments();
|
||||
const hasReferences = references.length > 0;
|
||||
const hasAttachmentFiles = attachments.files.length > 0;
|
||||
|
|
@ -1137,7 +1229,7 @@ function AttachmentPreviewBar({
|
|||
}}
|
||||
data-testid="reference-chip"
|
||||
onRemove={() => onRemoveReference(reference)}
|
||||
title={`${REFERENCE_SOURCE_LABELS[reference.ref_source]}${reference.path ? ` · ${getPathTail(reference.path)}` : ""}`}
|
||||
title={`${referenceSourceLabels[reference.ref_source]}${reference.path ? ` · ${getPathTail(reference.path)}` : ""}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -263,8 +263,9 @@ function ToolCall({
|
|||
language?: BundledLanguage;
|
||||
expanded?: boolean;
|
||||
}) => {
|
||||
// Always start collapsed in thinking blocks; user must explicitly expand.
|
||||
const shouldShowCodeBlock = expanded;
|
||||
// During streaming, never render code block content in thinking area.
|
||||
// Code is only available for expand/collapse after streaming is complete.
|
||||
const shouldShowCodeBlock = !isLoading && expanded;
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
|
|
@ -462,8 +463,10 @@ function ToolCall({
|
|||
<Button
|
||||
className="h-7 px-3 text-xs"
|
||||
variant="ghost"
|
||||
disabled={isLoading}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
if (isLoading) return;
|
||||
setIsCommandExpanded((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ import {
|
|||
import { Task, TaskTrigger } from "@/components/ai-elements/task";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { resolveArtifactURL } from "@/core/artifacts/utils";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import {
|
||||
|
|
@ -27,6 +33,7 @@ import {
|
|||
stripUploadedFilesTag,
|
||||
type FileInMessage,
|
||||
} from "@/core/messages/utils";
|
||||
import { dispatchMentionReference } from "@/core/threads/reference-events";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { materializeSkillYaml } from "@/core/skills";
|
||||
import { humanMessagePlugins } from "@/core/streamdown";
|
||||
|
|
@ -380,30 +387,57 @@ function RichFileCard({
|
|||
clear_target: true,
|
||||
});
|
||||
setMaterializeMessage(
|
||||
`已创建 ${result.created_files} 个文件 / ${result.created_directories} 个目录`,
|
||||
t.messageListItem.materializeSuccess(
|
||||
result.created_files,
|
||||
result.created_directories,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "解析失败";
|
||||
setMaterializeMessage(`失败: ${message}`);
|
||||
const message =
|
||||
error instanceof Error ? error.message : t.messageListItem.parseFailed;
|
||||
setMaterializeMessage(t.messageListItem.materializeFailed(message));
|
||||
} finally {
|
||||
setIsMaterializing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isImage) {
|
||||
const refSource = file.ref_source ?? "upload";
|
||||
const canReference = Boolean(file.path);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={fileUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group border-border/40 relative block overflow-hidden rounded-lg border"
|
||||
>
|
||||
<img
|
||||
src={fileUrl}
|
||||
alt={file.filename}
|
||||
className="h-32 w-auto max-w-[240px] object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
</a>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>
|
||||
<a
|
||||
href={fileUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group border-border/40 relative block overflow-hidden rounded-lg border"
|
||||
>
|
||||
<img
|
||||
src={fileUrl}
|
||||
alt={file.filename}
|
||||
className="h-32 w-auto max-w-[240px] object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
</a>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="min-w-[120px] p-1">
|
||||
<ContextMenuItem
|
||||
disabled={!canReference}
|
||||
onSelect={() => {
|
||||
if (!file.path) return;
|
||||
dispatchMentionReference({
|
||||
threadId,
|
||||
filename: file.filename,
|
||||
path: file.path,
|
||||
ref_source: refSource,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t.common.reference}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -440,7 +474,9 @@ function RichFileCard({
|
|||
}}
|
||||
disabled={isMaterializing}
|
||||
>
|
||||
{isMaterializing ? "解析中..." : "一键导入为 Skill 目录"}
|
||||
{isMaterializing
|
||||
? t.messageListItem.materializing
|
||||
: t.messageListItem.importAsSkillDir}
|
||||
</Button>
|
||||
{materializeMessage && (
|
||||
<span className="text-muted-foreground text-[10px] leading-tight">
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ export function MessageList({
|
|||
"z-20 rounded-full border bg-white/90 shadow-sm backdrop-blur-sm",
|
||||
scrollButtonClassName,
|
||||
)}
|
||||
title="滚动到底部"
|
||||
title={t.chats.scrollToBottom}
|
||||
/>
|
||||
)}
|
||||
</Conversation>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
|
|||
<div className="flex items-center justify-between gap-2">
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ? (
|
||||
<Link href="/" className="text-primary ml-2 font-serif">
|
||||
XClaw侧边栏
|
||||
{t.workspaceHeader.sidebarTitle}
|
||||
</Link>
|
||||
) : (
|
||||
<div className="text-primary ml-2 cursor-default font-serif">
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export const enUS: Translations = {
|
|||
exportAsJSON: "Export as JSON",
|
||||
exportSuccess: "Conversation exported",
|
||||
removeAttachment: "Remove attachment",
|
||||
reference: "Reference",
|
||||
},
|
||||
|
||||
// Welcome
|
||||
|
|
@ -115,6 +116,13 @@ export const enUS: Translations = {
|
|||
"You already have text in the input. Choose how to send it.",
|
||||
followupConfirmAppend: "Append & send",
|
||||
followupConfirmReplace: "Replace & send",
|
||||
submit: "Send",
|
||||
submitting: "Generating...",
|
||||
stop: "Stop",
|
||||
addReference: "Add reference",
|
||||
referenceSourceArtifact: "Generated file",
|
||||
referenceSourceUpload: "Uploaded attachment",
|
||||
maxReferencesReached: "You can reference up to 10 files per message",
|
||||
suggestions: [
|
||||
{
|
||||
suggestion: "Paper Writing",
|
||||
|
|
@ -248,6 +256,87 @@ export const enUS: Translations = {
|
|||
// Chats
|
||||
chats: {
|
||||
searchChats: "Search chats",
|
||||
scrollToBottom: "Scroll to bottom",
|
||||
},
|
||||
|
||||
// Workspace Chat Page
|
||||
chatPage: {
|
||||
defaultSlogan: "Let's study and work together",
|
||||
missingThreadIdForCreate: "Missing thread_id, cannot create session",
|
||||
createSessionFailed: "Failed to create session, please try again later",
|
||||
conversationFinished: "Conversation finished",
|
||||
missingThreadIdForSend: "Missing thread_id, cannot send message",
|
||||
viewArtifactsTooltip: "Click to view generated artifacts",
|
||||
noArtifactSelectedTitle: "No artifact selected",
|
||||
noArtifactSelectedDescription: "Select an artifact to view its details",
|
||||
exitDialogTitle: "Notice",
|
||||
exitDialogDescription:
|
||||
"Chat history is automatically deleted every seven days. You will return to the welcome page now. Continue?",
|
||||
exitDialogConfirm: "Confirm",
|
||||
selectedSkillLoadFailed: "Failed to load skill",
|
||||
unknownErrorRetry: "An unknown error occurred. Please try again later.",
|
||||
},
|
||||
|
||||
messageListItem: {
|
||||
materializing: "Parsing...",
|
||||
importAsSkillDir: "Import as Skill directory",
|
||||
materializeSuccess: (files: number, directories: number) =>
|
||||
`Created ${files} file(s) / ${directories} director${directories === 1 ? "y" : "ies"}`,
|
||||
parseFailed: "Parse failed",
|
||||
materializeFailed: (message: string) => `Failed: ${message}`,
|
||||
},
|
||||
|
||||
artifactPreview: {
|
||||
pdfPreviewFailed: "Unable to preview this PDF file. Please download it.",
|
||||
unsupportedType: "This file type is not previewable in the custom viewer.",
|
||||
docxPreviewFailed: "Unable to preview this DOCX file.",
|
||||
excelPreviewFailed: "Unable to preview this Excel file.",
|
||||
switchSheetFailed: "Failed to switch worksheet.",
|
||||
excelGridPreviewFailed: "Unable to render Excel grid preview.",
|
||||
pptxDownloadHint: "Please download the PPT file for the best experience.",
|
||||
openInNewTab: "Open in new tab",
|
||||
clickToDownload: "Click to download",
|
||||
pageCountLabel: (fileName: string, pageCount: number) =>
|
||||
`${fileName} · ${pageCount} page(s)`,
|
||||
zoomIn: "Zoom in",
|
||||
zoomOut: "Zoom out",
|
||||
showArtifactsTooltip: "Show artifacts of this conversation",
|
||||
},
|
||||
|
||||
workspaceHeader: {
|
||||
sidebarTitle: "XClaw Sidebar",
|
||||
},
|
||||
|
||||
models: {
|
||||
updating: "System is updating, please wait...",
|
||||
apiUnavailable:
|
||||
"Model API is unavailable. Please check backend routes or service status.",
|
||||
},
|
||||
|
||||
threads: {
|
||||
streamError: "Something went wrong.",
|
||||
invalidThreadId: "Invalid thread id 'new'. Please refresh and retry.",
|
||||
staleReferencesRemoved:
|
||||
"Some referenced files were invalid and were removed automatically.",
|
||||
uploadFailed: "Failed to upload files.",
|
||||
uploadPrepareFailed: (count: number) =>
|
||||
`Failed to prepare ${count} attachment(s) for upload. Please retry.`,
|
||||
threadNotReadyForUpload: "Thread is not ready for file upload.",
|
||||
},
|
||||
|
||||
skills: {
|
||||
loadFailed: "Failed to load skill",
|
||||
missingThreadId: "Missing thread_id, cannot initialize skill",
|
||||
invalidSkillId: "Invalid skill_id",
|
||||
loading: (title: string) => `Loading skill "${title}"...`,
|
||||
loadFailedWithTitle: (title: string) => `Failed to load skill "${title}"`,
|
||||
loadSuccessWithTitle: (title: string) =>
|
||||
`Skill "${title}" loaded successfully`,
|
||||
loadErrorWithTitle: (title: string) => `Error loading skill "${title}"`,
|
||||
unknownError: "Unknown error",
|
||||
networkRequestFailed: "Network request failed",
|
||||
createdFiles: (count: number) => `Created ${count} file(s)`,
|
||||
invalidSkillIdArray: "Invalid skill_id array",
|
||||
},
|
||||
|
||||
// Page titles (document title)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export interface Translations {
|
|||
exportAsJSON: string;
|
||||
exportSuccess: string;
|
||||
removeAttachment: string;
|
||||
reference: string;
|
||||
};
|
||||
|
||||
// Welcome
|
||||
|
|
@ -99,6 +100,13 @@ export interface Translations {
|
|||
followupConfirmDescription: string;
|
||||
followupConfirmAppend: string;
|
||||
followupConfirmReplace: string;
|
||||
submit: string;
|
||||
submitting: string;
|
||||
stop: string;
|
||||
addReference: string;
|
||||
referenceSourceArtifact: string;
|
||||
referenceSourceUpload: string;
|
||||
maxReferencesReached: string;
|
||||
suggestions: {
|
||||
suggestion: string;
|
||||
prompt: string;
|
||||
|
|
@ -179,6 +187,80 @@ export interface Translations {
|
|||
// Chats
|
||||
chats: {
|
||||
searchChats: string;
|
||||
scrollToBottom: string;
|
||||
};
|
||||
|
||||
// Workspace Chat Page
|
||||
chatPage: {
|
||||
defaultSlogan: string;
|
||||
missingThreadIdForCreate: string;
|
||||
createSessionFailed: string;
|
||||
conversationFinished: string;
|
||||
missingThreadIdForSend: string;
|
||||
viewArtifactsTooltip: string;
|
||||
noArtifactSelectedTitle: string;
|
||||
noArtifactSelectedDescription: string;
|
||||
exitDialogTitle: string;
|
||||
exitDialogDescription: string;
|
||||
exitDialogConfirm: string;
|
||||
selectedSkillLoadFailed: string;
|
||||
unknownErrorRetry: string;
|
||||
};
|
||||
|
||||
messageListItem: {
|
||||
materializing: string;
|
||||
importAsSkillDir: string;
|
||||
materializeSuccess: (files: number, directories: number) => string;
|
||||
parseFailed: string;
|
||||
materializeFailed: (message: string) => string;
|
||||
};
|
||||
|
||||
artifactPreview: {
|
||||
pdfPreviewFailed: string;
|
||||
unsupportedType: string;
|
||||
docxPreviewFailed: string;
|
||||
excelPreviewFailed: string;
|
||||
switchSheetFailed: string;
|
||||
excelGridPreviewFailed: string;
|
||||
pptxDownloadHint: string;
|
||||
openInNewTab: string;
|
||||
clickToDownload: string;
|
||||
pageCountLabel: (fileName: string, pageCount: number) => string;
|
||||
zoomIn: string;
|
||||
zoomOut: string;
|
||||
showArtifactsTooltip: string;
|
||||
};
|
||||
|
||||
workspaceHeader: {
|
||||
sidebarTitle: string;
|
||||
};
|
||||
|
||||
models: {
|
||||
updating: string;
|
||||
apiUnavailable: string;
|
||||
};
|
||||
|
||||
threads: {
|
||||
streamError: string;
|
||||
invalidThreadId: string;
|
||||
staleReferencesRemoved: string;
|
||||
uploadFailed: string;
|
||||
uploadPrepareFailed: (count: number) => string;
|
||||
threadNotReadyForUpload: string;
|
||||
};
|
||||
|
||||
skills: {
|
||||
loadFailed: string;
|
||||
missingThreadId: string;
|
||||
invalidSkillId: string;
|
||||
loading: (title: string) => string;
|
||||
loadFailedWithTitle: (title: string) => string;
|
||||
loadSuccessWithTitle: (title: string) => string;
|
||||
loadErrorWithTitle: (title: string) => string;
|
||||
unknownError: string;
|
||||
networkRequestFailed: string;
|
||||
createdFiles: (count: number) => string;
|
||||
invalidSkillIdArray: string;
|
||||
};
|
||||
|
||||
// Page titles (document title)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export const zhCN: Translations = {
|
|||
exportAsJSON: "导出为 JSON",
|
||||
exportSuccess: "对话已导出",
|
||||
removeAttachment: "移除附件",
|
||||
reference: "引用",
|
||||
},
|
||||
|
||||
// Welcome
|
||||
|
|
@ -77,7 +78,7 @@ export const zhCN: Translations = {
|
|||
|
||||
// Input Box
|
||||
inputBox: {
|
||||
placeholder: "可直接聊天,或者输入需求并选择skill,完成更专业的任务",
|
||||
placeholder: "可直接对话; 或输入需求并选择skill,完成专业任务;“@”可引用文件",
|
||||
createSkillPrompt:
|
||||
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
||||
sendMessagePrice:
|
||||
|
|
@ -112,6 +113,13 @@ export const zhCN: Translations = {
|
|||
followupConfirmDescription: "当前输入框已有内容,选择发送方式。",
|
||||
followupConfirmAppend: "追加并发送",
|
||||
followupConfirmReplace: "替换并发送",
|
||||
submit: "发送",
|
||||
submitting: "生成中...",
|
||||
stop: "停止",
|
||||
addReference: "添加引用",
|
||||
referenceSourceArtifact: "生成文件",
|
||||
referenceSourceUpload: "上传附件",
|
||||
maxReferencesReached: "单条消息最多引用 10 个文件",
|
||||
suggestions: [
|
||||
{
|
||||
suggestion: "自媒体文案",
|
||||
|
|
@ -237,6 +245,83 @@ export const zhCN: Translations = {
|
|||
// Chats
|
||||
chats: {
|
||||
searchChats: "搜索对话",
|
||||
scrollToBottom: "滚动到底部",
|
||||
},
|
||||
|
||||
// Workspace Chat Page
|
||||
chatPage: {
|
||||
defaultSlogan: "来,一起学习工作吧",
|
||||
missingThreadIdForCreate: "缺少 thread_id,无法创建会话",
|
||||
createSessionFailed: "会话创建失败,请稍后重试",
|
||||
conversationFinished: "对话已完成",
|
||||
missingThreadIdForSend: "缺少 thread_id,无法发送消息",
|
||||
viewArtifactsTooltip: "点击可查看生成的文件结果",
|
||||
noArtifactSelectedTitle: "未选择生成文件",
|
||||
noArtifactSelectedDescription: "请选择一个生成文件以查看详情",
|
||||
exitDialogTitle: "提示",
|
||||
exitDialogDescription: "历史记录每七天自动删除,现在将返回欢迎页,是否继续?",
|
||||
exitDialogConfirm: "确定",
|
||||
selectedSkillLoadFailed: "技能加载失败",
|
||||
unknownErrorRetry: "发生了未知错误,请稍后重试。",
|
||||
},
|
||||
|
||||
messageListItem: {
|
||||
materializing: "解析中...",
|
||||
importAsSkillDir: "一键导入为 Skill 目录",
|
||||
materializeSuccess: (files: number, directories: number) =>
|
||||
`已创建 ${files} 个文件 / ${directories} 个目录`,
|
||||
parseFailed: "解析失败",
|
||||
materializeFailed: (message: string) => `失败: ${message}`,
|
||||
},
|
||||
|
||||
artifactPreview: {
|
||||
pdfPreviewFailed: "无法预览该 PDF 文件,请下载后查看。",
|
||||
unsupportedType: "该文件类型暂不支持在自定义预览器中查看。",
|
||||
docxPreviewFailed: "无法预览该 DOCX 文件。",
|
||||
excelPreviewFailed: "无法预览该 Excel 文件。",
|
||||
switchSheetFailed: "切换工作表失败。",
|
||||
excelGridPreviewFailed: "无法渲染 Excel 网格预览。",
|
||||
pptxDownloadHint: "请下载 ppt 文件以获得最佳效果",
|
||||
openInNewTab: "在新标签页打开",
|
||||
clickToDownload: "点击下载",
|
||||
pageCountLabel: (fileName: string, pageCount: number) =>
|
||||
`${fileName} · 共 ${pageCount} 页`,
|
||||
zoomIn: "放大",
|
||||
zoomOut: "缩小",
|
||||
showArtifactsTooltip: "查看当前对话的生成文件",
|
||||
},
|
||||
|
||||
workspaceHeader: {
|
||||
sidebarTitle: "XClaw侧边栏",
|
||||
},
|
||||
|
||||
models: {
|
||||
updating: "系统正在更新,请稍候……",
|
||||
apiUnavailable: "模型接口不可用,请检查后端路由或服务状态。",
|
||||
},
|
||||
|
||||
threads: {
|
||||
streamError: "出现了某些错误。",
|
||||
invalidThreadId: "线程 ID 无效(new),请刷新后重试。",
|
||||
staleReferencesRemoved: "部分引用文件已失效,已自动移除并继续发送。",
|
||||
uploadFailed: "文件上传失败。",
|
||||
uploadPrepareFailed: (count: number) =>
|
||||
`准备上传附件失败(${count} 个),请重试。`,
|
||||
threadNotReadyForUpload: "当前线程尚未就绪,无法上传文件。",
|
||||
},
|
||||
|
||||
skills: {
|
||||
loadFailed: "技能加载失败",
|
||||
missingThreadId: "缺少 thread_id,无法初始化技能",
|
||||
invalidSkillId: "无效的 skill_id",
|
||||
loading: (title: string) => `正在加载技能「${title}」...`,
|
||||
loadFailedWithTitle: (title: string) => `技能「${title}」加载失败`,
|
||||
loadSuccessWithTitle: (title: string) => `技能「${title}」加载成功`,
|
||||
loadErrorWithTitle: (title: string) => `技能「${title}」加载出错`,
|
||||
unknownError: "未知错误",
|
||||
networkRequestFailed: "网络请求失败",
|
||||
createdFiles: (count: number) => `已创建 ${count} 个文件`,
|
||||
invalidSkillIdArray: "非法 skill_id 数组",
|
||||
},
|
||||
|
||||
// Page titles (document title)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ import { useQuery } from "@tanstack/react-query";
|
|||
import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useI18n } from "../i18n/hooks";
|
||||
|
||||
import { loadModels } from "./api";
|
||||
import type { Model } from "./types";
|
||||
|
||||
const MODELS_UPDATING_TOAST_ID = "models-server-updating";
|
||||
|
||||
export function useModels({ enabled = true }: { enabled?: boolean } = {}) {
|
||||
const { t } = useI18n();
|
||||
const { data, isLoading, error, failureReason } = useQuery<Model[], Error>({
|
||||
queryKey: ["models"],
|
||||
queryFn: () => loadModels(),
|
||||
|
|
@ -31,7 +34,7 @@ export function useModels({ enabled = true }: { enabled?: boolean } = {}) {
|
|||
);
|
||||
|
||||
if (serverError) {
|
||||
toast.loading("系统正在更新,请稍候……", {
|
||||
toast.loading(t.models.updating, {
|
||||
id: MODELS_UPDATING_TOAST_ID,
|
||||
});
|
||||
return;
|
||||
|
|
@ -42,9 +45,9 @@ export function useModels({ enabled = true }: { enabled?: boolean } = {}) {
|
|||
|
||||
useEffect(() => {
|
||||
if (error?.message.includes("HTTP error: 4")) {
|
||||
toast.error("模型接口不可用,请检查后端路由或服务状态。");
|
||||
toast.error(t.models.apiUnavailable);
|
||||
}
|
||||
}, [error]);
|
||||
}, [error, t.models.apiUnavailable]);
|
||||
|
||||
return { models: data ?? [], isLoading, error };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import type { FileInMessage } from "../messages/utils";
|
|||
import type { LocalSettings } from "../settings";
|
||||
import { useUpdateSubtask } from "../tasks/context";
|
||||
import type { UploadedFileInfo } from "../uploads";
|
||||
import { uploadFiles } from "../uploads";
|
||||
import { listUploadedFiles, uploadFiles } from "../uploads";
|
||||
import type { UploadTarget } from "../uploads/api";
|
||||
|
||||
import { buildFilesForSubmit } from "./submit-files";
|
||||
|
|
@ -50,7 +50,6 @@ export type LegacyThreadStreamOptions = {
|
|||
};
|
||||
|
||||
const STREAM_ERROR_FALLBACK_MESSAGE = "Request failed.";
|
||||
const STREAM_ERROR_TOAST_MESSAGE = "出现了某些错误。";
|
||||
const STREAM_ERROR_TOAST_DEDUPE_WINDOW_MS = 2000;
|
||||
const STREAM_CANCEL_PATTERNS = [
|
||||
/\bcancellederror\b/i,
|
||||
|
|
@ -276,8 +275,8 @@ export function useThreadStream({
|
|||
lastErrorToastRef.current = { message, timestamp: now };
|
||||
console.error("[useThreadStream] conversation stream error:", error);
|
||||
console.error("[useThreadStream] parsed error message:", message);
|
||||
toast.error(STREAM_ERROR_TOAST_MESSAGE);
|
||||
}, []);
|
||||
toast.error(t.threads.streamError);
|
||||
}, [t.threads.streamError]);
|
||||
|
||||
const handleStreamStart = useCallback(
|
||||
(_threadId: string) => {
|
||||
|
|
@ -407,7 +406,7 @@ export function useThreadStream({
|
|||
normalizeThreadId(threadIdRef.current) ??
|
||||
undefined;
|
||||
if (resolvedThreadId === "new") {
|
||||
toast.error("Invalid thread id 'new'. Please refresh and retry.");
|
||||
toast.error(t.threads.invalidThreadId);
|
||||
sendInFlightRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -503,17 +502,21 @@ export function useThreadStream({
|
|||
|
||||
if (failedConversions > 0) {
|
||||
throw new Error(
|
||||
`Failed to prepare ${failedConversions} attachment(s) for upload. Please retry.`,
|
||||
t.threads.uploadPrepareFailed(failedConversions),
|
||||
);
|
||||
}
|
||||
|
||||
if (!resolvedThreadId) {
|
||||
throw new Error("Thread is not ready for file upload.");
|
||||
throw new Error(t.threads.threadNotReadyForUpload);
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
const uploadResponse = await uploadFiles(resolvedThreadId, files);
|
||||
uploadedFileInfo = uploadResponse.files;
|
||||
await queryClient.fetchQuery({
|
||||
queryKey: ["uploads", "list", resolvedThreadId],
|
||||
queryFn: () => listUploadedFiles(resolvedThreadId),
|
||||
});
|
||||
|
||||
// Update optimistic human message with uploaded status + paths
|
||||
const uploadedFiles: FileInMessage[] = uploadedFileInfo.map(
|
||||
|
|
@ -541,9 +544,7 @@ export function useThreadStream({
|
|||
} catch (error) {
|
||||
console.error("Failed to upload files:", error);
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to upload files.";
|
||||
error instanceof Error ? error.message : t.threads.uploadFailed;
|
||||
toast.error(errorMessage);
|
||||
setOptimisticMessages([]);
|
||||
throw error;
|
||||
|
|
@ -559,7 +560,7 @@ export function useThreadStream({
|
|||
normalizedReferences,
|
||||
);
|
||||
if (staleCount > 0) {
|
||||
toast.error("部分引用文件已失效,已自动移除并继续发送。");
|
||||
toast.error(t.threads.staleReferencesRemoved);
|
||||
}
|
||||
|
||||
await thread.submit(
|
||||
|
|
@ -617,6 +618,11 @@ export function useThreadStream({
|
|||
thread,
|
||||
_handleOnStart,
|
||||
t.uploads.uploadingFiles,
|
||||
t.threads.invalidThreadId,
|
||||
t.threads.uploadPrepareFailed,
|
||||
t.threads.threadNotReadyForUpload,
|
||||
t.threads.uploadFailed,
|
||||
t.threads.staleReferencesRemoved,
|
||||
context,
|
||||
queryClient,
|
||||
apiClient,
|
||||
|
|
@ -655,12 +661,13 @@ export function useSubmitThread({
|
|||
uploadTarget?: UploadTarget;
|
||||
afterSubmit?: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
const apiClient = getAPIClient();
|
||||
const callback = useCallback(
|
||||
async (message: PromptInputMessage) => {
|
||||
if (threadId === "new") {
|
||||
toast.error("Invalid thread id 'new'. Please refresh and retry.");
|
||||
toast.error(t.threads.invalidThreadId);
|
||||
return;
|
||||
}
|
||||
const text = message.text.trim();
|
||||
|
|
@ -710,6 +717,10 @@ export function useSubmitThread({
|
|||
|
||||
if (files.length > 0 && threadId) {
|
||||
await uploadFiles(threadId, files, { target: uploadTarget });
|
||||
await queryClient.fetchQuery({
|
||||
queryKey: ["uploads", "list", threadId],
|
||||
queryFn: () => listUploadedFiles(threadId),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to upload files:", error);
|
||||
|
|
@ -722,7 +733,7 @@ export function useSubmitThread({
|
|||
normalizedReferences,
|
||||
);
|
||||
if (staleCount > 0) {
|
||||
toast.error("部分引用文件已失效,已自动移除并继续发送。");
|
||||
toast.error(t.threads.staleReferencesRemoved);
|
||||
}
|
||||
|
||||
await thread.submit(
|
||||
|
|
@ -761,6 +772,8 @@ export function useSubmitThread({
|
|||
},
|
||||
[
|
||||
thread,
|
||||
t.threads.invalidThreadId,
|
||||
t.threads.staleReferencesRemoved,
|
||||
createNewSession,
|
||||
threadId,
|
||||
threadContext,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
export type MentionReferenceEventDetail = {
|
||||
threadId: string;
|
||||
filename: string;
|
||||
path?: string;
|
||||
ref_source: "artifact" | "upload";
|
||||
};
|
||||
|
||||
export const MENTION_REFERENCE_EVENT = "deerflow:mention-reference";
|
||||
|
||||
export function dispatchMentionReference(detail: MentionReferenceEventDetail) {
|
||||
if (typeof window === "undefined") return;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent<MentionReferenceEventDetail>(MENTION_REFERENCE_EVENT, {
|
||||
detail,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
type SelectedSkillPayloadItem,
|
||||
sendToParent,
|
||||
} from "@/core/iframe-messages";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { bootstrapRemoteSkill } from "@/core/skills/api";
|
||||
|
||||
// Skill 数据类型
|
||||
|
|
@ -79,6 +80,7 @@ interface UseIframeSkillOptions {
|
|||
export function useIframeSkill(
|
||||
options?: UseIframeSkillOptions,
|
||||
): UseIframeSkillReturn {
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const threadIdFromQuery = searchParams.get("thread_id");
|
||||
|
|
@ -251,8 +253,8 @@ export function useIframeSkill(
|
|||
title: string;
|
||||
}) => {
|
||||
if (!threadId) {
|
||||
toast.error("技能加载失败", {
|
||||
description: "缺少 thread_id,无法初始化技能",
|
||||
toast.error(t.skills.loadFailed, {
|
||||
description: t.skills.missingThreadId,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -266,8 +268,8 @@ export function useIframeSkill(
|
|||
);
|
||||
|
||||
if (content_ids.length === 0) {
|
||||
toast.error("技能加载失败", {
|
||||
description: "无效的 skill_id",
|
||||
toast.error(t.skills.loadFailed, {
|
||||
description: t.skills.invalidSkillId,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -278,7 +280,7 @@ export function useIframeSkill(
|
|||
const languageType = languageTypeRaw ? Number(languageTypeRaw) : 0;
|
||||
|
||||
setIsBootstrapping(true);
|
||||
toast.loading(`正在加载技能「${title}」...`, {
|
||||
toast.loading(t.skills.loading(title), {
|
||||
id: "suggest-skill-bootstrap",
|
||||
});
|
||||
|
||||
|
|
@ -298,8 +300,8 @@ export function useIframeSkill(
|
|||
String(item.id).trim(),
|
||||
);
|
||||
removeFailedSkills(failedIds);
|
||||
toast.error(`技能「${title}」加载失败`, {
|
||||
description: result.message || "未知错误",
|
||||
toast.error(t.skills.loadFailedWithTitle(title), {
|
||||
description: result.message || t.skills.unknownError,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -312,9 +314,8 @@ export function useIframeSkill(
|
|||
setSelectedSkill(normalizedSkills[0] ?? null);
|
||||
setSelectedSkills(normalizedSkills);
|
||||
|
||||
toast.success(`技能「${title}」加载成功`, {
|
||||
description:
|
||||
result.message || `已创建 ${result.created_files} 个文件`,
|
||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||
description: result.message || t.skills.createdFiles(result.created_files),
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
@ -322,8 +323,9 @@ export function useIframeSkill(
|
|||
const failedIds = selectedSkills.map((item) => String(item.id).trim());
|
||||
removeFailedSkills(failedIds);
|
||||
toast.dismiss("suggest-skill-bootstrap");
|
||||
const message = error instanceof Error ? error.message : "网络请求失败";
|
||||
toast.error(`技能「${title}」加载失败`, {
|
||||
const message =
|
||||
error instanceof Error ? error.message : t.skills.networkRequestFailed;
|
||||
toast.error(t.skills.loadFailedWithTitle(title), {
|
||||
description: message,
|
||||
});
|
||||
return false;
|
||||
|
|
@ -331,7 +333,7 @@ export function useIframeSkill(
|
|||
setIsBootstrapping(false);
|
||||
}
|
||||
},
|
||||
[removeFailedSkills, searchParams, sendSelectSkill, threadId],
|
||||
[removeFailedSkills, searchParams, sendSelectSkill, t.skills, threadId],
|
||||
);
|
||||
|
||||
// 打开 skill 选择对话框
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
isSelectedSkillsMessage,
|
||||
type SelectedSkillPayloadItem,
|
||||
} from "@/core/iframe-messages";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { bootstrapRemoteSkill } from "@/core/skills/api";
|
||||
|
||||
/** 技能基础数据 */
|
||||
|
|
@ -46,6 +47,7 @@ interface UseSelectedSkillListenerReturn {
|
|||
export function useSelectedSkillListener({
|
||||
threadId,
|
||||
}: UseSelectedSkillListenerOptions): UseSelectedSkillListenerReturn {
|
||||
const { t } = useI18n();
|
||||
const searchParams = useSearchParams();
|
||||
const [selectedSkill, setSelectedSkill] = useState<SkillData | null>(null);
|
||||
const [skillError, setSkillError] = useState<SkillError | null>(null);
|
||||
|
|
@ -67,8 +69,8 @@ export function useSelectedSkillListener({
|
|||
if (contentIds.length === 0) {
|
||||
console.warn("[useSelectedSkillListener] 忽略非法 skill ids", skills);
|
||||
setSkillError({
|
||||
title: `技能「${title}」加载失败`,
|
||||
message: "非法 skill_id 数组",
|
||||
title: t.skills.loadFailedWithTitle(title),
|
||||
message: t.skills.invalidSkillIdArray,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -87,7 +89,7 @@ export function useSelectedSkillListener({
|
|||
`[useSelectedSkillListener] 开始初始化技能: ${title} (${contentIds.join(",")})`,
|
||||
);
|
||||
setIsBootstrapping(true);
|
||||
toast.loading(`正在加载技能「${title}」...`, { id: "skill-bootstrap" });
|
||||
toast.loading(t.skills.loading(title), { id: "skill-bootstrap" });
|
||||
|
||||
try {
|
||||
const result = await bootstrapRemoteSkill({
|
||||
|
|
@ -102,26 +104,26 @@ export function useSelectedSkillListener({
|
|||
|
||||
if (result.success) {
|
||||
skillBootstrappedKeyRef.current = initKey;
|
||||
toast.success(`技能「${title}」加载成功`, {
|
||||
description:
|
||||
result.message || `已创建 ${result.created_files} 个文件`,
|
||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||
description: result.message || t.skills.createdFiles(result.created_files),
|
||||
duration: 4000,
|
||||
});
|
||||
} else {
|
||||
setSkillError({
|
||||
title: `技能「${title}」加载失败`,
|
||||
message: result.message || "未知错误",
|
||||
title: t.skills.loadFailedWithTitle(title),
|
||||
message: result.message || t.skills.unknownError,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
toast.dismiss("skill-bootstrap");
|
||||
const message = err instanceof Error ? err.message : "网络请求失败";
|
||||
setSkillError({ title: `技能「${title}」加载出错`, message });
|
||||
const message =
|
||||
err instanceof Error ? err.message : t.skills.networkRequestFailed;
|
||||
setSkillError({ title: t.skills.loadErrorWithTitle(title), message });
|
||||
} finally {
|
||||
setIsBootstrapping(false);
|
||||
}
|
||||
},
|
||||
[threadId, searchParams],
|
||||
[threadId, searchParams, t.skills],
|
||||
);
|
||||
|
||||
// 1. URL 初始化集成
|
||||
|
|
|
|||
Loading…
Reference in New Issue