feat: restyle utility panel and streaming focus
This commit is contained in:
parent
79a90f4fad
commit
931a0488cc
@ -536,7 +536,7 @@ class WebTerminal(MainTerminal):
|
|||||||
|
|
||||||
|
|
||||||
# 如果是聚焦操作,广播聚焦文件更新
|
# 如果是聚焦操作,广播聚焦文件更新
|
||||||
if tool_name in ['focus_file', 'unfocus_file', 'modify_file']:
|
if tool_name in ['focus_file', 'unfocus_file', 'modify_file', 'append_to_file']:
|
||||||
try:
|
try:
|
||||||
focused_files_dict = self.get_focused_files_info()
|
focused_files_dict = self.get_focused_files_info()
|
||||||
self.broadcast('focused_files_update', focused_files_dict)
|
self.broadcast('focused_files_update', focused_files_dict)
|
||||||
|
|||||||
1
static/icons/layers.svg
Normal file
1
static/icons/layers.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/></svg>
|
||||||
|
After Width: | Height: | Size: 491 B |
@ -37,10 +37,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="main-container">
|
<div :class="['main-container', { 'utility-panel-hidden': utilityPanelHidden }]">
|
||||||
<ConversationSidebar
|
<ConversationSidebar
|
||||||
:icon-style="iconStyle"
|
:icon-style="iconStyle"
|
||||||
:format-time="formatTime"
|
:format-time="formatTime"
|
||||||
|
:utility-panel-hidden="utilityPanelHidden"
|
||||||
@toggle="toggleSidebar"
|
@toggle="toggleSidebar"
|
||||||
@create="createNewConversation"
|
@create="createNewConversation"
|
||||||
@search="handleSidebarSearch"
|
@search="handleSidebarSearch"
|
||||||
@ -49,6 +50,7 @@
|
|||||||
@personal="openPersonalPage"
|
@personal="openPersonalPage"
|
||||||
@delete="deleteConversation"
|
@delete="deleteConversation"
|
||||||
@duplicate="duplicateConversation"
|
@duplicate="duplicateConversation"
|
||||||
|
@toggle-utility-panel="toggleUtilityPanelVisibility"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
@ -60,12 +62,14 @@
|
|||||||
:is-connected="isConnected"
|
:is-connected="isConnected"
|
||||||
:panel-menu-open="panelMenuOpen"
|
:panel-menu-open="panelMenuOpen"
|
||||||
:panel-mode="panelMode"
|
:panel-mode="panelMode"
|
||||||
|
:hidden="utilityPanelHidden"
|
||||||
@toggle-panel-menu="togglePanelMenu"
|
@toggle-panel-menu="togglePanelMenu"
|
||||||
@select-panel="selectPanelMode"
|
@select-panel="selectPanelMode"
|
||||||
@open-file-manager="openGuiFileManager"
|
@open-file-manager="openGuiFileManager"
|
||||||
|
@toggle-mode="toggleThinkingMode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="resize-handle" @mousedown="startResize('left', $event)"></div>
|
<div v-if="!utilityPanelHidden" class="resize-handle" @mousedown="startResize('left', $event)"></div>
|
||||||
|
|
||||||
<main class="chat-container">
|
<main class="chat-container">
|
||||||
<TokenDrawer
|
<TokenDrawer
|
||||||
|
|||||||
@ -192,6 +192,7 @@ const appOptions = {
|
|||||||
...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
|
...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
|
||||||
...mapWritableState(useUiStore, [
|
...mapWritableState(useUiStore, [
|
||||||
'sidebarCollapsed',
|
'sidebarCollapsed',
|
||||||
|
'utilityPanelHidden',
|
||||||
'panelMode',
|
'panelMode',
|
||||||
'panelMenuOpen',
|
'panelMenuOpen',
|
||||||
'leftWidth',
|
'leftWidth',
|
||||||
@ -314,6 +315,8 @@ const appOptions = {
|
|||||||
uiSetPanelMode: 'setPanelMode',
|
uiSetPanelMode: 'setPanelMode',
|
||||||
uiSetPanelMenuOpen: 'setPanelMenuOpen',
|
uiSetPanelMenuOpen: 'setPanelMenuOpen',
|
||||||
uiTogglePanelMenu: 'togglePanelMenu',
|
uiTogglePanelMenu: 'togglePanelMenu',
|
||||||
|
uiToggleUtilityPanelHidden: 'toggleUtilityPanelHidden',
|
||||||
|
uiSetUtilityPanelHidden: 'setUtilityPanelHidden',
|
||||||
uiPushToast: 'pushToast',
|
uiPushToast: 'pushToast',
|
||||||
uiUpdateToast: 'updateToast',
|
uiUpdateToast: 'updateToast',
|
||||||
uiDismissToast: 'dismissToast',
|
uiDismissToast: 'dismissToast',
|
||||||
@ -1613,6 +1616,9 @@ const appOptions = {
|
|||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
this.uiToggleSidebar();
|
this.uiToggleSidebar();
|
||||||
},
|
},
|
||||||
|
toggleUtilityPanelVisibility() {
|
||||||
|
this.uiToggleUtilityPanelHidden();
|
||||||
|
},
|
||||||
|
|
||||||
togglePanelMenu() {
|
togglePanelMenu() {
|
||||||
this.uiTogglePanelMenu();
|
this.uiTogglePanelMenu();
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, onMounted, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useFocusStore } from '@/stores/focus';
|
import { useFocusStore } from '@/stores/focus';
|
||||||
|
|
||||||
@ -44,4 +44,23 @@ const focusedCount = computed(() => Object.keys(focusedFileMap.value).length);
|
|||||||
|
|
||||||
const languageClass = (path: string) => props.getLanguageClass(path);
|
const languageClass = (path: string) => props.getLanguageClass(path);
|
||||||
const formatSize = (size: number) => `${(size / 1024).toFixed(1)}KB`;
|
const formatSize = (size: number) => `${(size / 1024).toFixed(1)}KB`;
|
||||||
|
|
||||||
|
const refreshFocusedFiles = () => {
|
||||||
|
focusStore.fetchFocusedFiles().catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.collapsed) {
|
||||||
|
refreshFocusedFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.collapsed,
|
||||||
|
collapsed => {
|
||||||
|
if (!collapsed) {
|
||||||
|
refreshFocusedFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<aside class="sidebar left-sidebar" :style="{ width: width + 'px' }">
|
<aside
|
||||||
|
class="sidebar left-sidebar"
|
||||||
|
:class="{ 'panel-hidden': hidden }"
|
||||||
|
:style="panelStyles"
|
||||||
|
:aria-hidden="hidden ? 'true' : 'false'"
|
||||||
|
>
|
||||||
<div class="sidebar-status">
|
<div class="sidebar-status">
|
||||||
<div class="compact-status-card">
|
<div class="compact-status-card">
|
||||||
<div class="status-line">
|
<div class="status-line">
|
||||||
@ -11,7 +16,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-indicators">
|
<div class="status-indicators">
|
||||||
<span class="mode-indicator" :class="{ thinking: thinkingMode, fast: !thinkingMode }">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mode-indicator"
|
||||||
|
:class="{ thinking: thinkingMode, fast: !thinkingMode }"
|
||||||
|
:title="thinkingMode ? '切换到快速模式' : '切换到思考模式'"
|
||||||
|
:aria-pressed="thinkingMode ? 'true' : 'false'"
|
||||||
|
@click="$emit('toggle-mode')"
|
||||||
|
>
|
||||||
<transition name="mode-icon" mode="out-in">
|
<transition name="mode-icon" mode="out-in">
|
||||||
<span
|
<span
|
||||||
class="icon icon-sm"
|
class="icon icon-sm"
|
||||||
@ -20,7 +32,7 @@
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
</transition>
|
</transition>
|
||||||
</span>
|
</button>
|
||||||
<span class="connection-dot" :class="{ active: isConnected }" :title="isConnected ? '已连接' : '未连接'"></span>
|
<span class="connection-dot" :class="{ active: isConnected }" :title="isConnected ? '已连接' : '未连接'"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +134,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import FileNode from '@/components/files/FileNode.vue';
|
import FileNode from '@/components/files/FileNode.vue';
|
||||||
import { useFileStore } from '@/stores/file';
|
import { useFileStore } from '@/stores/file';
|
||||||
@ -138,12 +150,14 @@ const props = defineProps<{
|
|||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
panelMenuOpen: boolean;
|
panelMenuOpen: boolean;
|
||||||
panelMode: 'files' | 'todo' | 'subAgents';
|
panelMode: 'files' | 'todo' | 'subAgents';
|
||||||
|
hidden?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(event: 'toggle-panel-menu'): void;
|
(event: 'toggle-panel-menu'): void;
|
||||||
(event: 'select-panel', mode: 'files' | 'todo' | 'subAgents'): void;
|
(event: 'select-panel', mode: 'files' | 'todo' | 'subAgents'): void;
|
||||||
(event: 'open-file-manager'): void;
|
(event: 'open-file-manager'): void;
|
||||||
|
(event: 'toggle-mode'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const panelMenuWrapper = ref<HTMLElement | null>(null);
|
const panelMenuWrapper = ref<HTMLElement | null>(null);
|
||||||
@ -156,6 +170,15 @@ const openSubAgent = (agent: any) => {
|
|||||||
subAgentStore.openSubAgent(agent);
|
subAgentStore.openSubAgent(agent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const panelStyles = computed(() => {
|
||||||
|
const basis = props.hidden ? 0 : props.width;
|
||||||
|
return {
|
||||||
|
width: `${basis}px`,
|
||||||
|
minWidth: `${basis}px`,
|
||||||
|
flexBasis: `${basis}px`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
panelMenuWrapper
|
panelMenuWrapper
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,6 +54,20 @@
|
|||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="collapsed"
|
||||||
|
type="button"
|
||||||
|
class="collapsed-control-btn utility-panel-toggle-btn"
|
||||||
|
:class="{ active: utilityPanelHidden }"
|
||||||
|
:title="utilityPanelHidden ? '显示工作面板' : '隐藏工作面板'"
|
||||||
|
@click="$emit('toggle-utility-panel')"
|
||||||
|
:aria-pressed="utilityPanelHidden ? 'true' : 'false'"
|
||||||
|
>
|
||||||
|
<span class="sr-only">{{ utilityPanelHidden ? '显示' : '隐藏' }}工作面板</span>
|
||||||
|
<span class="layers-icon" aria-hidden="true">
|
||||||
|
<span class="icon icon-md" :style="iconStyle('layers')"></span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -153,6 +167,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineOptions({ name: 'ConversationSidebar' });
|
defineOptions({ name: 'ConversationSidebar' });
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useUiStore } from '@/stores/ui';
|
import { useUiStore } from '@/stores/ui';
|
||||||
import { useConversationStore } from '@/stores/conversation';
|
import { useConversationStore } from '@/stores/conversation';
|
||||||
@ -161,6 +176,7 @@ import { usePersonalizationStore } from '@/stores/personalization';
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
formatTime?: (input: unknown) => string;
|
formatTime?: (input: unknown) => string;
|
||||||
iconStyle?: (key: string) => Record<string, string>;
|
iconStyle?: (key: string) => Record<string, string>;
|
||||||
|
utilityPanelHidden?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
@ -172,6 +188,7 @@ defineEmits<{
|
|||||||
(event: 'personal'): void;
|
(event: 'personal'): void;
|
||||||
(event: 'delete', id: string): void;
|
(event: 'delete', id: string): void;
|
||||||
(event: 'duplicate', id: string): void;
|
(event: 'duplicate', id: string): void;
|
||||||
|
(event: 'toggle-utility-panel'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
@ -191,4 +208,5 @@ const { visible: personalPageVisible } = storeToRefs(personalizationStore);
|
|||||||
|
|
||||||
const formatTime = (value: unknown) => (props.formatTime ? props.formatTime(value) : String(value));
|
const formatTime = (value: unknown) => (props.formatTime ? props.formatTime(value) : String(value));
|
||||||
const iconStyle = (key: string) => (props.iconStyle ? props.iconStyle(key) : {});
|
const iconStyle = (key: string) => (props.iconStyle ? props.iconStyle(key) : {});
|
||||||
|
const utilityPanelHidden = computed(() => !!props.utilityPanelHidden);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -48,6 +48,7 @@ interface EasterEggState {
|
|||||||
|
|
||||||
interface UiState {
|
interface UiState {
|
||||||
sidebarCollapsed: boolean;
|
sidebarCollapsed: boolean;
|
||||||
|
utilityPanelHidden: boolean;
|
||||||
panelMode: PanelMode;
|
panelMode: PanelMode;
|
||||||
panelMenuOpen: boolean;
|
panelMenuOpen: boolean;
|
||||||
leftWidth: number;
|
leftWidth: number;
|
||||||
@ -69,6 +70,7 @@ interface UiState {
|
|||||||
export const useUiStore = defineStore('ui', {
|
export const useUiStore = defineStore('ui', {
|
||||||
state: (): UiState => ({
|
state: (): UiState => ({
|
||||||
sidebarCollapsed: true,
|
sidebarCollapsed: true,
|
||||||
|
utilityPanelHidden: false,
|
||||||
panelMode: 'files',
|
panelMode: 'files',
|
||||||
panelMenuOpen: false,
|
panelMenuOpen: false,
|
||||||
leftWidth: 350,
|
leftWidth: 350,
|
||||||
@ -98,6 +100,12 @@ export const useUiStore = defineStore('ui', {
|
|||||||
setSidebarCollapsed(collapsed: boolean) {
|
setSidebarCollapsed(collapsed: boolean) {
|
||||||
this.sidebarCollapsed = collapsed;
|
this.sidebarCollapsed = collapsed;
|
||||||
},
|
},
|
||||||
|
setUtilityPanelHidden(hidden: boolean) {
|
||||||
|
this.utilityPanelHidden = hidden;
|
||||||
|
},
|
||||||
|
toggleUtilityPanelHidden() {
|
||||||
|
this.utilityPanelHidden = !this.utilityPanelHidden;
|
||||||
|
},
|
||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
this.sidebarCollapsed = !this.sidebarCollapsed;
|
this.sidebarCollapsed = !this.sidebarCollapsed;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,18 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-container.utility-panel-hidden .messages-area {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: clamp(24px, 6vw, 72px);
|
||||||
|
padding-right: clamp(24px, 6vw, 72px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container.utility-panel-hidden .message-block {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-container .input-area {
|
.chat-container .input-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
/* 聚焦文件 */
|
/* 聚焦文件 */
|
||||||
.focused-files {
|
.focused-files {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
o-files {
|
.no-files {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--claude-text-secondary);
|
color: var(--claude-text-secondary);
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px dashed var(--claude-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tabs {
|
.file-tabs {
|
||||||
@ -17,21 +21,21 @@ o-files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-tab {
|
.file-tab {
|
||||||
border: 1px solid var(--claude-border);
|
border: 1px solid rgba(118, 103, 84, 0.15);
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: rgba(255, 255, 255, 0.75);
|
background: #fff;
|
||||||
box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08);
|
box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-header {
|
.tab-header {
|
||||||
background: rgba(218, 119, 86, 0.08);
|
background: rgba(247, 244, 240, 0.9);
|
||||||
padding: 10px 16px;
|
padding: 10px 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-bottom: 1px solid var(--claude-border);
|
border-bottom: 1px solid rgba(118, 103, 84, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
@ -45,19 +49,22 @@ o-files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-content {
|
.file-content {
|
||||||
max-height: 320px;
|
max-height: 340px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: #1e1e1e;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-content pre {
|
.file-content pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 16px;
|
padding: 16px 20px;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-content code {
|
.file-content code {
|
||||||
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #aed581;
|
color: #2c2c2c;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,24 @@
|
|||||||
.sidebar.left-sidebar {
|
.sidebar.left-sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
transition:
|
||||||
|
width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
min-width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
flex-basis 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
transform 0.35s ease,
|
||||||
|
opacity 0.35s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.left-sidebar.panel-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-80px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.left-sidebar.panel-hidden .sidebar-status,
|
||||||
|
.sidebar.left-sidebar.panel-hidden .sidebar-panel-card-wrapper {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-status {
|
.sidebar-status {
|
||||||
@ -86,6 +104,8 @@
|
|||||||
color: #fffef8;
|
color: #fffef8;
|
||||||
box-shadow: 0 8px 20px rgba(189, 93, 58, 0.25);
|
box-shadow: 0 8px 20px rgba(189, 93, 58, 0.25);
|
||||||
transition: background 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
|
transition: background 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-indicator.fast {
|
.mode-indicator.fast {
|
||||||
@ -224,6 +244,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-panel-card .sidebar-header {
|
.sidebar-panel-card .sidebar-header {
|
||||||
|
|||||||
@ -142,6 +142,34 @@
|
|||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.utility-panel-toggle-btn {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 24px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #2f251b;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: color 0.25s ease, transform 0.2s ease;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.utility-panel-toggle-btn .icon {
|
||||||
|
--icon-size: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.utility-panel-toggle-btn:hover,
|
||||||
|
.utility-panel-toggle-btn:focus-visible {
|
||||||
|
color: var(--claude-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.utility-panel-toggle-btn.active {
|
||||||
|
color: #2f251b;
|
||||||
|
}
|
||||||
|
|
||||||
.new-conversation-btn {
|
.new-conversation-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%);
|
background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%);
|
||||||
|
|||||||
@ -9,6 +9,14 @@
|
|||||||
color: var(--claude-text);
|
color: var(--claude-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-container.utility-panel-hidden {
|
||||||
|
background: var(--claude-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container.utility-panel-hidden .chat-container {
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
min-height: var(--app-viewport, 100vh);
|
min-height: var(--app-viewport, 100vh);
|
||||||
background: var(--claude-bg);
|
background: var(--claude-bg);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const ICONS = Object.freeze({
|
|||||||
hammer: '/static/icons/hammer.svg',
|
hammer: '/static/icons/hammer.svg',
|
||||||
info: '/static/icons/info.svg',
|
info: '/static/icons/info.svg',
|
||||||
laptop: '/static/icons/laptop.svg',
|
laptop: '/static/icons/laptop.svg',
|
||||||
|
layers: '/static/icons/layers.svg',
|
||||||
menu: '/static/icons/menu.svg',
|
menu: '/static/icons/menu.svg',
|
||||||
monitor: '/static/icons/monitor.svg',
|
monitor: '/static/icons/monitor.svg',
|
||||||
octagon: '/static/icons/octagon.svg',
|
octagon: '/static/icons/octagon.svg',
|
||||||
|
|||||||
@ -525,7 +525,7 @@ class WebTerminal(MainTerminal):
|
|||||||
|
|
||||||
|
|
||||||
# 如果是聚焦操作,广播聚焦文件更新
|
# 如果是聚焦操作,广播聚焦文件更新
|
||||||
if tool_name in ['focus_file', 'unfocus_file', 'modify_file']:
|
if tool_name in ['focus_file', 'unfocus_file', 'modify_file', 'append_to_file']:
|
||||||
try:
|
try:
|
||||||
focused_files_dict = self.get_focused_files_info()
|
focused_files_dict = self.get_focused_files_info()
|
||||||
self.broadcast('focused_files_update', focused_files_dict)
|
self.broadcast('focused_files_update', focused_files_dict)
|
||||||
|
|||||||
@ -2757,6 +2757,10 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
if refreshed.get("success"):
|
if refreshed.get("success"):
|
||||||
web_terminal.focused_files[path] = refreshed["content"]
|
web_terminal.focused_files[path] = refreshed["content"]
|
||||||
debug_log(f"聚焦文件已刷新: {path}")
|
debug_log(f"聚焦文件已刷新: {path}")
|
||||||
|
try:
|
||||||
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
except Exception as focus_exc:
|
||||||
|
debug_log(f"广播聚焦文件更新失败: {focus_exc}")
|
||||||
|
|
||||||
debug_log(f"追加写入完成: {summary}")
|
debug_log(f"追加写入完成: {summary}")
|
||||||
else:
|
else:
|
||||||
@ -3108,6 +3112,10 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
if refreshed.get("success"):
|
if refreshed.get("success"):
|
||||||
web_terminal.focused_files[path] = refreshed["content"]
|
web_terminal.focused_files[path] = refreshed["content"]
|
||||||
debug_log(f"聚焦文件已刷新: {path}")
|
debug_log(f"聚焦文件已刷新: {path}")
|
||||||
|
try:
|
||||||
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
except Exception as focus_exc:
|
||||||
|
debug_log(f"广播聚焦文件更新失败: {focus_exc}")
|
||||||
|
|
||||||
pending_modify = None
|
pending_modify = None
|
||||||
modify_probe_buffer = ""
|
modify_probe_buffer = ""
|
||||||
@ -4093,7 +4101,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
sender('update_action', update_payload)
|
sender('update_action', update_payload)
|
||||||
|
|
||||||
# 更新UI状态
|
# 更新UI状态
|
||||||
if function_name in ['focus_file', 'unfocus_file', 'modify_file']:
|
if function_name in ['focus_file', 'unfocus_file', 'modify_file', 'append_to_file']:
|
||||||
sender('focused_files_update', web_terminal.get_focused_files_info())
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
|
||||||
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
||||||
|
|||||||
@ -2662,6 +2662,10 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
if refreshed.get("success"):
|
if refreshed.get("success"):
|
||||||
web_terminal.focused_files[path] = refreshed["content"]
|
web_terminal.focused_files[path] = refreshed["content"]
|
||||||
debug_log(f"聚焦文件已刷新: {path}")
|
debug_log(f"聚焦文件已刷新: {path}")
|
||||||
|
try:
|
||||||
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
except Exception as focus_exc:
|
||||||
|
debug_log(f"广播聚焦文件更新失败: {focus_exc}")
|
||||||
|
|
||||||
debug_log(f"追加写入完成: {summary}")
|
debug_log(f"追加写入完成: {summary}")
|
||||||
else:
|
else:
|
||||||
@ -2961,6 +2965,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
summary_parts.append(apply_result["error"])
|
summary_parts.append(apply_result["error"])
|
||||||
|
|
||||||
matching_note = "提示:补丁匹配基于完整文本,包含注释和空白符,请确保 <<<OLD>>> 段落与文件内容逐字一致。如果修改成功,请忽略,如果失败,请明确原文后再次尝试。"
|
matching_note = "提示:补丁匹配基于完整文本,包含注释和空白符,请确保 <<<OLD>>> 段落与文件内容逐字一致。如果修改成功,请忽略,如果失败,请明确原文后再次尝试。"
|
||||||
|
if failed_blocks or not completed_blocks:
|
||||||
summary_parts.append(matching_note)
|
summary_parts.append(matching_note)
|
||||||
summary_message = " ".join(summary_parts).strip()
|
summary_message = " ".join(summary_parts).strip()
|
||||||
result["summary_message"] = summary_message
|
result["summary_message"] = summary_message
|
||||||
@ -3013,6 +3018,10 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
if refreshed.get("success"):
|
if refreshed.get("success"):
|
||||||
web_terminal.focused_files[path] = refreshed["content"]
|
web_terminal.focused_files[path] = refreshed["content"]
|
||||||
debug_log(f"聚焦文件已刷新: {path}")
|
debug_log(f"聚焦文件已刷新: {path}")
|
||||||
|
try:
|
||||||
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
except Exception as focus_exc:
|
||||||
|
debug_log(f"广播聚焦文件更新失败: {focus_exc}")
|
||||||
|
|
||||||
pending_modify = None
|
pending_modify = None
|
||||||
modify_probe_buffer = ""
|
modify_probe_buffer = ""
|
||||||
@ -4089,7 +4098,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
sender('update_action', update_payload)
|
sender('update_action', update_payload)
|
||||||
|
|
||||||
# 更新UI状态
|
# 更新UI状态
|
||||||
if function_name in ['focus_file', 'unfocus_file', 'modify_file']:
|
if function_name in ['focus_file', 'unfocus_file', 'modify_file', 'append_to_file']:
|
||||||
sender('focused_files_update', web_terminal.get_focused_files_info())
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
|
||||||
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user