Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qjclaw-dmg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qjclaw-dmg
Commits
0f949b0d
Commit
0f949b0d
authored
May 15, 2026
by
edy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(ui): stabilize sidebar and knowledge layouts
parent
8645a0f7
Pipeline
#18462
failed
Changes
9
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
361 additions
and
93 deletions
+361
-93
App.tsx
apps/ui/src/App.tsx
+4
-1
AppIcons.tsx
apps/ui/src/components/icons/AppIcons.tsx
+3
-4
AppSidebar.tsx
apps/ui/src/features/shell/AppSidebar.tsx
+15
-3
Sidebar.tsx
apps/ui/src/features/shell/Sidebar.tsx
+23
-1
chat.css
apps/ui/src/styles/chat.css
+1
-1
knowledge.css
apps/ui/src/styles/knowledge.css
+110
-79
shell.css
apps/ui/src/styles/shell.css
+1
-1
theme-openclaw.css
apps/ui/src/styles/theme-openclaw.css
+123
-3
sidebarKnowledgeSource.test.ts
apps/ui/test/sidebarKnowledgeSource.test.ts
+81
-0
No files found.
apps/ui/src/App.tsx
View file @
0f949b0d
...
@@ -285,6 +285,7 @@ export default function App() {
...
@@ -285,6 +285,7 @@ export default function App() {
const
[
expandedCategories
,
setExpandedCategories
]
=
useState
<
Record
<
string
,
boolean
>>
({});
const
[
expandedCategories
,
setExpandedCategories
]
=
useState
<
Record
<
string
,
boolean
>>
({});
const
[
messageReactions
,
setMessageReactions
]
=
useState
<
Record
<
string
,
MessageReaction
|
undefined
>>
({});
const
[
messageReactions
,
setMessageReactions
]
=
useState
<
Record
<
string
,
MessageReaction
|
undefined
>>
({});
const
[
sidebarSessionTitles
,
setSidebarSessionTitles
]
=
useState
<
Record
<
string
,
string
>>
({});
const
[
sidebarSessionTitles
,
setSidebarSessionTitles
]
=
useState
<
Record
<
string
,
string
>>
({});
const
[
isSidebarCollapsed
,
setIsSidebarCollapsed
]
=
useState
(
false
);
const
[
skillMenuOpen
,
setSkillMenuOpen
]
=
useState
(
false
);
const
[
skillMenuOpen
,
setSkillMenuOpen
]
=
useState
(
false
);
const
[
copiedToken
,
setCopiedToken
]
=
useState
(
""
);
const
[
copiedToken
,
setCopiedToken
]
=
useState
(
""
);
const
skillMenuRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
skillMenuRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
...
@@ -1553,7 +1554,7 @@ export default function App() {
...
@@ -1553,7 +1554,7 @@ export default function App() {
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
return
(
return
(
<
div
className=
{
"shell openclaw-theme"
+
(
isConversationView
?
" conversation-shell"
:
""
)
+
(
viewMode
===
"experts"
?
" conversation-shell-experts"
:
""
)
}
>
<
div
className=
{
"shell openclaw-theme"
+
(
isConversationView
?
" conversation-shell"
:
""
)
+
(
viewMode
===
"experts"
?
" conversation-shell-experts"
:
""
)
+
(
isSidebarCollapsed
?
" sidebar-collapsed"
:
""
)
}
>
<
TopBar
onMinimize=
{
minimizeWindow
}
onMaximize=
{
maximizeWindow
}
onClose=
{
closeWindow
}
/>
<
TopBar
onMinimize=
{
minimizeWindow
}
onMaximize=
{
maximizeWindow
}
onClose=
{
closeWindow
}
/>
<
AppSidebar
<
AppSidebar
viewMode=
{
viewMode
}
viewMode=
{
viewMode
}
...
@@ -1569,6 +1570,8 @@ export default function App() {
...
@@ -1569,6 +1570,8 @@ export default function App() {
activeStreamSessionId=
{
activeStreamRef
.
current
?.
sessionId
}
activeStreamSessionId=
{
activeStreamRef
.
current
?.
sessionId
}
sidebarSessionLabel=
{
sidebarSessionLabel
}
sidebarSessionLabel=
{
sidebarSessionLabel
}
formatSessionTitle=
{
formatSessionTitle
}
formatSessionTitle=
{
formatSessionTitle
}
isCollapsed=
{
isSidebarCollapsed
}
onToggleCollapsed=
{
()
=>
setIsSidebarCollapsed
((
current
)
=>
!
current
)
}
navIcon=
{
(
kind
)
=>
<
NavIcon
kind=
{
kind
}
/>
}
navIcon=
{
(
kind
)
=>
<
NavIcon
kind=
{
kind
}
/>
}
onNavSelection=
{
handleNavSelection
}
onNavSelection=
{
handleNavSelection
}
sidebarNewSessionAction=
{
sidebarNewSessionAction
}
sidebarNewSessionAction=
{
sidebarNewSessionAction
}
...
...
apps/ui/src/components/icons/AppIcons.tsx
View file @
0f949b0d
...
@@ -268,10 +268,9 @@ export function NavIcon({ kind }: { kind: "chat" | "experts" | "tasks" | "plugin
...
@@ -268,10 +268,9 @@ export function NavIcon({ kind }: { kind: "chat" | "experts" | "tasks" | "plugin
case
"knowledge"
:
case
"knowledge"
:
return
(
return
(
<
svg
viewBox=
"0 0 24 24"
aria
-
hidden=
"true"
focusable=
"false"
>
<
svg
viewBox=
"0 0 24 24"
aria
-
hidden=
"true"
focusable=
"false"
>
<
path
d=
"M6 4.5A2.5 2.5 0 0 1 8.5 2h7.1L20 6.4v11.1a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 6 17.5v-13Z"
fill=
"#DBEAFE"
stroke=
"#2563EB"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"1.45"
/>
<
path
d=
"M5.25 5.7A2.45 2.45 0 0 1 7.7 3.25H11c1.1 0 2.12.34 3 .92.88-.58 1.9-.92 3-.92h1.05c.66 0 1.2.54 1.2 1.2v13.6c0 .66-.54 1.2-1.2 1.2H17c-1.1 0-2.12.34-3 .92-.88-.58-1.9-.92-3-.92H7.7a2.45 2.45 0 0 1-2.45-2.45V5.7Z"
fill=
"#DBEAFE"
stroke=
"#2563EB"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"1.45"
/>
<
path
d=
"M15.45 2.2v3.1c0 .82.66 1.48 1.48 1.48h2.92"
fill=
"#BFDBFE"
stroke=
"#60A5FA"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"1.35"
/>
<
path
d=
"M12 6.65v11.8M8.45 7.95h2.15M8.45 11h2.15M15.4 7.95h1.95M15.4 11h1.95"
fill=
"none"
stroke=
"#1D4ED8"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"1.35"
/>
<
path
d=
"M9.1 10.2h5.8M9.1 13.15h4.25"
fill=
"none"
stroke=
"#1D4ED8"
strokeLinecap=
"round"
strokeWidth=
"1.35"
/>
<
path
d=
"M14 4.17v16"
fill=
"none"
stroke=
"#60A5FA"
strokeLinecap=
"round"
strokeWidth=
"1.2"
/>
<
path
d=
"m16.7 12.55.38.8.82.12-.6.58.15.82-.75-.4-.75.4.15-.82-.6-.58.82-.12.38-.8Z"
fill=
"#F59E0B"
/>
</
svg
>
</
svg
>
);
);
case
"settings"
:
case
"settings"
:
...
...
apps/ui/src/features/shell/AppSidebar.tsx
View file @
0f949b0d
...
@@ -26,6 +26,8 @@ interface AppSidebarProps {
...
@@ -26,6 +26,8 @@ interface AppSidebarProps {
activeStreamSessionId
?:
string
activeStreamSessionId
?:
string
sidebarSessionLabel
:
string
sidebarSessionLabel
:
string
formatSessionTitle
(
title
:
string
,
index
:
number
):
string
formatSessionTitle
(
title
:
string
,
index
:
number
):
string
isCollapsed
:
boolean
onToggleCollapsed
():
void
navIcon
(
kind
:
ViewMode
):
ReactNode
navIcon
(
kind
:
ViewMode
):
ReactNode
onNavSelection
(
mode
:
ViewMode
):
void
onNavSelection
(
mode
:
ViewMode
):
void
sidebarNewSessionAction
:
ReactNode
sidebarNewSessionAction
:
ReactNode
...
@@ -53,6 +55,8 @@ export function AppSidebar({
...
@@ -53,6 +55,8 @@ export function AppSidebar({
activeStreamSessionId
,
activeStreamSessionId
,
sidebarSessionLabel
,
sidebarSessionLabel
,
formatSessionTitle
,
formatSessionTitle
,
isCollapsed
,
onToggleCollapsed
,
navIcon
,
navIcon
,
onNavSelection
,
onNavSelection
,
sidebarNewSessionAction
,
sidebarNewSessionAction
,
...
@@ -72,7 +76,6 @@ export function AppSidebar({
...
@@ -72,7 +76,6 @@ export function AppSidebar({
{
id
:
"chat"
as
const
,
label
:
"对话"
},
{
id
:
"chat"
as
const
,
label
:
"对话"
},
{
id
:
"tasks"
as
const
,
label
:
"工作台"
},
{
id
:
"tasks"
as
const
,
label
:
"工作台"
},
{
id
:
"knowledge"
as
const
,
label
:
ui
.
knowledge
},
{
id
:
"knowledge"
as
const
,
label
:
ui
.
knowledge
},
{
id
:
"plugins"
as
const
,
label
:
ui
.
plugins
},
{
id
:
"settings"
as
const
,
label
:
ui
.
settings
}
{
id
:
"settings"
as
const
,
label
:
ui
.
settings
}
].
map
((
item
)
=>
(
].
map
((
item
)
=>
(
<
button
<
button
...
@@ -89,7 +92,9 @@ export function AppSidebar({
...
@@ -89,7 +92,9 @@ export function AppSidebar({
</
button
>
</
button
>
))
}
))
}
</
nav
>
</
nav
>
{
!
showBindEntry
?
sidebarNewSessionAction
:
null
}
<
div
className=
"conversation-sidebar-action"
>
{
!
showBindEntry
?
sidebarNewSessionAction
:
null
}
</
div
>
</>
</>
)
)
...
@@ -123,5 +128,12 @@ export function AppSidebar({
...
@@ -123,5 +128,12 @@ export function AppSidebar({
</>
</>
)
)
return
<
Sidebar
topContent=
{
topContent
}
bottomContent=
{
bottomContent
}
/>
return
(
<
Sidebar
topContent=
{
topContent
}
bottomContent=
{
bottomContent
}
isCollapsed=
{
isCollapsed
}
onToggleCollapsed=
{
onToggleCollapsed
}
/>
)
}
}
apps/ui/src/features/shell/Sidebar.tsx
View file @
0f949b0d
import
type
{
ReactNode
}
from
"react"
import
type
{
ReactNode
}
from
"react"
import
brandIcon
from
"../../assets/brand-icon.png"
interface
SidebarProps
{
interface
SidebarProps
{
topContent
:
ReactNode
topContent
:
ReactNode
bottomContent
:
ReactNode
bottomContent
:
ReactNode
isCollapsed
:
boolean
onToggleCollapsed
():
void
}
}
export
function
Sidebar
({
topContent
,
bottomContent
}:
SidebarProps
)
{
export
function
Sidebar
({
topContent
,
bottomContent
,
isCollapsed
,
onToggleCollapsed
}:
SidebarProps
)
{
return
(
return
(
<
aside
className=
"sidebar conversation-sidebar-layout app-drag-region"
>
<
aside
className=
"sidebar conversation-sidebar-layout app-drag-region"
>
<
div
className=
"sidebar-brand"
>
<
img
src=
{
brandIcon
}
alt=
""
className=
"sidebar-brand-logo"
/>
<
strong
className=
"sidebar-brand-name"
>
千匠问天
</
strong
>
<
button
type=
"button"
className=
"sidebar-collapse-button app-no-drag"
aria
-
label=
{
isCollapsed
?
"展开侧栏"
:
"折叠侧栏"
}
aria
-
expanded=
{
!
isCollapsed
}
onClick=
{
onToggleCollapsed
}
>
<
svg
viewBox=
"0 0 24 24"
aria
-
hidden=
"true"
focusable=
"false"
>
{
isCollapsed
?
(
<
path
d=
"m10 7 5 5-5 5"
fill=
"none"
stroke=
"currentColor"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"2"
/>
)
:
(
<
path
d=
"m14 7-5 5 5 5"
fill=
"none"
stroke=
"currentColor"
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
"2"
/>
)
}
</
svg
>
</
button
>
</
div
>
<
div
className=
"sidebar-top"
>
<
div
className=
"sidebar-top"
>
{
topContent
}
{
topContent
}
</
div
>
</
div
>
...
...
apps/ui/src/styles/chat.css
View file @
0f949b0d
...
@@ -130,7 +130,7 @@
...
@@ -130,7 +130,7 @@
.conversation-sidebar-layout
{
.conversation-sidebar-layout
{
position
:
relative
;
position
:
relative
;
isolation
:
isolate
;
isolation
:
isolate
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
auto
minmax
(
0
,
1
fr
);
gap
:
14px
;
gap
:
14px
;
padding
:
18px
16px
;
padding
:
18px
16px
;
}
}
...
...
apps/ui/src/styles/knowledge.css
View file @
0f949b0d
.knowledge-page-stack
{
.knowledge-page-stack
{
height
:
100%
;
height
:
100%
;
min-height
:
0
;
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
}
}
.knowledge-panel
{
.knowledge-panel
{
flex
:
1
;
flex
:
1
;
min-height
:
0
;
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
padding
:
32px
40px
;
padding
:
28px
32px
;
gap
:
32px
;
gap
:
24px
;
border-color
:
rgba
(
148
,
163
,
184
,
0.24
);
background
:
#ffffff
;
box-shadow
:
0
8px
18px
rgba
(
15
,
23
,
42
,
0.04
);
}
}
.knowledge-panel-body
{
.knowledge-panel-body
{
...
@@ -17,157 +22,186 @@
...
@@ -17,157 +22,186 @@
min-height
:
0
;
min-height
:
0
;
flex
:
1
;
flex
:
1
;
flex-direction
:
column
;
flex-direction
:
column
;
gap
:
32
px
;
gap
:
24
px
;
padding
:
0
;
padding
:
0
;
overflow
:
auto
;
}
}
.knowledge-header
{
.knowledge-header
{
text-align
:
center
;
text-align
:
left
;
margin
-bottom
:
16px
;
margin
:
0
;
}
}
.knowledge-title
{
.knowledge-title
{
font-size
:
32px
;
margin
:
0
0
8px
;
color
:
#12355f
;
font-size
:
28px
;
font-weight
:
700
;
font-weight
:
700
;
margin
:
0
0
8px
0
;
line-height
:
1.25
;
background
:
linear-gradient
(
135deg
,
#7C3AED
0%
,
#A78BFA
100%
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
}
}
.knowledge-subtitle
{
.knowledge-subtitle
{
font-size
:
16px
;
color
:
#94A3B8
;
margin
:
0
;
margin
:
0
;
color
:
#486985
;
font-size
:
14px
;
line-height
:
1.6
;
}
}
.knowledge-upload-section
{
.knowledge-upload-section
{
display
:
flex
;
display
:
flex
;
justify-content
:
center
;
justify-content
:
center
;
margin
:
32
px
0
;
margin
:
8
px
0
;
}
}
.upload-card
{
.upload-card
{
background
:
rgba
(
255
,
255
,
255
,
0.08
);
width
:
min
(
520px
,
100%
);
backdrop-filter
:
blur
(
12px
);
padding
:
36px
40px
;
-webkit-backdrop-filter
:
blur
(
12px
);
border
:
2px
dashed
rgba
(
124
,
58
,
237
,
0.3
);
border-radius
:
24px
;
padding
:
48px
64px
;
text-align
:
center
;
text-align
:
center
;
max-width
:
500px
;
border
:
1px
dashed
rgba
(
59
,
130
,
246
,
0.34
);
width
:
100%
;
border-radius
:
12px
;
transition
:
all
0.3s
ease
;
background
:
#ffffff
;
box-shadow
:
0
10px
24px
rgba
(
37
,
99
,
235
,
0.08
);
transition
:
border-color
180ms
ease
,
background
180ms
ease
,
box-shadow
180ms
ease
;
}
}
.upload-card
:hover
{
.upload-card
:hover
{
border-color
:
rgba
(
124
,
58
,
237
,
0.6
);
border-color
:
rgba
(
37
,
99
,
235
,
0.56
);
background
:
rgba
(
255
,
255
,
255
,
0.12
);
background
:
#f8fbff
;
transform
:
translateY
(
-4px
);
box-shadow
:
0
14px
30px
rgba
(
37
,
99
,
235
,
0.12
);
box-shadow
:
0
20px
40px
rgba
(
124
,
58
,
237
,
0.15
);
}
}
.upload-icon
{
.upload-icon
{
margin-bottom
:
24px
;
width
:
56px
;
height
:
56px
;
display
:
inline-grid
;
place-items
:
center
;
margin-bottom
:
20px
;
border-radius
:
12px
;
background
:
#e0edff
;
color
:
#2563eb
;
box-shadow
:
inset
0
0
0
1px
rgba
(
37
,
99
,
235
,
0.14
);
}
}
.upload-icon
svg
{
.upload-icon
svg
{
stroke
:
#A78BFA
;
width
:
34px
;
height
:
34px
;
stroke
:
currentColor
;
}
}
.upload-title
{
.upload-title
{
font-size
:
24px
;
margin
:
0
0
10px
;
font-weight
:
600
;
color
:
#12355f
;
margin
:
0
0
12px
0
;
font-size
:
20px
;
color
:
#E2E8F0
;
font-weight
:
700
;
line-height
:
1.35
;
}
}
.upload-desc
{
.upload-desc
{
margin
:
0
0
22px
;
color
:
#64748b
;
font-size
:
14px
;
font-size
:
14px
;
color
:
#94A3B8
;
line-height
:
1.6
;
margin
:
0
0
24px
0
;
}
}
.upload-button
{
.upload-button
{
background
:
linear-gradient
(
135deg
,
#7C3AED
0%
,
#A78BFA
100%
);
min-height
:
44px
;
color
:
white
;
padding
:
0
24px
;
border
:
none
;
border
:
1px
solid
rgba
(
29
,
78
,
216
,
0.18
);
border-radius
:
12px
;
border-radius
:
10px
;
padding
:
16px
32px
;
background
:
linear-gradient
(
135deg
,
#2563eb
,
#0f6eea
);
font-size
:
16px
;
color
:
#ffffff
;
font-weight
:
600
;
font-size
:
14px
;
cursor
:
pointer
;
font-weight
:
700
;
transition
:
all
0.3s
ease
;
box-shadow
:
0
10px
22px
rgba
(
37
,
99
,
235
,
0.24
);
margin-bottom
:
16px
;
transition
:
border-color
180ms
ease
,
background
180ms
ease
,
box-shadow
180ms
ease
;
margin-bottom
:
14px
;
}
}
.upload-button
:hover
{
.upload-button
:hover
:not
(
:disabled
)
{
opacity
:
0.9
;
border-color
:
rgba
(
29
,
78
,
216
,
0.32
)
;
transform
:
translateY
(
-2px
);
background
:
linear-gradient
(
135deg
,
#1d4ed8
,
#075bd8
);
box-shadow
:
0
1
0px
20px
rgba
(
124
,
58
,
237
,
0.3
);
box-shadow
:
0
1
2px
24px
rgba
(
37
,
99
,
235
,
0.28
);
}
}
.upload-hint
{
.upload-hint
{
font-size
:
12px
;
color
:
#64748B
;
margin
:
0
;
margin
:
0
;
color
:
#64748b
;
font-size
:
12px
;
line-height
:
1.5
;
}
}
.knowledge-list-section
{
.knowledge-list-section
{
margin-top
:
48px
;
display
:
grid
;
gap
:
16px
;
margin-top
:
8px
;
}
}
.section-title
{
.section-title
{
font-size
:
20px
;
margin
:
0
;
font-weight
:
600
;
color
:
#12355f
;
margin
:
0
0
24px
0
;
font-size
:
18px
;
color
:
#E2E8F0
;
font-weight
:
700
;
line-height
:
1.35
;
}
}
.document-grid
{
.document-grid
{
display
:
grid
;
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
2
8
0px
,
1
fr
));
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
2
6
0px
,
1
fr
));
gap
:
20
px
;
gap
:
14
px
;
}
}
.document-card
{
.document-card
{
background
:
rgba
(
255
,
255
,
255
,
0.05
);
min-height
:
76px
;
border
:
1px
solid
rgba
(
124
,
58
,
237
,
0.2
);
border-radius
:
16px
;
padding
:
20px
;
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
gap
:
16px
;
gap
:
14px
;
transition
:
all
0.3s
ease
;
padding
:
16px
;
border
:
1px
solid
rgba
(
59
,
130
,
246
,
0.22
);
border-radius
:
10px
;
background
:
#ffffff
;
color
:
#12355f
;
transition
:
border-color
180ms
ease
,
background
180ms
ease
,
box-shadow
180ms
ease
;
cursor
:
pointer
;
cursor
:
pointer
;
}
}
.document-card
:hover
{
.document-card
:hover
{
background
:
rgba
(
255
,
255
,
255
,
0.1
);
border-color
:
rgba
(
37
,
99
,
235
,
0.42
);
border-color
:
rgba
(
124
,
58
,
237
,
0.4
);
background
:
#f8fbff
;
transform
:
translateY
(
-4px
);
box-shadow
:
0
8px
16px
rgba
(
37
,
99
,
235
,
0.08
);
box-shadow
:
0
12px
24px
rgba
(
124
,
58
,
237
,
0.15
);
}
}
.document-icon
{
.document-icon
{
font-size
:
32px
;
width
:
42px
;
height
:
42px
;
flex
:
0
0
auto
;
display
:
inline-grid
;
place-items
:
center
;
border-radius
:
10px
;
background
:
#e0edff
;
color
:
#2563eb
;
font-size
:
12px
;
font-weight
:
800
;
}
.document-info
{
min-width
:
0
;
}
}
.document-info
h4
{
.document-info
h4
{
font-size
:
16px
;
margin
:
0
0
4px
;
font-weight
:
600
;
color
:
#12355f
;
margin
:
0
0
4px
0
;
font-size
:
15px
;
color
:
#E2E8F0
;
font-weight
:
700
;
line-height
:
1.35
;
}
}
.document-info
p
{
.document-info
p
{
font-size
:
12px
;
color
:
#94A3B8
;
margin
:
0
;
margin
:
0
;
color
:
#64748b
;
font-size
:
12px
;
line-height
:
1.5
;
}
}
h1
,
h2
,
h3
,
h4
,
h5
,
h6
,
h1
,
h2
,
h3
,
h4
,
h5
,
h6
,
.knowledge-title
,
.knowledge-title
,
.section-title
,
.section-title
,
...
@@ -175,6 +209,3 @@ h1, h2, h3, h4, h5, h6,
...
@@ -175,6 +209,3 @@ h1, h2, h3, h4, h5, h6,
font-family
:
var
(
--font-heading
);
font-family
:
var
(
--font-heading
);
font-weight
:
600
;
font-weight
:
600
;
}
}
/* gray-blue revamp: consolidated final theme layer */
apps/ui/src/styles/shell.css
View file @
0f949b0d
...
@@ -14,7 +14,7 @@
...
@@ -14,7 +14,7 @@
height
:
100vh
;
height
:
100vh
;
padding
:
var
(
--space-5
)
var
(
--space-4
);
padding
:
var
(
--space-5
)
var
(
--space-4
);
display
:
grid
;
display
:
grid
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
auto
minmax
(
0
,
1
fr
);
gap
:
18px
;
gap
:
18px
;
background
:
background
:
linear-gradient
(
180deg
,
rgba
(
167
,
139
,
250
,
0.15
),
rgba
(
124
,
58
,
237
,
0.1
)),
linear-gradient
(
180deg
,
rgba
(
167
,
139
,
250
,
0.15
),
rgba
(
124
,
58
,
237
,
0.1
)),
...
...
apps/ui/src/styles/theme-openclaw.css
View file @
0f949b0d
...
@@ -28,6 +28,10 @@
...
@@ -28,6 +28,10 @@
color
:
var
(
--revamp-text
);
color
:
var
(
--revamp-text
);
}
}
.shell.openclaw-theme.sidebar-collapsed
{
grid-template-columns
:
64px
minmax
(
0
,
1
fr
);
}
.shell.openclaw-theme
button
,
.shell.openclaw-theme
button
,
.shell.openclaw-theme
[
role
=
"button"
],
.shell.openclaw-theme
[
role
=
"button"
],
.shell.openclaw-theme
summary
,
.shell.openclaw-theme
summary
,
...
@@ -95,11 +99,64 @@
...
@@ -95,11 +99,64 @@
.shell.openclaw-theme
.conversation-sidebar-layout
{
.shell.openclaw-theme
.conversation-sidebar-layout
{
isolation
:
isolate
;
isolation
:
isolate
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
auto
minmax
(
0
,
1
fr
);
gap
:
12px
;
gap
:
12px
;
padding
:
16px
14px
;
padding
:
16px
14px
;
}
}
.shell.openclaw-theme
.sidebar-brand
{
position
:
relative
;
z-index
:
1
;
display
:
grid
;
grid-template-columns
:
32px
minmax
(
0
,
1
fr
)
28px
;
align-items
:
center
;
gap
:
10px
;
min-height
:
36px
;
}
.shell.openclaw-theme
.sidebar-brand-logo
{
width
:
32px
;
height
:
32px
;
display
:
block
;
object-fit
:
contain
;
}
.shell.openclaw-theme
.sidebar-brand-name
{
min-width
:
0
;
overflow
:
hidden
;
color
:
#12355f
;
font-size
:
15px
;
font-weight
:
800
;
line-height
:
1.3
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.shell.openclaw-theme
.sidebar-collapse-button
{
width
:
28px
;
height
:
28px
;
padding
:
0
;
display
:
inline-grid
;
place-items
:
center
;
border
:
1px
solid
rgba
(
96
,
165
,
250
,
0.28
);
border-radius
:
8px
;
background
:
rgba
(
255
,
255
,
255
,
0.74
);
color
:
#2563eb
;
box-shadow
:
none
;
transition
:
border-color
180ms
ease
,
background
180ms
ease
,
color
180ms
ease
;
}
.shell.openclaw-theme
.sidebar-collapse-button
:hover:not
(
:disabled
)
{
border-color
:
rgba
(
37
,
99
,
235
,
0.48
);
background
:
#ffffff
;
color
:
#1d4ed8
;
}
.shell.openclaw-theme
.sidebar-collapse-button
svg
{
width
:
18px
;
height
:
18px
;
}
.shell.openclaw-theme
.sidebar-top
{
.shell.openclaw-theme
.sidebar-top
{
position
:
relative
;
position
:
relative
;
z-index
:
1
;
z-index
:
1
;
...
@@ -162,9 +219,9 @@
...
@@ -162,9 +219,9 @@
.shell.openclaw-theme
.sidebar-new-session.conversation-new-session
{
.shell.openclaw-theme
.sidebar-new-session.conversation-new-session
{
width
:
100%
;
width
:
100%
;
min-height
:
4
4
px
;
min-height
:
4
2
px
;
justify-content
:
center
;
justify-content
:
center
;
padding
:
0
1
6
px
;
padding
:
0
1
2
px
;
border-radius
:
10px
;
border-radius
:
10px
;
border-color
:
rgba
(
29
,
78
,
216
,
0.18
);
border-color
:
rgba
(
29
,
78
,
216
,
0.18
);
background
:
linear-gradient
(
135deg
,
#2563eb
,
#0f6eea
);
background
:
linear-gradient
(
135deg
,
#2563eb
,
#0f6eea
);
...
@@ -208,6 +265,69 @@
...
@@ -208,6 +265,69 @@
padding-right
:
0
;
padding-right
:
0
;
}
}
.shell.openclaw-theme.sidebar-collapsed
.conversation-sidebar-layout
{
grid-template-rows
:
auto
1
fr
;
gap
:
14px
;
padding
:
16px
10px
;
}
.shell.openclaw-theme.sidebar-collapsed
.sidebar-brand
{
grid-template-columns
:
44px
;
justify-items
:
center
;
justify-content
:
center
;
gap
:
0
;
min-height
:
44px
;
}
.shell.openclaw-theme.sidebar-collapsed
.sidebar-brand-logo
,
.shell.openclaw-theme.sidebar-collapsed
.sidebar-brand-name
,
.shell.openclaw-theme.sidebar-collapsed
.nav-item-label
,
.shell.openclaw-theme.sidebar-collapsed
.conversation-sidebar-action
,
.shell.openclaw-theme.sidebar-collapsed
.sidebar-bottom
{
display
:
none
;
}
.shell.openclaw-theme.sidebar-collapsed
.sidebar-collapse-button
{
width
:
44px
;
height
:
44px
;
border-radius
:
10px
;
}
.shell.openclaw-theme.sidebar-collapsed
.sidebar-top
{
min-height
:
0
;
align-content
:
start
;
gap
:
12px
;
}
.shell.openclaw-theme.sidebar-collapsed
.nav-list
{
justify-items
:
center
;
align-content
:
start
;
gap
:
8px
;
}
.shell.openclaw-theme.sidebar-collapsed
.nav-item
{
width
:
44px
;
min-height
:
44px
;
justify-content
:
center
;
padding
:
0
;
}
.shell.openclaw-theme.sidebar-collapsed
.nav-item.active
{
box-shadow
:
inset
0
-3px
0
var
(
--revamp-blue
),
0
8px
18px
rgba
(
37
,
99
,
235
,
0.12
);
}
.shell.openclaw-theme.sidebar-collapsed
.nav-item-icon
{
display
:
inline-grid
;
place-items
:
center
;
}
.shell.openclaw-theme.sidebar-collapsed
.nav-item-icon
svg
{
width
:
20px
;
height
:
20px
;
}
.shell.openclaw-theme
.sidebar-experts-entry
,
.shell.openclaw-theme
.sidebar-experts-entry
,
.shell.openclaw-theme
.sidebar-session-section
,
.shell.openclaw-theme
.sidebar-session-section
,
.shell.openclaw-theme
.expert-category-item
,
.shell.openclaw-theme
.expert-category-item
,
...
...
apps/ui/test/sidebarKnowledgeSource.test.ts
0 → 100644
View file @
0f949b0d
import
test
from
"node:test"
import
assert
from
"node:assert/strict"
import
{
readFileSync
}
from
"node:fs"
const
appSource
=
readFileSync
(
new
URL
(
"../src/App.tsx"
,
import
.
meta
.
url
),
"utf8"
)
const
appSidebarSource
=
readFileSync
(
new
URL
(
"../src/features/shell/AppSidebar.tsx"
,
import
.
meta
.
url
),
"utf8"
)
const
sidebarSource
=
readFileSync
(
new
URL
(
"../src/features/shell/Sidebar.tsx"
,
import
.
meta
.
url
),
"utf8"
)
const
iconSource
=
readFileSync
(
new
URL
(
"../src/components/icons/AppIcons.tsx"
,
import
.
meta
.
url
),
"utf8"
)
const
knowledgeStylesSource
=
readFileSync
(
new
URL
(
"../src/styles/knowledge.css"
,
import
.
meta
.
url
),
"utf8"
)
const
shellStylesSource
=
readFileSync
(
new
URL
(
"../src/styles/shell.css"
,
import
.
meta
.
url
),
"utf8"
)
const
chatStylesSource
=
readFileSync
(
new
URL
(
"../src/styles/chat.css"
,
import
.
meta
.
url
),
"utf8"
)
const
themeStylesSource
=
readFileSync
(
new
URL
(
"../src/styles/theme-openclaw.css"
,
import
.
meta
.
url
),
"utf8"
)
function
cssBlock
(
source
:
string
,
selector
:
string
):
string
{
const
start
=
source
.
indexOf
(
`
${
selector
}
{`
)
assert
.
notEqual
(
start
,
-
1
,
`Missing CSS selector:
${
selector
}
`
)
const
end
=
source
.
indexOf
(
"
\n
}"
,
start
)
assert
.
notEqual
(
end
,
-
1
,
`Missing CSS block end:
${
selector
}
`
)
return
source
.
slice
(
start
,
end
+
2
)
}
test
(
"app shell wires a collapsible sidebar state into the sidebar components"
,
()
=>
{
assert
.
match
(
appSource
,
/const
\[
isSidebarCollapsed, setIsSidebarCollapsed
\]
= useState
\(
false
\)
/
)
assert
.
match
(
appSource
,
/sidebar-collapsed/
)
assert
.
match
(
appSource
,
/isCollapsed=
\{
isSidebarCollapsed
\}
/
)
assert
.
match
(
appSource
,
/onToggleCollapsed=
\{\(\)
=> setIsSidebarCollapsed
\(\(
current
\)
=> !current
\)\}
/
)
assert
.
match
(
appSidebarSource
,
/
\b
isCollapsed:
\s
*boolean/
)
assert
.
match
(
appSidebarSource
,
/
\b
onToggleCollapsed
\(\)
:
\s
*void/
)
assert
.
match
(
appSidebarSource
,
/<Sidebar
[\s\S]
*
?
isCollapsed=
\{
isCollapsed
\}[\s\S]
*
?
onToggleCollapsed=
\{
onToggleCollapsed
\}
/
)
})
test
(
"sidebar nav omits plugins and keeps the new conversation action visible when expanded"
,
()
=>
{
const
navItems
=
appSidebarSource
.
match
(
/
\{\s
*id:
\s
*"chat"
[\s\S]
*
?\]\.
map/
)?.[
0
]
??
""
assert
.
match
(
navItems
,
/id:
\s
*"chat"/
)
assert
.
match
(
navItems
,
/id:
\s
*"tasks"/
)
assert
.
match
(
navItems
,
/id:
\s
*"knowledge"/
)
assert
.
match
(
navItems
,
/id:
\s
*"settings"/
)
assert
.
doesNotMatch
(
navItems
,
/id:
\s
*"plugins"/
)
assert
.
match
(
appSidebarSource
,
/<div className="conversation-sidebar-action">
\s
*
\{
!showBindEntry
\?
sidebarNewSessionAction : null
\}\s
*<
\/
div>/
)
})
test
(
"sidebar renders the brand row and hides non-icon content in collapsed mode"
,
()
=>
{
assert
.
match
(
sidebarSource
,
/brandIcon/
)
assert
.
match
(
sidebarSource
,
/sidebar-brand/
)
assert
.
match
(
sidebarSource
,
/千匠问天/
)
assert
.
match
(
sidebarSource
,
/sidebar-collapse-button/
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\s
*
\{[\s\S]
*
?
grid-template-columns:
\s
*64px minmax
\(
0, 1fr
\)
;/m
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-brand-logo,
\s
*
\n\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-brand-name/
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-brand
\s
*
\{[\s\S]
*
?
grid-template-columns:
\s
*44px;/m
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-collapse-button
\s
*
\{[\s\S]
*
?
width:
\s
*44px;
[\s\S]
*
?
height:
\s
*44px;/m
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-top
\s
*
\{[\s\S]
*
?
align-content:
\s
*start;/m
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
nav-item-label/
)
assert
.
match
(
themeStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
sidebar-collapsed
\.
sidebar-bottom/
)
})
test
(
"sidebar keeps brand, navigation, and lower content in fixed grid rows"
,
()
=>
{
assert
.
match
(
cssBlock
(
shellStylesSource
,
".sidebar"
),
/grid-template-rows:
\s
*auto auto minmax
\(
0, 1fr
\)
;/
)
assert
.
match
(
cssBlock
(
chatStylesSource
,
".conversation-sidebar-layout"
),
/grid-template-rows:
\s
*auto auto minmax
\(
0, 1fr
\)
;/
)
assert
.
match
(
cssBlock
(
themeStylesSource
,
".shell.openclaw-theme .conversation-sidebar-layout"
),
/grid-template-rows:
\s
*auto auto minmax
\(
0, 1fr
\)
;/
)
assert
.
match
(
cssBlock
(
themeStylesSource
,
".shell.openclaw-theme .sidebar-bottom"
),
/grid-template-rows:
\s
*minmax
\(
0, 56fr
\)
minmax
\(
150px, 44fr
\)
;
[\s\S]
*
?
overflow:
\s
*hidden;/
)
})
test
(
"new conversation action matches the sidebar navigation control size"
,
()
=>
{
assert
.
match
(
cssBlock
(
themeStylesSource
,
".shell.openclaw-theme .nav-item"
),
/min-height:
\s
*42px;
[\s\S]
*
?
border-radius:
\s
*10px;/
)
assert
.
match
(
cssBlock
(
themeStylesSource
,
".shell.openclaw-theme .sidebar-new-session.conversation-new-session"
),
/width:
\s
*100%;
[\s\S]
*
?
min-height:
\s
*42px;
[\s\S]
*
?
justify-content:
\s
*center;
[\s\S]
*
?
border-radius:
\s
*10px;/
)
})
test
(
"knowledge navigation uses a book icon instead of the old document icon"
,
()
=>
{
const
knowledgeIconCase
=
iconSource
.
match
(
/case "knowledge":
[\s\S]
*
?
case "settings":/
)?.[
0
]
??
""
assert
.
match
(
knowledgeIconCase
,
/M5
\.
25 5
\.
7/
)
assert
.
match
(
knowledgeIconCase
,
/M12 6
\.
65/
)
assert
.
doesNotMatch
(
knowledgeIconCase
,
/M6 4
\.
5A2
\.
5 2
\.
5/
)
})
test
(
"knowledge page uses the OpenClaw blue-white theme instead of purple glass styling"
,
()
=>
{
assert
.
doesNotMatch
(
knowledgeStylesSource
,
/#7C3AED|#A78BFA|backdrop-filter|rgba
\(
255,
\s
*255,
\s
*255,
\s
*0
\.
0
[
58
]\)
/
)
assert
.
match
(
knowledgeStylesSource
,
/
\.
upload-card
\s
*
\{[\s\S]
*
?
background:
\s
*#ffffff;/m
)
assert
.
match
(
knowledgeStylesSource
,
/
\.
upload-card
\s
*
\{[\s\S]
*
?
border:
\s
*1px dashed rgba
\(
59, 130, 246, 0
\.
34
\)
;/m
)
assert
.
match
(
knowledgeStylesSource
,
/
\.
knowledge-title
\s
*
\{[\s\S]
*
?
color:
\s
*#12355f;/m
)
assert
.
match
(
knowledgeStylesSource
,
/
\.
upload-button
\s
*
\{[\s\S]
*
?
background:
\s
*linear-gradient
\(
135deg, #2563eb, #0f6eea
\)
;/m
)
})
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment