Compare commits
5 Commits
297a6cc5ec
...
f6cbe78d68
| Author | SHA1 | Date | |
|---|---|---|---|
| f6cbe78d68 | |||
| 57aaf76809 | |||
| a60e164aa2 | |||
| f8f5d26ee3 | |||
| 4e1d08f106 |
@ -207,7 +207,9 @@
|
||||
|
||||
### 前端调试日志
|
||||
|
||||
- **统一筛选词**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。
|
||||
- **统一筛选词(强制)**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。
|
||||
- **同一次调试任务中严禁使用多个前缀**。如果涉及多个模块(如状态栏、Git 摘要、用户问题),应共用同一个前缀(如 `[status-bar-debug]`),通过日志对象里的字段区分模块,而不是发明多个前缀增加用户筛选成本。
|
||||
- 选择前缀时优先与本次排查的「用户可见现象」对齐,而不是与内部模块名对齐。
|
||||
- **控制数量与时机**:
|
||||
- 禁止一次性输出上百上千条日志刷屏。
|
||||
- 只在关键路径(入口、分支判断、状态变化、导航动作)打印,避免在循环、高频事件、每帧渲染中输出。
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@ -10,7 +11,16 @@ def extract_intent_from_partial(arg_str: str) -> Optional[str]:
|
||||
return None
|
||||
match = re.search(r'"intent"\s*:\s*"([^"]{0,128})', arg_str, re.IGNORECASE | re.DOTALL)
|
||||
if match:
|
||||
return match.group(1)
|
||||
raw = match.group(1)
|
||||
try:
|
||||
# 某些模型会对非 ASCII 字符输出 JSON Unicode 转义(如 \u4e2d\u6587),
|
||||
# 这里用 json.loads 安全解码,失败时回退原始值。
|
||||
decoded = json.loads('"{}"'.format(raw))
|
||||
if isinstance(decoded, str):
|
||||
return decoded
|
||||
except Exception:
|
||||
pass
|
||||
return raw
|
||||
return None
|
||||
|
||||
|
||||
|
||||
BIN
static/apple-touch-icon.png
Normal file
BIN
static/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
35
static/astrion-avatar.svg
Normal file
35
static/astrion-avatar.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -4 200 200" aria-hidden="true">
|
||||
<style>
|
||||
.sa-frame, .sa-eye {
|
||||
stroke: #000000;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
fill: none;
|
||||
}
|
||||
.sa-frame {
|
||||
stroke-width: 12;
|
||||
}
|
||||
.sa-eye {
|
||||
stroke-width: 10;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.sa-frame, .sa-eye {
|
||||
stroke: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
class="sa-frame"
|
||||
d="M 175.959,107.000 Q 180.000,100.000 175.959,93.000 L 144.041,37.718 Q 140.000,30.718 131.917,30.718 L 68.083,30.718 Q 60.000,30.718 55.959,37.718 L 24.041,93.000 Q 20.000,100.000 24.041,107.000 L 55.959,162.282 Q 60.000,169.282 68.083,169.282 L 131.917,169.282 Q 140.000,169.282 144.041,162.282 Z"
|
||||
/>
|
||||
<path
|
||||
class="sa-eye"
|
||||
d="M 0,-11 Q 0,-5.5 0,0 Q 0,5.5 0,11 Q 0,5.5 0,0 Q 0,-5.5 0,-11"
|
||||
transform="translate(84, 96)"
|
||||
/>
|
||||
<path
|
||||
class="sa-eye"
|
||||
d="M 0,-11 Q 0,-5.5 0,0 Q 0,5.5 0,11 Q 0,5.5 0,0 Q 0,-5.5 0,-11"
|
||||
transform="translate(116, 96)"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
static/favicon-16x16.png
Normal file
BIN
static/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 362 B |
BIN
static/favicon-32x32.png
Normal file
BIN
static/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 716 B |
@ -4,6 +4,10 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Astrion</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/astrion-avatar.svg">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link rel="stylesheet" href="/static/easter-eggs/flood.css">
|
||||
<link rel="stylesheet" href="/static/easter-eggs/snake.css">
|
||||
|
||||
@ -41,7 +41,6 @@ export async function mounted() {
|
||||
|
||||
// 立即加载初始数据(并行获取状态,优先同步运行模式)
|
||||
const initialDataPromise = this.loadInitialData();
|
||||
this.refreshProjectGitSummary?.();
|
||||
this.startProjectGitSummaryIdleRefresh?.();
|
||||
this.startConnectionHeartbeat();
|
||||
this.fetchTerminalCount();
|
||||
@ -50,6 +49,8 @@ export async function mounted() {
|
||||
if (initialDataPromise && typeof initialDataPromise.then === 'function') {
|
||||
initialDataPromise
|
||||
.then(() => {
|
||||
// 初始数据加载完成后再刷新 git 摘要,确保 workspace/project_path 已就绪
|
||||
this.refreshProjectGitSummary?.();
|
||||
// 避免在初始加载阶段被覆盖,加载完成后再兜底检查一次
|
||||
this.checkTutorialPrompt();
|
||||
})
|
||||
|
||||
@ -232,6 +232,8 @@ export const loadMethods = {
|
||||
this.fetchNetworkPermission();
|
||||
await this.fetchVersioningStatus(conversationId, { silent: true });
|
||||
this.fetchPendingToolApprovals();
|
||||
this.fetchPendingUserQuestions();
|
||||
this.refreshProjectGitSummary();
|
||||
this.fetchConversationTokenStatistics();
|
||||
this.updateCurrentContextTokens();
|
||||
traceLog('loadConversation:after-history', {
|
||||
|
||||
@ -58,6 +58,35 @@ export const dialogMethods = {
|
||||
this.userQuestionDialogVisible = true;
|
||||
this.userQuestionMinimized = false;
|
||||
},
|
||||
async fetchPendingUserQuestions() {
|
||||
if (!this.currentConversationId) {
|
||||
this.pendingUserQuestions = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/user-questions/pending?conversation_id=${encodeURIComponent(this.currentConversationId)}`
|
||||
);
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !payload?.success) {
|
||||
return;
|
||||
}
|
||||
const items = Array.isArray(payload.items) ? payload.items : [];
|
||||
this.pendingUserQuestions = items;
|
||||
if (items.length > 0) {
|
||||
this.userQuestionActiveIndex = Math.min(
|
||||
Math.max(0, Number(this.userQuestionActiveIndex || 0)),
|
||||
Math.max(0, items.length - 1)
|
||||
);
|
||||
} else {
|
||||
this.userQuestionDialogVisible = false;
|
||||
this.userQuestionMinimized = false;
|
||||
this.userQuestionActiveIndex = 0;
|
||||
}
|
||||
} catch (_error) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
approveToolApproval(approvalId) {
|
||||
return this.decideToolApproval(approvalId, 'approved');
|
||||
},
|
||||
|
||||
@ -70,7 +70,7 @@ export const gitMethods = {
|
||||
if (this.gitChangesPanelOpen) {
|
||||
this.loadGitChangesDiff?.();
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (_error) {
|
||||
this.projectGitSummary = null;
|
||||
this.gitChangesDiff = null;
|
||||
} finally {
|
||||
|
||||
@ -151,6 +151,9 @@ export const routeMethods = {
|
||||
this.initialRouteResolved = true;
|
||||
}
|
||||
await this.restoreComposerDraftState('bootstrap-route:conversation');
|
||||
if (this.currentConversationId) {
|
||||
this.fetchPendingUserQuestions?.();
|
||||
}
|
||||
},
|
||||
handlePopState(event) {
|
||||
const state = event.state || {};
|
||||
|
||||
@ -372,6 +372,7 @@ export const socketMethods = {
|
||||
this.fetchConversationTokenStatistics();
|
||||
this.updateCurrentContextTokens();
|
||||
await this.fetchVersioningStatus(this.currentConversationId, { silent: true });
|
||||
this.fetchPendingUserQuestions();
|
||||
} catch (e) {
|
||||
console.warn('获取当前对话标题失败:', e);
|
||||
this.titleReady = true;
|
||||
|
||||
@ -170,7 +170,7 @@
|
||||
>目标模式就绪</span
|
||||
>
|
||||
<button
|
||||
v-if="userQuestionMinimized && pendingUserQuestionCount > 0"
|
||||
v-if="pendingUserQuestionCount > 0"
|
||||
type="button"
|
||||
class="floating-project-status__notice floating-project-status__notice--button"
|
||||
@click.stop="$emit('restore-user-question')"
|
||||
@ -828,33 +828,43 @@ const runtimeQueuedMessagesForRender = computed(() => {
|
||||
});
|
||||
|
||||
const projectGitSummaryForRender = computed(() => {
|
||||
if (personalizationStore?.form?.show_git_status_bar === false) return null;
|
||||
if (personalizationStore?.form?.show_git_status_bar === false) {
|
||||
return null;
|
||||
}
|
||||
const summary = props.projectGitSummary;
|
||||
if (!summary?.has_git) return null;
|
||||
if (!summary?.has_git) {
|
||||
return null;
|
||||
}
|
||||
const projectName = String(summary.project_name || '').trim();
|
||||
const branch = String(summary.branch || '').trim();
|
||||
if (!projectName || !branch) return null;
|
||||
return {
|
||||
if (!projectName || !branch) {
|
||||
return null;
|
||||
}
|
||||
const result = {
|
||||
projectName,
|
||||
projectPath: String(summary.project_path || '').trim(),
|
||||
branch,
|
||||
additions: Math.max(0, Number(summary.additions || 0)),
|
||||
deletions: Math.max(0, Number(summary.deletions || 0))
|
||||
};
|
||||
return result;
|
||||
});
|
||||
|
||||
const floatingStatusVisible = computed(() => {
|
||||
if (skillSlashMenuOpen.value || fileAtOpen.value || props.quickMenuOpen) return false;
|
||||
// 消息队列(预输入/引导消息)存在时隐藏git状态栏,与 / 菜单出现时逻辑相同
|
||||
if (runtimeQueuedMessagesForRender.value.length > 0 || props.hasPendingRuntimeGuidance)
|
||||
if (skillSlashMenuOpen.value || fileAtOpen.value || props.quickMenuOpen) {
|
||||
return false;
|
||||
return (
|
||||
}
|
||||
// 消息队列(预输入/引导消息)存在时隐藏git状态栏,与 / 菜单出现时逻辑相同
|
||||
if (runtimeQueuedMessagesForRender.value.length > 0 || props.hasPendingRuntimeGuidance) {
|
||||
return false;
|
||||
}
|
||||
const visible =
|
||||
!!projectGitSummaryForRender.value ||
|
||||
!!props.goalRunning ||
|
||||
!!props.goalModeArmed ||
|
||||
(!!props.userQuestionMinimized && Number(props.pendingUserQuestionCount || 0) > 0) ||
|
||||
(Number(props.activeSubAgentCount || 0) > 0)
|
||||
);
|
||||
(Number(props.pendingUserQuestionCount || 0) > 0) ||
|
||||
(Number(props.activeSubAgentCount || 0) > 0);
|
||||
return visible;
|
||||
});
|
||||
|
||||
const showComposerAvatar = computed(() => {
|
||||
|
||||
@ -213,6 +213,7 @@ function submit() {
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 20px 22px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-question-kicker-row {
|
||||
@ -240,6 +241,9 @@ function submit() {
|
||||
line-height: 1.55;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.01em;
|
||||
white-space: pre-wrap;
|
||||
max-height: min(180px, 30vh);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-question-nav {
|
||||
@ -297,6 +301,9 @@ function submit() {
|
||||
color: var(--claude-text-secondary);
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
white-space: pre-wrap;
|
||||
max-height: min(160px, 25vh);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-question-options {
|
||||
@ -335,6 +342,7 @@ function submit() {
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.42;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.user-question-textarea-wrap {
|
||||
|
||||
@ -2289,13 +2289,37 @@ const updateFloatingMenuPosition = async () => {
|
||||
const button = document.querySelector<HTMLElement>(
|
||||
'.settings-select-wrap.open .settings-select-button'
|
||||
);
|
||||
const menu = document.querySelector<HTMLElement>(
|
||||
'.settings-select-wrap.open .settings-floating-menu'
|
||||
);
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
const rect = button.getBoundingClientRect();
|
||||
const menuWidth = Math.min(300, Math.max(240, window.innerWidth - 32));
|
||||
const left = Math.max(16, Math.min(rect.right - menuWidth, window.innerWidth - menuWidth - 16));
|
||||
const top = Math.max(16, Math.min(rect.bottom + 10, window.innerHeight - 80));
|
||||
|
||||
const padding = 16;
|
||||
const gap = 10;
|
||||
const menuHeight = menu?.getBoundingClientRect().height || 0;
|
||||
let top: number;
|
||||
if (menuHeight > 0) {
|
||||
const spaceBelow = window.innerHeight - rect.bottom - padding;
|
||||
const spaceAbove = rect.top - padding;
|
||||
if (spaceBelow >= menuHeight) {
|
||||
top = rect.bottom + gap;
|
||||
} else if (spaceAbove >= menuHeight) {
|
||||
top = rect.top - menuHeight - gap;
|
||||
} else if (spaceBelow >= spaceAbove) {
|
||||
// 下方空间相对更大:菜单贴底部,内容通过 max-height 滚动
|
||||
top = Math.max(rect.bottom + gap, window.innerHeight - menuHeight - padding);
|
||||
} else {
|
||||
// 上方空间相对更大:菜单贴顶部
|
||||
top = padding;
|
||||
}
|
||||
} else {
|
||||
top = Math.max(padding, Math.min(rect.bottom + gap, window.innerHeight - 80));
|
||||
}
|
||||
floatingMenuStyle.value = {
|
||||
position: 'fixed',
|
||||
top: `${Math.round(top)}px`,
|
||||
|
||||
@ -40,6 +40,10 @@ class ServerRefactorSmokeTest(unittest.TestCase):
|
||||
import server.chat_flow_runner_helpers as helpers
|
||||
|
||||
self.assertEqual(helpers.extract_intent_from_partial('{"intent": "fix"}'), "fix")
|
||||
self.assertEqual(
|
||||
helpers.extract_intent_from_partial('{"intent": "\\u7b49\\u5f85\\u4e0b\\u8f7d\\u5b8c\\u6210"}'),
|
||||
"等待下载完成",
|
||||
)
|
||||
self.assertEqual(helpers.resolve_monitor_path({"file_path": " a.txt "}), "a.txt")
|
||||
self.assertEqual(helpers.resolve_monitor_memory([1, 2, 3], entry_limit=2), ["1", "2"])
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user