feat(settings): 新建对话按钮行为可选,移除根目录创建文件与诊断日志入口

This commit is contained in:
JOJO 2026-07-26 00:55:46 +08:00
parent 57352b28fb
commit 87a6a03943
6 changed files with 49 additions and 190 deletions

View File

@ -64,18 +64,6 @@ class CrudMixin:
relative_path = self._relative_path(full_path) relative_path = self._relative_path(full_path)
try: try:
# 检查是否允许在根目录创建文件
personalization_config = self._load_personalization_config()
allow_root_creation = bool(
personalization_config.get("allow_root_file_creation", False)
) if isinstance(personalization_config, dict) else False
if full_path.parent == self.project_path and not allow_root_creation:
return {
"success": False,
"error": "禁止在项目根目录直接创建文件,请先创建或选择合适的子目录。",
"suggestion": "然后必须**重新**再次创建文件。"
}
if self._use_container(): if self._use_container():
result = self._container_call("create_file", { result = self._container_call("create_file", {
"path": relative_path, "path": relative_path,

View File

@ -103,7 +103,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"versioning_backup_mode": "shallow", # 文件备份方式shallow-浅备份只备份AI编辑的文件/ full-完全备份(整个工作区) "versioning_backup_mode": "shallow", # 文件备份方式shallow-浅备份只备份AI编辑的文件/ full-完全备份(整个工作区)
"versioning_restore_mode": "overwrite", # 版本回溯模式固定为 overwrite "versioning_restore_mode": "overwrite", # 版本回溯模式固定为 overwrite
"agents_md_auto_inject": False, # AGENTS.md 自动注入开关 "agents_md_auto_inject": False, # AGENTS.md 自动注入开关
"allow_root_file_creation": False, # 允许在根目录创建文件开关 "new_chat_button_behavior": "route", # 新建对话按钮行为route-跳转空白新对话页 / blank-立即创建空对话
"default_hide_workspace": False, # 默认隐藏工作区 "default_hide_workspace": False, # 默认隐藏工作区
"hide_quick_dock": False, # 隐藏快捷窗口(对话区右侧的待办/子智能体/后台指令/文件窗口列) "hide_quick_dock": False, # 隐藏快捷窗口(对话区右侧的待办/子智能体/后台指令/文件窗口列)
"group_sidebar_by_workspace": False, # 侧边栏按工作区/项目分组显示对话 "group_sidebar_by_workspace": False, # 侧边栏按工作区/项目分组显示对话
@ -517,11 +517,12 @@ def sanitize_personalization_payload(
else: else:
base["agents_md_auto_inject"] = bool(base.get("agents_md_auto_inject", False)) base["agents_md_auto_inject"] = bool(base.get("agents_md_auto_inject", False))
# 允许根目录创建文件开关 # 新建对话按钮行为route-跳转空白新对话页 / blank-立即创建空对话
if "allow_root_file_creation" in data: new_chat_behavior = data.get("new_chat_button_behavior", base.get("new_chat_button_behavior"))
base["allow_root_file_creation"] = bool(data.get("allow_root_file_creation")) if isinstance(new_chat_behavior, str) and new_chat_behavior.strip().lower() in ("route", "blank"):
base["new_chat_button_behavior"] = new_chat_behavior.strip().lower()
else: else:
base["allow_root_file_creation"] = bool(base.get("allow_root_file_creation", False)) base["new_chat_button_behavior"] = "route"
# 默认隐藏工作区 # 默认隐藏工作区
if "default_hide_workspace" in data: if "default_hide_workspace" in data:

View File

@ -1134,21 +1134,6 @@ def resource_busy_page():
return app.send_static_file('resource_busy.html'), 503 return app.send_static_file('resource_busy.html'), 503
@app.route('/api/voice_debug', methods=['POST'])
def voice_debug():
"""接收手机端语音调试日志"""
import os, datetime
data = request.get_data(as_text=True)
log_dir = os.environ.get('LOGS_DIR', os.path.join(os.path.dirname(__file__), '..', 'logs'))
voice_log_dir = os.path.join(log_dir, 'voice_debug')
os.makedirs(voice_log_dir, exist_ok=True)
ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
filename = os.path.join(voice_log_dir, f'voice_{ts}.log')
with open(filename, 'w') as f:
f.write(data)
return jsonify({'ok': True, 'file': filename})
@app.route('/api/client_debug_log', methods=['POST']) @app.route('/api/client_debug_log', methods=['POST'])
def client_debug_log(): def client_debug_log():
"""接收前端目标模式等调试日志""" """接收前端目标模式等调试日志"""

View File

@ -62,6 +62,23 @@ export const actionMethods = {
keepalive: true keepalive: true
}).catch(() => {}); }).catch(() => {});
// 按个性化设置分流route-跳转空白新对话页(发送首条消息时才真正创建);
// blank-保持现有行为,立即创建空对话
let newChatBehavior = 'route';
try {
newChatBehavior = usePersonalizationStore()?.form?.new_chat_button_behavior || 'route';
} catch (error) {
console.warn('读取新建对话按钮行为设置失败,按默认跳转处理:', error);
}
if (newChatBehavior !== 'blank') {
const target = this.multiAgentMode ? '/multiagent/new' : '/new';
const normalize = (p) => String(p || '/').replace(/^\/+|\/+$/g, '');
if (normalize(window.location.pathname) !== normalize(target)) {
window.location.href = target;
}
return;
}
// 应用个性化设置中的默认模型和思考模式 // 应用个性化设置中的默认模型和思考模式
try { try {
const personalizationStore = usePersonalizationStore(); const personalizationStore = usePersonalizationStore();

View File

@ -431,21 +431,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="settings-action-row">
<span class="settings-row-copy">
<span class="settings-row-title">上传诊断日志</span>
<span class="settings-row-desc"
>复现问题后点击把当前环境信息上传给开发者排查</span
>
</span>
<button
type="button"
class="settings-secondary-button"
@click="uploadDiagnosticLog"
>
上传日志
</button>
</div>
</section> </section>
<section <section
@ -640,6 +625,28 @@
class="fancy-path" class="fancy-path"
></path></svg></span ></path></svg></span
></label> ></label>
<label class="settings-toggle-row"
><span class="settings-row-copy"
><span class="settings-row-title">新建对话跳转空白页</span
><span class="settings-row-desc"
>开启后点击新建对话跳转新对话页发送首条消息时才创建关闭则立即创建空对话</span
></span
><input
type="checkbox"
:checked="form.new_chat_button_behavior === 'route'"
@change="
personalization.updateField({
key: 'new_chat_button_behavior',
value: $event.target.checked ? 'route' : 'blank'
})
" /><span class="fancy-check" aria-hidden="true"
><svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path></svg></span
></label>
<label class="settings-toggle-row" <label class="settings-toggle-row"
><span class="settings-row-copy" ><span class="settings-row-copy"
><span class="settings-row-title">使用自定义称呼</span ><span class="settings-row-title">使用自定义称呼</span
@ -915,28 +922,6 @@
</div> </div>
</div> </div>
</div> </div>
<label class="settings-toggle-row"
><span class="settings-row-copy"
><span class="settings-row-title">允许根目录创建文件</span
><span class="settings-row-desc"
>关闭则禁止在工作区根目录创建文件</span
></span
><input
type="checkbox"
:checked="form.allow_root_file_creation"
@change="
personalization.updateField({
key: 'allow_root_file_creation',
value: $event.target.checked
})
" /><span class="fancy-check" aria-hidden="true"
><svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path></svg></span
></label>
<label class="settings-toggle-row" <label class="settings-toggle-row"
><span class="settings-row-copy" ><span class="settings-row-copy"
><span class="settings-row-title">AGENTS.md 自动注入</span ><span class="settings-row-title">AGENTS.md 自动注入</span
@ -1711,20 +1696,6 @@
</button> </button>
</div> </div>
</div> </div>
<div class="settings-action-row" style="margin-top: 8px">
<span class="settings-row-copy">
<span class="settings-row-desc" style="font-size: 12px"
>点击麦克风闪退先复现一次再点上传日志</span
>
</span>
<button
type="button"
class="settings-secondary-button"
@click="saveDebugLog"
>
上传日志
</button>
</div>
<div <div
v-if="voiceDownloading" v-if="voiceDownloading"
class="voice-download-bar" class="voice-download-bar"
@ -2259,29 +2230,6 @@ const deleteVoiceModel = () => {
voiceDownloadMsg.value = ''; voiceDownloadMsg.value = '';
}; };
const saveDebugLog = async () => {
const bridge = (window as any)?.AndroidVoiceBridge;
if (!bridge) return;
let log = '';
if (typeof bridge.collectDebugLog === 'function') {
log = bridge.collectDebugLog();
}
if (!log) {
alert('无法收集日志');
return;
}
try {
const res = await fetch('/api/voice_debug', { method: 'POST', body: log });
if (res.ok) {
alert('日志已上传到服务器');
} else {
alert('上传失败: ' + res.status);
}
} catch (e: any) {
alert('上传失败: ' + (e.message || e));
}
};
// //
checkVoiceModel(); checkVoiceModel();
@ -2721,86 +2669,6 @@ const downloadLatestApp = () => {
window.location.href = url; window.location.href = url;
}; };
const uploadDiagnosticLog = async () => {
try {
const w = window as any;
const params = new URLSearchParams(window.location.search);
const screenInfo =
typeof window !== 'undefined'
? {
width: window.innerWidth,
height: window.innerHeight,
dpr: window.devicePixelRatio
}
: null;
const bridgeProbe = (name: string) => {
const b = w?.[name];
if (!b) return { exists: false };
const methods: Record<string, any> = {};
[
'getAppVersionCode',
'getAppVersionName',
'onThemeChanged',
'previewPdf',
'isPdfPreviewSupported',
'downloadFile',
'isSupported',
'isModelReady',
'isModelPartial',
'startRecording',
'stopRecording'
].forEach((m) => {
try {
methods[m] = typeof b[m] === 'function';
} catch {
methods[m] = 'error';
}
});
let versionCode = '';
let versionName = '';
try {
versionCode =
typeof b.getAppVersionCode === 'function' ? String(b.getAppVersionCode() || '') : '';
versionName =
typeof b.getAppVersionName === 'function' ? String(b.getAppVersionName() || '') : '';
} catch {}
return { exists: true, methods, versionCode, versionName };
};
const payload = {
source: 'diagnostic_log',
url: window.location.href,
userAgent: navigator.userAgent,
screen: screenInfo,
urlParams: {
app_vc: params.get('app_vc') || '',
app_vn: params.get('app_vn') || '',
app_shell: params.get('app_shell') || ''
},
bridges: {
AndroidThemeBridge: bridgeProbe('AndroidThemeBridge'),
AndroidPdfBridge: bridgeProbe('AndroidPdfBridge'),
AndroidDownloadBridge: bridgeProbe('AndroidDownloadBridge'),
AndroidVoiceBridge: bridgeProbe('AndroidVoiceBridge')
},
appCurrentVersionCode: appCurrentVersionCode.value,
appCurrentVersionName: appCurrentVersionName.value,
appUpdateInfo: appUpdateInfo.value,
appUpdateError: appUpdateError.value,
appUpdateCheckedAt: appUpdateCheckedAt.value,
timestamp: new Date().toISOString()
};
const resp = await fetch('/api/client_debug_log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!resp.ok) throw new Error(`status ${resp.status}`);
alert('诊断日志已上传');
} catch (e: any) {
alert('上传诊断日志失败:' + (e?.message || String(e)));
}
};
onMounted(async () => { onMounted(async () => {
hydrateAppVersionFromBridge(); hydrateAppVersionFromBridge();
try { try {

View File

@ -60,7 +60,7 @@ interface PersonalForm {
deep_compress_trigger_tokens: number | null; deep_compress_trigger_tokens: number | null;
deep_compress_form: 'file' | 'inject'; deep_compress_form: 'file' | 'inject';
agents_md_auto_inject: boolean; agents_md_auto_inject: boolean;
allow_root_file_creation: boolean; new_chat_button_behavior: 'route' | 'blank';
group_sidebar_by_workspace: boolean; group_sidebar_by_workspace: boolean;
sidebar_pinned_workspaces: string[]; sidebar_pinned_workspaces: string[];
sidebar_workspace_order: string[]; sidebar_workspace_order: string[];
@ -212,7 +212,7 @@ const defaultForm = (): PersonalForm => ({
default_model: null, default_model: null,
image_compression: 'original', image_compression: 'original',
auto_shallow_compress_enabled: false, auto_shallow_compress_enabled: false,
auto_deep_compress_enabled: false, auto_deep_compress_enabled: true,
shallow_compress_trigger_tokens: null, shallow_compress_trigger_tokens: null,
shallow_compress_keep_recent_tools: null, shallow_compress_keep_recent_tools: null,
shallow_compress_keep_user_turn_tools: null, shallow_compress_keep_user_turn_tools: null,
@ -221,7 +221,7 @@ const defaultForm = (): PersonalForm => ({
deep_compress_trigger_tokens: null, deep_compress_trigger_tokens: null,
deep_compress_form: 'file', deep_compress_form: 'file',
agents_md_auto_inject: false, agents_md_auto_inject: false,
allow_root_file_creation: false, new_chat_button_behavior: 'route',
group_sidebar_by_workspace: false, group_sidebar_by_workspace: false,
sidebar_pinned_workspaces: [], sidebar_pinned_workspaces: [],
sidebar_workspace_order: [], sidebar_workspace_order: [],
@ -460,7 +460,7 @@ export const usePersonalizationStore = defineStore('personalization', {
), ),
deep_compress_form: data.deep_compress_form === 'inject' ? 'inject' : 'file', deep_compress_form: data.deep_compress_form === 'inject' ? 'inject' : 'file',
agents_md_auto_inject: !!data.agents_md_auto_inject, agents_md_auto_inject: !!data.agents_md_auto_inject,
allow_root_file_creation: !!data.allow_root_file_creation, new_chat_button_behavior: data.new_chat_button_behavior === 'blank' ? 'blank' : 'route',
group_sidebar_by_workspace: !!data.group_sidebar_by_workspace, group_sidebar_by_workspace: !!data.group_sidebar_by_workspace,
sidebar_pinned_workspaces: Array.isArray(data.sidebar_pinned_workspaces) sidebar_pinned_workspaces: Array.isArray(data.sidebar_pinned_workspaces)
? data.sidebar_pinned_workspaces.filter((item: unknown) => typeof item === 'string') ? data.sidebar_pinned_workspaces.filter((item: unknown) => typeof item === 'string')