feat(settings): 新建对话按钮行为可选,移除根目录创建文件与诊断日志入口
This commit is contained in:
parent
57352b28fb
commit
87a6a03943
@ -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,
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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():
|
||||||
"""接收前端目标模式等调试日志"""
|
"""接收前端目标模式等调试日志"""
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user