重构: 巨函数拆分 + 清理历史标记注释 + custom_prompt/停止按钮/tunnel 改进
This commit is contained in:
@@ -102,7 +102,7 @@ import { ref, computed, nextTick, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAiStore } from '../../stores/ai'
|
||||
import { useProjectStore } from '../../stores/project'
|
||||
import { getConvState } from '../../composables/ai/useAiEvents'
|
||||
import { getConvState, textIdle } from '../../composables/ai/useAiEvents'
|
||||
import SkillMention from './SkillMention.vue'
|
||||
import ImageInput from './ImageInput.vue'
|
||||
import MentionPopover from './MentionPopover.vue'
|
||||
@@ -226,26 +226,17 @@ async function onDrop(e: DragEvent): Promise<void> {
|
||||
/** 发送可用:有文本或图片或已选技能(纯技能调用允许空文本) */
|
||||
const canSend = computed(() => inputText.value.trim().length > 0 || pendingImages.value.length > 0 || !!pendingSkill.value)
|
||||
|
||||
// L2 状态机停止按钮三态(批2 1c):从 conv_state 派生,旧 streaming/generating bool 双轨过渡兜底。
|
||||
//
|
||||
// 三态映射(对齐后端 ConvState 语义):
|
||||
// - 'stopping':ConvState=stopping,停止中 → 显"停止中" disabled(防重复点);
|
||||
// - 'stop': ConvState=generating(含审批挂起/压缩派生等活跃态)→ 显停止按钮可点;
|
||||
// - 'retry': ConvState=error,停止失败可重试 → 显重试按钮(再发 stop 信号);
|
||||
// - 'idle': 空闲(或 conv_state 未收到回退旧 bool=false)→ 显发送按钮。
|
||||
//
|
||||
// 兜底:conv_state 未追踪过(getConvState 返回 null,CONV_STATE_ENABLED=off 或老后端)时,
|
||||
// 回退旧 store.state.streaming 判断(generating→显停止,否则 idle)。不强制全替旧 bool,防回归。
|
||||
// 注:旧 streaming 是全局单值(非 per-conv),仅当前会话生成时为 true;F-09 多会话并发下 conv_state
|
||||
// 更精确,但回退路径用 streaming 对单会话场景语义等价。
|
||||
// 按钮状态:streaming=true 且 textIdle=false(最近 300ms 有新 delta) → 'stop'(红);
|
||||
// 否则 → 'idle'(白)。textIdle 是 reactive ref,在 AiTextDelta 中每 delta 复位 false,
|
||||
// 300ms 无新 delta 后 setTimeout 置 true。不与 streaming 绑定——文本显示完后 300ms 按钮白,
|
||||
// 不等 AiCompleted,streaming 持续 true 也不影响。
|
||||
const stopBtnState = computed<'idle' | 'stop' | 'stopping' | 'retry'>(() => {
|
||||
const convId = store.state.activeConversationId
|
||||
const cs = getConvState(convId)
|
||||
if (cs === 'stopping') return 'stopping'
|
||||
if (cs === 'error') return 'retry'
|
||||
if (cs === 'generating' || cs === 'compressed') return 'stop'
|
||||
// cs === null (conv_state 已 idle 收敛删项) 或 cs === 'idle' → 发送态
|
||||
return 'idle'
|
||||
const textActive = store.state.streaming && !textIdle.value
|
||||
return textActive ? 'stop' : 'idle'
|
||||
})
|
||||
|
||||
// ── 技能 `/` 联想 ──
|
||||
|
||||
@@ -38,6 +38,16 @@
|
||||
<option :value="3600000">{{ $t('settings.approvalTimeout60m') }}</option>
|
||||
</select>
|
||||
</SettingRow>
|
||||
<!-- 自定义提示词:注入到 AI 系统 prompt 末尾,用户可自定义 AI 行为偏好 -->
|
||||
<SettingRow ref="rowCustomPrompt" :label="$t('settings.labelCustomPrompt')" :desc="$t('settings.descCustomPrompt')">
|
||||
<textarea
|
||||
v-model="customPrompt"
|
||||
class="custom-prompt-input"
|
||||
:placeholder="$t('settings.placeholderCustomPrompt')"
|
||||
rows="4"
|
||||
@change="onCustomPromptChange"
|
||||
/>
|
||||
</SettingRow>
|
||||
</div>
|
||||
|
||||
<!-- all 二次确认弹层(独立,不与 Settings 壳层共用:壳层按钮写死「删除」不适合「确认接管」) -->
|
||||
@@ -108,9 +118,11 @@ const settings = reactive({
|
||||
// 审批超时(ms):0=不限时,缺省 900000(15min);仅固定合法选项,脏值回退默认
|
||||
approvalTimeout: clampApprovalTimeout(appSettings.get<number>('df-approval-timeout', 900000)),
|
||||
})
|
||||
const customPrompt = ref(appSettings.get<string>('custom_prompt', ''))
|
||||
|
||||
const rowAutoExecute = useTemplateRef<InstanceType<typeof SettingRow>>('rowAutoExecute')
|
||||
const rowLogLevel = useTemplateRef<InstanceType<typeof SettingRow>>('rowLogLevel')
|
||||
const rowCustomPrompt = useTemplateRef<InstanceType<typeof SettingRow>>('rowCustomPrompt')
|
||||
const rowApprovalTimeout = useTemplateRef<InstanceType<typeof SettingRow>>('rowApprovalTimeout')
|
||||
|
||||
// 上一次确认生效的档位:all 二次确认取消时恢复此值(非写死 low),避免 medium 用户误选 all
|
||||
@@ -152,6 +164,11 @@ function markSaved() {
|
||||
}
|
||||
|
||||
// 下一笔发起的审批即生效(aiShared.startApprovalTimer 每次实时读),已在跑的计时器沿用旧值。
|
||||
function onCustomPromptChange() {
|
||||
appSettings.set('custom_prompt', customPrompt.value)
|
||||
rowCustomPrompt.value?.markSaved()
|
||||
}
|
||||
|
||||
function onApprovalTimeoutChange() {
|
||||
appSettings.set('df-approval-timeout', settings.approvalTimeout)
|
||||
rowApprovalTimeout.value?.markSaved()
|
||||
@@ -192,6 +209,23 @@ function onApprovalTimeoutChange() {
|
||||
.confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.btn-danger { background: var(--df-danger); color: #fff; }
|
||||
.btn-danger:hover { filter: brightness(1.1); }
|
||||
.custom-prompt-input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-sm);
|
||||
background: var(--df-bg);
|
||||
color: var(--df-text);
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.custom-prompt-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--df-primary);
|
||||
}
|
||||
.confirm-enter-active, .confirm-leave-active { transition: opacity 0.15s; }
|
||||
.confirm-enter-from, .confirm-leave-to { opacity: 0; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user