dsh-side-chat-plus 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -519,10 +519,15 @@ function buildApi(ctx, sideChats, getSettings) {
519
519
  const items = [];
520
520
  for (const record of sideChats.values()) {
521
521
  if (record.parentSessionId !== parentSessionId) continue;
522
- const runningSince = openTurnStart(record.handle.agent.session.events ?? []);
522
+ const agent = record.handle.agent;
523
+ const status = agent.status;
524
+ const statusRunning = status === "running";
525
+ const eventRunningSince = openTurnStart(agent.session.events ?? []);
526
+ const running = statusRunning || status === void 0 && eventRunningSince !== void 0;
527
+ const runningSince = running ? eventRunningSince ?? Date.now() : void 0;
523
528
  items.push({
524
529
  childId: record.childId,
525
- running: runningSince !== void 0,
530
+ running,
526
531
  ...runningSince !== void 0 ? { runningSince } : {}
527
532
  });
528
533
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-side-chat-plus",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "侧边聊天 Plus (Side chat Plus) — DSH web plugin: select text in a conversation and ask it in a per-session side chat (hidden ordinary session, own right-side panel, inherits model/effort/permissions, lookup toggle).",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,89 +1,89 @@
1
- .root {
2
- position: relative;
3
- display: flex;
4
- align-items: center;
5
- min-width: 0;
6
- }
7
-
8
- .rail {
9
- display: flex;
10
- gap: 8px;
11
- overflow-x: auto;
12
- scrollbar-width: none;
13
- padding: 2px;
14
- }
15
-
16
- .rail::-webkit-scrollbar {
17
- display: none;
18
- }
19
-
20
- .item {
21
- position: relative;
22
- flex: none;
23
- }
24
-
25
- .thumbnail {
26
- display: block;
27
- width: 56px;
28
- height: 56px;
29
- padding: 0;
30
- overflow: hidden;
31
- border: 1px solid var(--dsw-color-border, rgba(0, 0, 0, 0.1));
32
- border-radius: 8px;
33
- background: none;
34
- cursor: pointer;
35
- }
36
-
37
- .thumbnail img {
38
- width: 100%;
39
- height: 100%;
40
- object-fit: cover;
41
- }
42
-
43
- .remove {
44
- position: absolute;
45
- top: -6px;
46
- right: -6px;
47
- display: flex;
48
- align-items: center;
49
- justify-content: center;
50
- width: 18px;
51
- height: 18px;
52
- padding: 0;
53
- border: none;
54
- border-radius: 50%;
55
- background: var(--dsw-color-surface-raised, #fff);
56
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
57
- color: var(--dsw-color-text-secondary, #646a73);
58
- cursor: pointer;
59
- opacity: 0;
60
- }
61
-
62
- .item:hover .remove,
63
- .remove:focus-visible {
64
- opacity: 1;
65
- }
66
-
67
- .arrow {
68
- position: absolute;
69
- z-index: 1;
70
- display: flex;
71
- align-items: center;
72
- justify-content: center;
73
- width: 22px;
74
- height: 22px;
75
- padding: 0;
76
- border: 1px solid var(--dsw-color-border, rgba(0, 0, 0, 0.1));
77
- border-radius: 50%;
78
- background: var(--dsw-color-surface-raised, #fff);
79
- color: var(--dsw-color-text-secondary, #646a73);
80
- cursor: pointer;
81
- }
82
-
83
- .arrowLeft {
84
- left: -10px;
85
- }
86
-
87
- .arrowRight {
88
- right: -10px;
89
- }
1
+ .root {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ min-width: 0;
6
+ }
7
+
8
+ .rail {
9
+ display: flex;
10
+ gap: 8px;
11
+ overflow-x: auto;
12
+ scrollbar-width: none;
13
+ padding: 2px;
14
+ }
15
+
16
+ .rail::-webkit-scrollbar {
17
+ display: none;
18
+ }
19
+
20
+ .item {
21
+ position: relative;
22
+ flex: none;
23
+ }
24
+
25
+ .thumbnail {
26
+ display: block;
27
+ width: 56px;
28
+ height: 56px;
29
+ padding: 0;
30
+ overflow: hidden;
31
+ border: 1px solid var(--dsw-color-border, rgba(0, 0, 0, 0.1));
32
+ border-radius: 8px;
33
+ background: none;
34
+ cursor: pointer;
35
+ }
36
+
37
+ .thumbnail img {
38
+ width: 100%;
39
+ height: 100%;
40
+ object-fit: cover;
41
+ }
42
+
43
+ .remove {
44
+ position: absolute;
45
+ top: -6px;
46
+ right: -6px;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ width: 18px;
51
+ height: 18px;
52
+ padding: 0;
53
+ border: none;
54
+ border-radius: 50%;
55
+ background: var(--dsw-color-surface-raised, #fff);
56
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
57
+ color: var(--dsw-color-text-secondary, #646a73);
58
+ cursor: pointer;
59
+ opacity: 0;
60
+ }
61
+
62
+ .item:hover .remove,
63
+ .remove:focus-visible {
64
+ opacity: 1;
65
+ }
66
+
67
+ .arrow {
68
+ position: absolute;
69
+ z-index: 1;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ width: 22px;
74
+ height: 22px;
75
+ padding: 0;
76
+ border: 1px solid var(--dsw-color-border, rgba(0, 0, 0, 0.1));
77
+ border-radius: 50%;
78
+ background: var(--dsw-color-surface-raised, #fff);
79
+ color: var(--dsw-color-text-secondary, #646a73);
80
+ cursor: pointer;
81
+ }
82
+
83
+ .arrowLeft {
84
+ left: -10px;
85
+ }
86
+
87
+ .arrowRight {
88
+ right: -10px;
89
+ }
@@ -1,173 +1,173 @@
1
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
2
- import {
3
- IconChevronLeftOutline14,
4
- IconChevronRightOutline14,
5
- IconCloseFill14,
6
- } from '@deepseek-ai/dsh-client-ui-primitives'
7
- import css from './AttachmentRail.module.css'
8
-
9
- /** One rail thumbnail; strings arrive resolved (zero-cordis atom). */
10
- export interface AttachmentRailItem {
11
- /** Stable identity for the React key. */
12
- id: string
13
- /** Object or data URL rendered as the thumbnail. */
14
- previewUrl: string
15
- /** Image alt text (display name with the owner's fallback applied). */
16
- alt: string
17
- /** Accessible label of the item's remove control. */
18
- removeLabel: string
19
- }
20
-
21
- /** Rail-level strings the owner resolves from its own locale namespace. */
22
- export interface AttachmentRailLabels {
23
- /** Accessible name of the rail group. */
24
- group: string
25
- /** Thumbnail tooltip inviting the original-image preview. */
26
- open: string
27
- /** Accessible label of the left paging arrow. */
28
- scrollLeft: string
29
- /** Accessible label of the right paging arrow. */
30
- scrollRight: string
31
- }
32
-
33
- /** Approximate pixels per wheel step for `deltaMode` LINE deltas (Firefox notch wheels report lines). */
34
- const WHEEL_LINE_PX = 16
35
-
36
- /** Smooth paging unless the user asked for reduced motion. */
37
- function pageBehavior(): ScrollBehavior {
38
- return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth'
39
- }
40
-
41
- /**
42
- * Horizontal thumbnail rail over the caller's draft attachments.
43
- *
44
- * The rail scrolls with its scrollbar hidden; overflow is announced by edge
45
- * arrows recomputed from scroll geometry on scroll, item-count changes, and
46
- * rail size changes (a ResizeObserver on the rail element, so sidebar or panel
47
- * resizes count, not only window resizes). A vertical wheel pans the rail
48
- * horizontally and is consumed exclusively, a newly added item is revealed at
49
- * the rail's end, and each thumbnail opens on a single click while its remove
50
- * control sits inside the card. The owner decides mounting.
51
- */
52
- export function AttachmentRail<T extends AttachmentRailItem>({ items, labels, onOpen, onRemove }: {
53
- items: readonly T[]
54
- labels: AttachmentRailLabels
55
- onOpen: (item: T) => void
56
- onRemove: (item: T) => void
57
- }) {
58
- const railRef = useRef<HTMLDivElement | null>(null)
59
- // null marks the first layout pass: a rail that MOUNTS over an existing
60
- // draft is initial display, not growth, and must not jump to the end.
61
- const countRef = useRef<number | null>(null)
62
- const [edges, setEdges] = useState({ left: false, right: false })
63
- const updateEdges = useCallback(() => {
64
- const el = railRef.current
65
- if (el === null) return
66
- // 1px slack: engines report fractional scroll positions at the edges.
67
- const left = el.scrollLeft > 1
68
- const right = el.scrollLeft < el.scrollWidth - el.clientWidth - 1
69
- setEdges(prev => prev.left === left && prev.right === right ? prev : { left, right })
70
- }, [])
71
- useLayoutEffect(() => {
72
- const grew = countRef.current !== null && items.length > countRef.current
73
- countRef.current = items.length
74
- const el = railRef.current
75
- if (el === null) return
76
- // A newly added attachment lands at the rail's end: reveal it.
77
- if (grew) el.scrollLeft = el.scrollWidth - el.clientWidth
78
- updateEdges()
79
- }, [items.length, updateEdges])
80
- useEffect(() => {
81
- const el = railRef.current
82
- if (el === null) return
83
- // The rail's width follows the composer, which resizes with sidebars and
84
- // panels, not only the window — observe the element itself.
85
- let disconnect = (): void => {}
86
- if (typeof ResizeObserver !== 'undefined') {
87
- const observer = new ResizeObserver(updateEdges)
88
- observer.observe(el)
89
- disconnect = () => { observer.disconnect() }
90
- }
91
- // The rail scrolls horizontally ONLY: any wheel tick with a vertical
92
- // component is consumed — without preventDefault it would also scroll the
93
- // conversation behind the composer, and React's root wheel listener is
94
- // passive, so the exclusion needs this manually attached non-passive
95
- // listener.
96
- const onWheel = (event: globalThis.WheelEvent): void => {
97
- if (event.deltaY === 0) return
98
- const scale = event.deltaMode === WheelEvent.DOM_DELTA_LINE
99
- ? WHEEL_LINE_PX
100
- : event.deltaMode === WheelEvent.DOM_DELTA_PAGE ? el.clientWidth : 1
101
- event.preventDefault()
102
- el.scrollBy({
103
- left: event.deltaX !== 0
104
- ? event.deltaX * scale
105
- : Math.sign(event.deltaY) * Math.min(Math.abs(event.deltaY) * scale, 60),
106
- behavior: 'auto',
107
- })
108
- }
109
- el.addEventListener('wheel', onWheel, { passive: false })
110
- return () => {
111
- disconnect()
112
- el.removeEventListener('wheel', onWheel)
113
- }
114
- }, [updateEdges])
115
- const page = (direction: -1 | 1): void => {
116
- const el = railRef.current
117
- if (el === null) return
118
- // One viewport minus a card keeps the last visible thumbnail as context.
119
- el.scrollBy({ left: direction * Math.max(el.clientWidth - 64, 200), behavior: pageBehavior() })
120
- }
121
- return (
122
- <div className={css.root}>
123
- {edges.left && (
124
- <button
125
- type="button"
126
- className={`${css.arrow} ${css.arrowLeft}`}
127
- aria-label={labels.scrollLeft}
128
- onClick={() => { page(-1) }}
129
- >
130
- <IconChevronLeftOutline14 />
131
- </button>
132
- )}
133
- <div
134
- ref={railRef}
135
- className={css.rail}
136
- role="group"
137
- aria-label={labels.group}
138
- onScroll={updateEdges}
139
- >
140
- {items.map(item => (
141
- <div key={item.id} className={css.item}>
142
- <button
143
- type="button"
144
- className={css.thumbnail}
145
- title={labels.open}
146
- onClick={() => { onOpen(item) }}
147
- >
148
- <img src={item.previewUrl} alt={item.alt} />
149
- </button>
150
- <button
151
- type="button"
152
- className={css.remove}
153
- aria-label={item.removeLabel}
154
- onClick={() => { onRemove(item) }}
155
- >
156
- <IconCloseFill14 size={12} />
157
- </button>
158
- </div>
159
- ))}
160
- </div>
161
- {edges.right && (
162
- <button
163
- type="button"
164
- className={`${css.arrow} ${css.arrowRight}`}
165
- aria-label={labels.scrollRight}
166
- onClick={() => { page(1) }}
167
- >
168
- <IconChevronRightOutline14 />
169
- </button>
170
- )}
171
- </div>
172
- )
173
- }
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
2
+ import {
3
+ IconChevronLeftOutline14,
4
+ IconChevronRightOutline14,
5
+ IconCloseFill14,
6
+ } from '@deepseek-ai/dsh-client-ui-primitives'
7
+ import css from './AttachmentRail.module.css'
8
+
9
+ /** One rail thumbnail; strings arrive resolved (zero-cordis atom). */
10
+ export interface AttachmentRailItem {
11
+ /** Stable identity for the React key. */
12
+ id: string
13
+ /** Object or data URL rendered as the thumbnail. */
14
+ previewUrl: string
15
+ /** Image alt text (display name with the owner's fallback applied). */
16
+ alt: string
17
+ /** Accessible label of the item's remove control. */
18
+ removeLabel: string
19
+ }
20
+
21
+ /** Rail-level strings the owner resolves from its own locale namespace. */
22
+ export interface AttachmentRailLabels {
23
+ /** Accessible name of the rail group. */
24
+ group: string
25
+ /** Thumbnail tooltip inviting the original-image preview. */
26
+ open: string
27
+ /** Accessible label of the left paging arrow. */
28
+ scrollLeft: string
29
+ /** Accessible label of the right paging arrow. */
30
+ scrollRight: string
31
+ }
32
+
33
+ /** Approximate pixels per wheel step for `deltaMode` LINE deltas (Firefox notch wheels report lines). */
34
+ const WHEEL_LINE_PX = 16
35
+
36
+ /** Smooth paging unless the user asked for reduced motion. */
37
+ function pageBehavior(): ScrollBehavior {
38
+ return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth'
39
+ }
40
+
41
+ /**
42
+ * Horizontal thumbnail rail over the caller's draft attachments.
43
+ *
44
+ * The rail scrolls with its scrollbar hidden; overflow is announced by edge
45
+ * arrows recomputed from scroll geometry on scroll, item-count changes, and
46
+ * rail size changes (a ResizeObserver on the rail element, so sidebar or panel
47
+ * resizes count, not only window resizes). A vertical wheel pans the rail
48
+ * horizontally and is consumed exclusively, a newly added item is revealed at
49
+ * the rail's end, and each thumbnail opens on a single click while its remove
50
+ * control sits inside the card. The owner decides mounting.
51
+ */
52
+ export function AttachmentRail<T extends AttachmentRailItem>({ items, labels, onOpen, onRemove }: {
53
+ items: readonly T[]
54
+ labels: AttachmentRailLabels
55
+ onOpen: (item: T) => void
56
+ onRemove: (item: T) => void
57
+ }) {
58
+ const railRef = useRef<HTMLDivElement | null>(null)
59
+ // null marks the first layout pass: a rail that MOUNTS over an existing
60
+ // draft is initial display, not growth, and must not jump to the end.
61
+ const countRef = useRef<number | null>(null)
62
+ const [edges, setEdges] = useState({ left: false, right: false })
63
+ const updateEdges = useCallback(() => {
64
+ const el = railRef.current
65
+ if (el === null) return
66
+ // 1px slack: engines report fractional scroll positions at the edges.
67
+ const left = el.scrollLeft > 1
68
+ const right = el.scrollLeft < el.scrollWidth - el.clientWidth - 1
69
+ setEdges(prev => prev.left === left && prev.right === right ? prev : { left, right })
70
+ }, [])
71
+ useLayoutEffect(() => {
72
+ const grew = countRef.current !== null && items.length > countRef.current
73
+ countRef.current = items.length
74
+ const el = railRef.current
75
+ if (el === null) return
76
+ // A newly added attachment lands at the rail's end: reveal it.
77
+ if (grew) el.scrollLeft = el.scrollWidth - el.clientWidth
78
+ updateEdges()
79
+ }, [items.length, updateEdges])
80
+ useEffect(() => {
81
+ const el = railRef.current
82
+ if (el === null) return
83
+ // The rail's width follows the composer, which resizes with sidebars and
84
+ // panels, not only the window — observe the element itself.
85
+ let disconnect = (): void => {}
86
+ if (typeof ResizeObserver !== 'undefined') {
87
+ const observer = new ResizeObserver(updateEdges)
88
+ observer.observe(el)
89
+ disconnect = () => { observer.disconnect() }
90
+ }
91
+ // The rail scrolls horizontally ONLY: any wheel tick with a vertical
92
+ // component is consumed — without preventDefault it would also scroll the
93
+ // conversation behind the composer, and React's root wheel listener is
94
+ // passive, so the exclusion needs this manually attached non-passive
95
+ // listener.
96
+ const onWheel = (event: globalThis.WheelEvent): void => {
97
+ if (event.deltaY === 0) return
98
+ const scale = event.deltaMode === WheelEvent.DOM_DELTA_LINE
99
+ ? WHEEL_LINE_PX
100
+ : event.deltaMode === WheelEvent.DOM_DELTA_PAGE ? el.clientWidth : 1
101
+ event.preventDefault()
102
+ el.scrollBy({
103
+ left: event.deltaX !== 0
104
+ ? event.deltaX * scale
105
+ : Math.sign(event.deltaY) * Math.min(Math.abs(event.deltaY) * scale, 60),
106
+ behavior: 'auto',
107
+ })
108
+ }
109
+ el.addEventListener('wheel', onWheel, { passive: false })
110
+ return () => {
111
+ disconnect()
112
+ el.removeEventListener('wheel', onWheel)
113
+ }
114
+ }, [updateEdges])
115
+ const page = (direction: -1 | 1): void => {
116
+ const el = railRef.current
117
+ if (el === null) return
118
+ // One viewport minus a card keeps the last visible thumbnail as context.
119
+ el.scrollBy({ left: direction * Math.max(el.clientWidth - 64, 200), behavior: pageBehavior() })
120
+ }
121
+ return (
122
+ <div className={css.root}>
123
+ {edges.left && (
124
+ <button
125
+ type="button"
126
+ className={`${css.arrow} ${css.arrowLeft}`}
127
+ aria-label={labels.scrollLeft}
128
+ onClick={() => { page(-1) }}
129
+ >
130
+ <IconChevronLeftOutline14 />
131
+ </button>
132
+ )}
133
+ <div
134
+ ref={railRef}
135
+ className={css.rail}
136
+ role="group"
137
+ aria-label={labels.group}
138
+ onScroll={updateEdges}
139
+ >
140
+ {items.map(item => (
141
+ <div key={item.id} className={css.item}>
142
+ <button
143
+ type="button"
144
+ className={css.thumbnail}
145
+ title={labels.open}
146
+ onClick={() => { onOpen(item) }}
147
+ >
148
+ <img src={item.previewUrl} alt={item.alt} />
149
+ </button>
150
+ <button
151
+ type="button"
152
+ className={css.remove}
153
+ aria-label={item.removeLabel}
154
+ onClick={() => { onRemove(item) }}
155
+ >
156
+ <IconCloseFill14 size={12} />
157
+ </button>
158
+ </div>
159
+ ))}
160
+ </div>
161
+ {edges.right && (
162
+ <button
163
+ type="button"
164
+ className={`${css.arrow} ${css.arrowRight}`}
165
+ aria-label={labels.scrollRight}
166
+ onClick={() => { page(1) }}
167
+ >
168
+ <IconChevronRightOutline14 />
169
+ </button>
170
+ )}
171
+ </div>
172
+ )
173
+ }
@@ -1,38 +1,38 @@
1
- .mask {
2
- position: fixed;
3
- inset: 0;
4
- z-index: 999;
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- /* Decoration only: drag targeting must stay on the page below. */
9
- pointer-events: none;
10
- background: rgba(255, 255, 255, 0.72);
11
- border: 2px dashed var(--dsw-color-border-strong, #4d6bfe);
12
- border-radius: 12px;
13
- }
14
-
15
- .wrap {
16
- display: flex;
17
- flex-direction: column;
18
- align-items: center;
19
- gap: 12px;
20
- text-align: center;
21
- }
22
-
23
- .illustration {
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- }
28
-
29
- .title {
30
- font-size: 16px;
31
- font-weight: 600;
32
- color: var(--dsw-color-text, #1f2329);
33
- }
34
-
35
- .desc {
36
- font-size: 13px;
37
- color: var(--dsw-color-text-secondary, #646a73);
38
- }
1
+ .mask {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 999;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ /* Decoration only: drag targeting must stay on the page below. */
9
+ pointer-events: none;
10
+ background: rgba(255, 255, 255, 0.72);
11
+ border: 2px dashed var(--dsw-color-border-strong, #4d6bfe);
12
+ border-radius: 12px;
13
+ }
14
+
15
+ .wrap {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ gap: 12px;
20
+ text-align: center;
21
+ }
22
+
23
+ .illustration {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ }
28
+
29
+ .title {
30
+ font-size: 16px;
31
+ font-weight: 600;
32
+ color: var(--dsw-color-text, #1f2329);
33
+ }
34
+
35
+ .desc {
36
+ font-size: 13px;
37
+ color: var(--dsw-color-text-secondary, #646a73);
38
+ }