feat(slash-menu): 扩展 / 菜单至19项,可见行数5→7,等比缩放样式,优化间距
This commit is contained in:
parent
af4ec5c1a8
commit
788753f6fd
@ -535,7 +535,7 @@ const stadiumInput = ref<HTMLTextAreaElement | null>(null);
|
||||
const fileUploadInput = ref<HTMLInputElement | null>(null);
|
||||
const baselineComposerVisualHeight = ref<number>(0);
|
||||
type SkillItem = { name: string; description: string; path: string };
|
||||
type SlashMenuMode = 'root' | 'skills' | 'theme';
|
||||
type SlashMenuMode = 'root' | 'skills' | 'theme' | 'permission' | 'execution' | 'model' | 'run-mode';
|
||||
type SlashMenuItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
@ -556,7 +556,7 @@ const skillSlashList = ref<HTMLElement | null>(null);
|
||||
const slashMenuMode = ref<SlashMenuMode>('root');
|
||||
const slashMenuParentIndex = ref(0);
|
||||
const slashMenuTransitioning = ref(false);
|
||||
const slashHighlightTop = ref(5);
|
||||
const slashHighlightTop = ref(4);
|
||||
let slashAnimId: number | null = null;
|
||||
|
||||
const slashHighlightStyle = computed(() => {
|
||||
@ -577,7 +577,7 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
|
||||
const startScroll = element.scrollTop;
|
||||
const startHighlight = slashHighlightTop.value;
|
||||
const distScroll = targetScroll - startScroll;
|
||||
const targetHighlight = 5 + skillSlashActiveIndex.value * 43 - targetScroll;
|
||||
const targetHighlight = 4 + skillSlashActiveIndex.value * 31 - targetScroll;
|
||||
const distHighlight = targetHighlight - startHighlight;
|
||||
const duration = 180;
|
||||
const startTime = performance.now();
|
||||
@ -723,7 +723,7 @@ const closeSlashMenu = () => {
|
||||
skillSlashActiveIndex.value = 0;
|
||||
slashMenuMode.value = 'root';
|
||||
slashMenuParentIndex.value = 0;
|
||||
slashHighlightTop.value = 5;
|
||||
slashHighlightTop.value = 4;
|
||||
};
|
||||
|
||||
const deleteSlashToken = () => {
|
||||
@ -876,6 +876,81 @@ const rootSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
action: () => {
|
||||
void personalizationStore.openDrawer();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'terminal',
|
||||
label: '实时终端',
|
||||
description: '打开实时终端面板',
|
||||
disabled: !props.isConnected || props.blockRealtimeTerminal,
|
||||
action: () => emit('realtime-terminal')
|
||||
},
|
||||
{
|
||||
id: 'model',
|
||||
label: '切换模型',
|
||||
description: '选择对话使用的模型',
|
||||
disabled: !props.isConnected || props.streamingMessage,
|
||||
mode: 'model'
|
||||
},
|
||||
{
|
||||
id: 'run-mode',
|
||||
label: '思考模式',
|
||||
description: '切换 fast / thinking / deep',
|
||||
disabled: !props.isConnected || props.streamingMessage,
|
||||
mode: 'run-mode'
|
||||
},
|
||||
{
|
||||
id: 'permission',
|
||||
label: '权限模式',
|
||||
description: '切换 readonly / approval / auto / unrestricted',
|
||||
disabled: !props.isConnected || props.streamingMessage,
|
||||
mode: 'permission'
|
||||
},
|
||||
...(props.executionModeEnabled
|
||||
? [{
|
||||
id: 'execution',
|
||||
label: '执行环境',
|
||||
description: '切换 sandbox / direct',
|
||||
disabled: !props.isConnected || props.streamingMessage,
|
||||
mode: 'execution'
|
||||
} as SlashMenuItem]
|
||||
: []),
|
||||
{
|
||||
id: 'versioning',
|
||||
label: '版本控制',
|
||||
description: props.versioningEnabled ? '当前:开启' : '当前:关闭',
|
||||
disabled: !props.isConnected || props.streamingMessage,
|
||||
action: () => emit('open-versioning-dialog')
|
||||
},
|
||||
{
|
||||
id: 'git-bar',
|
||||
label: 'Git 状态栏',
|
||||
description: (personalizationStore?.form?.show_git_status_bar !== false) ? '当前:显示' : '当前:隐藏',
|
||||
action: () => {
|
||||
const currentValue = personalizationStore?.form?.show_git_status_bar !== false;
|
||||
const newValue = !currentValue;
|
||||
if (personalizationStore?.form) {
|
||||
personalizationStore.form.show_git_status_bar = newValue;
|
||||
}
|
||||
fetch('/api/personalization', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ show_git_status_bar: newValue })
|
||||
}).catch(() => {});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'path-auth',
|
||||
label: '路径授权',
|
||||
description: '查看和管理路径授权',
|
||||
disabled: !props.isConnected,
|
||||
action: () => emit('open-path-authorization')
|
||||
},
|
||||
{
|
||||
id: 'approval',
|
||||
label: '审批面板',
|
||||
description: '查看审批记录',
|
||||
disabled: !props.isConnected,
|
||||
action: () => emit('toggle-approval-panel')
|
||||
}
|
||||
];
|
||||
const query = skillSlashQuery.value.trim().toLowerCase();
|
||||
@ -917,6 +992,55 @@ const themeSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
const permissionSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const options = (props.permissionOptions || []);
|
||||
const current = String(props.currentPermissionMode || '');
|
||||
return options.map((opt) => ({
|
||||
id: `perm:${opt.value}`,
|
||||
label: opt.label,
|
||||
description: `${opt.value === current ? '当前 · ' : ''}${opt.description || ''}`,
|
||||
action: () => emit('change-permission-mode', opt.value)
|
||||
}));
|
||||
});
|
||||
|
||||
const executionSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const options = (props.executionModeOptions || []);
|
||||
const current = String(props.currentExecutionMode || '');
|
||||
return options.map((opt) => ({
|
||||
id: `exec:${opt.value}`,
|
||||
label: opt.label,
|
||||
description: `${opt.value === current ? '当前 · ' : ''}${opt.description || ''}`,
|
||||
action: () => emit('change-execution-mode', opt.value)
|
||||
}));
|
||||
});
|
||||
|
||||
const modelSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const list = Array.isArray(props.modelOptions) ? props.modelOptions : [];
|
||||
const current = String(props.currentModelKey || '');
|
||||
return list.map((model) => ({
|
||||
id: `model:${model.key}`,
|
||||
label: model.label,
|
||||
description: `${model.key === current ? '当前 · ' : ''}${model.description || ''}`,
|
||||
disabled: !!model.disabled,
|
||||
action: () => emit('select-model', model.key)
|
||||
}));
|
||||
});
|
||||
|
||||
const runModeSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const modes: Array<{ value: string; label: string }> = [
|
||||
{ value: 'fast', label: '快速' },
|
||||
{ value: 'thinking', label: '思考' },
|
||||
{ value: 'deep', label: '深度' }
|
||||
];
|
||||
const current = String(props.runMode || '');
|
||||
return modes.map((mode) => ({
|
||||
id: `runmode:${mode.value}`,
|
||||
label: mode.label,
|
||||
description: mode.value === current ? '当前' : '',
|
||||
action: () => emit('select-run-mode', mode.value)
|
||||
}));
|
||||
});
|
||||
|
||||
const skillSlashMenuItems = computed<SlashMenuItem[]>(() =>
|
||||
filteredSkills.value.map((skill) => ({
|
||||
id: `skill:${skill.path}`,
|
||||
@ -933,17 +1057,36 @@ const activeSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
if (slashMenuMode.value === 'theme') {
|
||||
return themeSlashMenuItems.value;
|
||||
}
|
||||
if (slashMenuMode.value === 'permission') {
|
||||
return permissionSlashMenuItems.value;
|
||||
}
|
||||
if (slashMenuMode.value === 'execution') {
|
||||
return executionSlashMenuItems.value;
|
||||
}
|
||||
if (slashMenuMode.value === 'model') {
|
||||
return modelSlashMenuItems.value;
|
||||
}
|
||||
if (slashMenuMode.value === 'run-mode') {
|
||||
return runModeSlashMenuItems.value;
|
||||
}
|
||||
return rootSlashMenuItems.value;
|
||||
});
|
||||
|
||||
const slashMenuAriaLabel = computed(() => {
|
||||
if (slashMenuMode.value === 'skills') return '可用 AgentSkills';
|
||||
if (slashMenuMode.value === 'theme') return '主题选项';
|
||||
if (slashMenuMode.value === 'permission') return '权限模式选项';
|
||||
if (slashMenuMode.value === 'execution') return '执行环境选项';
|
||||
if (slashMenuMode.value === 'model') return '模型选项';
|
||||
if (slashMenuMode.value === 'run-mode') return '思考模式选项';
|
||||
return '快捷指令';
|
||||
});
|
||||
|
||||
const slashMenuEmptyText = computed(() => {
|
||||
if (slashMenuMode.value === 'skills') return skillsLoading.value ? '正在加载 skills...' : '没有匹配的 skill';
|
||||
if (slashMenuMode.value === 'permission') return '无可用权限模式';
|
||||
if (slashMenuMode.value === 'execution') return '无可用执行环境';
|
||||
if (slashMenuMode.value === 'model') return '无可用模型';
|
||||
return '没有匹配的指令';
|
||||
});
|
||||
|
||||
@ -1866,7 +2009,7 @@ const emitComposerHeight = () => {
|
||||
}
|
||||
const baseline = baselineComposerVisualHeight.value || visualHeight;
|
||||
const growth = Math.max(0, visualHeight - baseline);
|
||||
const baseReservedHeight = 90;
|
||||
const baseReservedHeight = 93;
|
||||
const reservedHeight = Math.ceil(baseReservedHeight + growth);
|
||||
emit('composer-height-change', {
|
||||
reservedHeight,
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
--runtime-queue-border: rgba(15, 23, 42, 0.12);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(100% - 2px);
|
||||
bottom: calc(100% + 1px);
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
@ -128,16 +128,16 @@
|
||||
}
|
||||
|
||||
.skill-slash-menu-wrapper {
|
||||
--skill-slash-row-height: 38px;
|
||||
--skill-slash-gap: 5px;
|
||||
--skill-slash-radius: 12px;
|
||||
--skill-slash-pad-x: 5px;
|
||||
--skill-slash-row-height: 28px;
|
||||
--skill-slash-gap: 3px;
|
||||
--skill-slash-radius: 9px;
|
||||
--skill-slash-pad-x: 4px;
|
||||
--skill-slash-motion-duration: 300ms;
|
||||
--skill-slash-motion-easing: cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
--skill-slash-visible-rows: 5;
|
||||
--skill-slash-visible-rows: 7;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(100% - 2px);
|
||||
bottom: calc(100% - 1px);
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
width: 90%;
|
||||
@ -146,7 +146,7 @@
|
||||
max-height: calc(
|
||||
(var(--skill-slash-row-height) * var(--skill-slash-visible-rows)) +
|
||||
(var(--skill-slash-gap) * (var(--skill-slash-visible-rows) + 1)) +
|
||||
2px
|
||||
2px + 2px - 2px
|
||||
);
|
||||
border: 1px solid var(--claude-border);
|
||||
border-top-left-radius: var(--skill-slash-radius);
|
||||
@ -187,7 +187,7 @@
|
||||
max-height: inherit;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
padding: var(--skill-slash-gap) var(--skill-slash-pad-x);
|
||||
padding: 4px var(--skill-slash-pad-x) 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--skill-slash-gap);
|
||||
@ -204,7 +204,7 @@
|
||||
right: var(--skill-slash-pad-x);
|
||||
top: var(--skill-slash-gap);
|
||||
height: var(--skill-slash-row-height);
|
||||
border-radius: 10px;
|
||||
border-radius: 7px;
|
||||
background: var(--hover-bg);
|
||||
box-shadow: 0 1px 2px var(--shadow-color);
|
||||
pointer-events: none;
|
||||
@ -218,10 +218,10 @@
|
||||
flex: 0 0 var(--skill-slash-row-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 7px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 7px;
|
||||
padding: 3px 9px;
|
||||
background: transparent;
|
||||
color: var(--claude-text);
|
||||
text-align: left;
|
||||
@ -262,9 +262,9 @@
|
||||
|
||||
.skill-slash-item__name {
|
||||
flex: 0 0 auto;
|
||||
max-width: 210px;
|
||||
max-width: 160px;
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@ -287,9 +287,9 @@
|
||||
flex: 0 0 var(--skill-slash-row-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 7px 12px;
|
||||
padding: 3px 9px;
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 13px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user