@qnote/q-ai-note 1.0.2 → 1.0.4

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.
Files changed (40) hide show
  1. package/dist/server/aiClient.d.ts.map +1 -1
  2. package/dist/server/aiClient.js +7 -0
  3. package/dist/server/aiClient.js.map +1 -1
  4. package/dist/server/api/chat.d.ts.map +1 -1
  5. package/dist/server/api/chat.js +3 -0
  6. package/dist/server/api/chat.js.map +1 -1
  7. package/dist/server/api/nodeEntities.d.ts +3 -0
  8. package/dist/server/api/nodeEntities.d.ts.map +1 -0
  9. package/dist/server/api/nodeEntities.js +116 -0
  10. package/dist/server/api/nodeEntities.js.map +1 -0
  11. package/dist/server/api/settings.d.ts.map +1 -1
  12. package/dist/server/api/settings.js +16 -1
  13. package/dist/server/api/settings.js.map +1 -1
  14. package/dist/server/config.d.ts +3 -2
  15. package/dist/server/config.d.ts.map +1 -1
  16. package/dist/server/config.js +6 -1
  17. package/dist/server/config.js.map +1 -1
  18. package/dist/server/index.d.ts.map +1 -1
  19. package/dist/server/index.js +2 -0
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/nodeEntitiesStore.d.ts +78 -0
  22. package/dist/server/nodeEntitiesStore.d.ts.map +1 -0
  23. package/dist/server/nodeEntitiesStore.js +196 -0
  24. package/dist/server/nodeEntitiesStore.js.map +1 -0
  25. package/dist/server/react/agent.d.ts +3 -0
  26. package/dist/server/react/agent.d.ts.map +1 -1
  27. package/dist/server/react/agent.js +132 -2
  28. package/dist/server/react/agent.js.map +1 -1
  29. package/dist/server/react/prompts.d.ts +1 -0
  30. package/dist/server/react/prompts.d.ts.map +1 -1
  31. package/dist/server/react/prompts.js +25 -4
  32. package/dist/server/react/prompts.js.map +1 -1
  33. package/dist/server/react/tools.d.ts.map +1 -1
  34. package/dist/server/react/tools.js +105 -0
  35. package/dist/server/react/tools.js.map +1 -1
  36. package/dist/web/app.js +872 -57
  37. package/dist/web/index.html +78 -1
  38. package/dist/web/styles.css +783 -33
  39. package/dist/web/vueRenderers.js +228 -13
  40. package/package.json +1 -1
@@ -83,18 +83,46 @@ export function mountDiaryTimeline(targetId, options) {
83
83
  });
84
84
  }
85
85
 
86
- function renderTreeNode(node, byParent, expandedIdSet) {
86
+ function renderEntityBadges(summary) {
87
+ const issue = Number(summary?.issue || 0);
88
+ const knowledge = Number(summary?.knowledge || 0);
89
+ const capability = Number(summary?.capability || 0);
90
+ const issueOpen = Number(summary?.issue_open || 0);
91
+ const issueClosed = Number(summary?.issue_closed || 0);
92
+ const isBlindSpot = issue + knowledge + capability === 0;
93
+ const issueStateClass = issue === 0
94
+ ? 'issue-none'
95
+ : issueOpen > 0
96
+ ? 'issue-open'
97
+ : issueClosed > 0
98
+ ? 'issue-closed'
99
+ : 'issue-none';
100
+ return `
101
+ <span
102
+ class="node-entity-mini-badges ${isBlindSpot ? 'blind-spot' : ''} ${issueStateClass}"
103
+ title="节点摘要:${issue}/${knowledge}/${capability}(Issue/Knowledge/Capability)"
104
+ >
105
+ <span class="node-entity-mini-badge ${issue > 0 || knowledge > 0 || capability > 0 ? 'active' : ''}">${issue}/${knowledge}/${capability}</span>
106
+ </span>
107
+ `;
108
+ }
109
+
110
+ function renderTreeNode(node, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee = false) {
87
111
  const children = byParent.get(node.id) || [];
88
112
  const hasChildren = children.length > 0;
89
113
  const isExpanded = expandedIdSet.has(node.id);
90
114
  const isShort = String(node.name || '').trim().length <= 10;
115
+ const nodeSummary = entitySummaryByNodeId?.[node.id] || { issue: 0, knowledge: 0, capability: 0 };
91
116
 
92
117
  if (!hasChildren) {
93
118
  return `
94
- <div class="tree-leaf-node" data-id="${esc(node.id)}" tabindex="0">
119
+ <div class="tree-leaf-node" data-id="${esc(node.id)}" data-select-id="${esc(node.id)}" tabindex="0">
95
120
  <span class="node-status ${esc(node.status)}"></span>
96
- <span class="node-name ${isShort ? 'short-name' : ''}">${esc(node.name)}</span>
121
+ <span class="node-name ${isShort ? 'short-name' : ''}" data-select-id="${esc(node.id)}">${esc(node.name)}</span>
122
+ ${renderEntityBadges(nodeSummary)}
97
123
  <div class="node-actions">
124
+ <button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
125
+ <button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
98
126
  <button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
99
127
  <button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
100
128
  </div>
@@ -106,15 +134,24 @@ function renderTreeNode(node, byParent, expandedIdSet) {
106
134
  const leafChildren = children.filter((child) => ((byParent.get(child.id) || []).length === 0));
107
135
 
108
136
  return `
109
- <div class="tree-parent-card">
110
- <div class="tree-parent-header" data-action="toggle" data-id="${esc(node.id)}" tabindex="0">
111
- <span class="node-expand ${isExpanded ? 'expanded' : ''}">
137
+ <div class="tree-parent-card ${isExpanded ? 'expanded' : 'collapsed'}">
138
+ <div class="tree-parent-header" data-select-id="${esc(node.id)}" tabindex="0" aria-expanded="${isExpanded ? 'true' : 'false'}">
139
+ <button
140
+ class="node-expand-btn ${isExpanded ? 'expanded' : ''}"
141
+ data-action="toggle"
142
+ data-id="${esc(node.id)}"
143
+ aria-label="${isExpanded ? '折叠节点' : '展开节点'}"
144
+ aria-expanded="${isExpanded ? 'true' : 'false'}"
145
+ type="button"
146
+ >
112
147
  <svg viewBox="0 0 24 24" fill="currentColor"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>
113
- </span>
148
+ </button>
114
149
  <span class="node-status ${esc(node.status)}"></span>
115
150
  <span class="node-name">${esc(node.name)}</span>
116
- ${node.assignee ? `<span class="node-meta">@${esc(node.assignee)}</span>` : ''}
151
+ ${renderEntityBadges(nodeSummary)}
152
+ ${showAssignee && node.assignee ? `<span class="node-meta">@${esc(node.assignee)}</span>` : ''}
117
153
  <div class="node-actions">
154
+ <button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
118
155
  <button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
119
156
  <button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
120
157
  <button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
@@ -122,17 +159,21 @@ function renderTreeNode(node, byParent, expandedIdSet) {
122
159
  </div>
123
160
  ${isExpanded ? `
124
161
  <div class="tree-parent-children">
125
- ${branchChildren.map((child) => renderTreeNode(child, byParent, expandedIdSet)).join('')}
162
+ ${branchChildren.map((child) => renderTreeNode(child, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee)).join('')}
126
163
  ${leafChildren.length ? `
127
164
  <div class="tree-leaf-container">
128
165
  <div class="tree-leaf-grid">
129
166
  ${leafChildren.map((child) => {
130
167
  const shortName = String(child.name || '').trim().length <= 10;
168
+ const childSummary = entitySummaryByNodeId?.[child.id] || { issue: 0, knowledge: 0, capability: 0 };
131
169
  return `
132
- <div class="tree-leaf-node" data-id="${esc(child.id)}" tabindex="0">
170
+ <div class="tree-leaf-node" data-id="${esc(child.id)}" data-select-id="${esc(child.id)}" tabindex="0">
133
171
  <span class="node-status ${esc(child.status)}"></span>
134
- <span class="node-name ${shortName ? 'short-name' : ''}">${esc(child.name)}</span>
172
+ <span class="node-name ${shortName ? 'short-name' : ''}" data-select-id="${esc(child.id)}">${esc(child.name)}</span>
173
+ ${renderEntityBadges(childSummary)}
135
174
  <div class="node-actions">
175
+ <button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(child.id)}" title="快捷提问">💬</button>
176
+ <button class="node-action-btn add-child" data-action="add-child" data-id="${esc(child.id)}" title="添加子任务">+</button>
136
177
  <button class="node-action-btn" data-action="edit" data-id="${esc(child.id)}" title="编辑">✎</button>
137
178
  <button class="node-action-btn delete" data-action="delete" data-id="${esc(child.id)}" title="删除">✕</button>
138
179
  </div>
@@ -148,6 +189,151 @@ function renderTreeNode(node, byParent, expandedIdSet) {
148
189
  `;
149
190
  }
150
191
 
192
+ function applyAdaptiveLeafGridLayout(container) {
193
+ const MIN_WIDTH = 180;
194
+ const MAX_WIDTH = 320;
195
+ const GAP = 8;
196
+ const grids = container.querySelectorAll('.tree-leaf-grid, .tree-root-leaf-grid');
197
+ grids.forEach((grid) => {
198
+ const cards = grid.querySelectorAll(':scope > .tree-leaf-node');
199
+ const count = cards.length;
200
+ if (!count) return;
201
+ const availableWidth = Math.max(0, grid.clientWidth);
202
+ if (!availableWidth) return;
203
+ const maxColsByWidth = Math.max(1, Math.floor((availableWidth + GAP) / (MIN_WIDTH + GAP)));
204
+ const cols = Math.max(1, Math.min(count, maxColsByWidth));
205
+ const rawWidth = Math.floor((availableWidth - GAP * (cols - 1)) / cols);
206
+ const cardWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, rawWidth));
207
+ grid.style.gridTemplateColumns = `repeat(${cols}, minmax(${cardWidth}px, 1fr))`;
208
+ });
209
+ }
210
+
211
+ function applyAdaptiveRootMixedGridLayout(container) {
212
+ const MIN_COLUMN_WIDTH = 240;
213
+ const GAP = 10;
214
+ const grids = container.querySelectorAll('.tree-root-mixed-grid');
215
+ grids.forEach((grid) => {
216
+ const cards = grid.querySelectorAll(':scope > .root-grid-item');
217
+ const count = cards.length;
218
+ if (!count) return;
219
+ const availableWidth = Math.max(0, grid.clientWidth);
220
+ if (!availableWidth) return;
221
+ const cols = Math.max(1, Math.floor((availableWidth + GAP) / (MIN_COLUMN_WIDTH + GAP)));
222
+ grid.style.gridTemplateColumns = `repeat(${cols}, minmax(0, 1fr))`;
223
+ cards.forEach((card) => {
224
+ const type = card.getAttribute('data-root-item-type');
225
+ if (type === 'branch') {
226
+ const span = Math.min(2, cols);
227
+ card.style.gridColumn = `span ${span}`;
228
+ } else {
229
+ card.style.gridColumn = 'span 1';
230
+ }
231
+ });
232
+ });
233
+ }
234
+
235
+ function renderDenseLaneNode(node, byParent, expandedIdSet, entitySummaryByNodeId, depth = 0, showAssignee = false) {
236
+ const children = byParent.get(node.id) || [];
237
+ const hasChildren = children.length > 0;
238
+ const isExpanded = expandedIdSet.has(node.id);
239
+ const nodeSummary = entitySummaryByNodeId?.[node.id] || { issue: 0, knowledge: 0, capability: 0 };
240
+ const useStackSummary = depth >= 3;
241
+ const childrenHtml = hasChildren && isExpanded
242
+ ? `<div class="lane-tree-children">${children.map((child) => renderDenseLaneNode(child, byParent, expandedIdSet, entitySummaryByNodeId, depth + 1, showAssignee)).join('')}</div>`
243
+ : '';
244
+ return `
245
+ <div class="lane-tree-node" data-depth="${depth}">
246
+ <div class="lane-tree-node-row" data-select-id="${esc(node.id)}" data-node-id="${esc(node.id)}" tabindex="0">
247
+ ${hasChildren ? `
248
+ <button
249
+ class="node-expand-btn dense-expand-btn ${isExpanded ? 'expanded' : ''}"
250
+ data-action="toggle"
251
+ data-id="${esc(node.id)}"
252
+ aria-label="${isExpanded ? '折叠节点' : '展开节点'}"
253
+ aria-expanded="${isExpanded ? 'true' : 'false'}"
254
+ type="button"
255
+ >
256
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>
257
+ </button>
258
+ ` : ''}
259
+ <span class="node-status ${esc(node.status)}"></span>
260
+ <div class="lane-tree-node-main ${useStackSummary ? 'stack-summary' : ''}">
261
+ <div class="lane-tree-title-line">
262
+ <span class="dense-node-name">${esc(node.name)}</span>
263
+ ${!useStackSummary ? renderEntityBadges(nodeSummary) : ''}
264
+ ${!useStackSummary && showAssignee && node.assignee ? `<span class="node-meta">@${esc(node.assignee)}</span>` : ''}
265
+ </div>
266
+ ${useStackSummary ? `
267
+ <div class="lane-tree-summary-line">
268
+ ${renderEntityBadges(nodeSummary)}
269
+ ${showAssignee && node.assignee ? `<span class="node-meta">@${esc(node.assignee)}</span>` : ''}
270
+ </div>
271
+ ` : ''}
272
+ </div>
273
+ <div class="node-actions">
274
+ <button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
275
+ <button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
276
+ <button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
277
+ <button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
278
+ </div>
279
+ </div>
280
+ ${childrenHtml}
281
+ </div>
282
+ `;
283
+ }
284
+
285
+ function collectDenseLaneMetrics(root, byParent, depth = 0) {
286
+ const stack = [{ node: root, depth }];
287
+ let maxDepth = depth;
288
+ let maxNameLength = String(root?.name || '').trim().length;
289
+ let maxAssigneeLength = String(root?.assignee || '').trim().length;
290
+ let totalNodes = 0;
291
+ while (stack.length) {
292
+ const current = stack.pop();
293
+ if (!current?.node) continue;
294
+ totalNodes += 1;
295
+ maxDepth = Math.max(maxDepth, current.depth);
296
+ maxNameLength = Math.max(maxNameLength, String(current.node.name || '').trim().length);
297
+ maxAssigneeLength = Math.max(maxAssigneeLength, String(current.node.assignee || '').trim().length);
298
+ const children = byParent.get(current.node.id) || [];
299
+ for (const child of children) {
300
+ stack.push({ node: child, depth: current.depth + 1 });
301
+ }
302
+ }
303
+ return { maxDepth, maxNameLength, maxAssigneeLength, totalNodes };
304
+ }
305
+
306
+ function getDenseLaneWidthPx(root, byParent, showAssignee = false) {
307
+ const MIN_WIDTH = 190;
308
+ const MAX_WIDTH = 420;
309
+ const { maxDepth, maxNameLength, maxAssigneeLength, totalNodes } = collectDenseLaneMetrics(root, byParent, 0);
310
+ const base = 150;
311
+ const nameFactor = Math.min(maxNameLength, 28) * 6;
312
+ const depthFactor = Math.min(maxDepth, 8) * 8;
313
+ const assigneeFactor = showAssignee ? Math.min(maxAssigneeLength, 18) * 5 : 0;
314
+ const densityFactor = Math.min(totalNodes, 20) * 2;
315
+ const preferred = base + nameFactor + depthFactor + assigneeFactor + densityFactor;
316
+ return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, preferred));
317
+ }
318
+
319
+ function renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee = false) {
320
+ return `
321
+ <div class="dense-tree dense-horizontal dense-lane-board">
322
+ ${roots.map((root) => {
323
+ const laneWidth = getDenseLaneWidthPx(root, byParent, showAssignee);
324
+ const laneNameMax = Math.max(120, laneWidth - 86);
325
+ return `
326
+ <section class="dense-lane" style="--lane-width:${laneWidth}px;--lane-name-max:${laneNameMax}px;">
327
+ <div class="dense-lane-body">
328
+ ${renderDenseLaneNode(root, byParent, expandedIdSet, entitySummaryByNodeId, 0, showAssignee)}
329
+ </div>
330
+ </section>
331
+ `;
332
+ }).join('')}
333
+ </div>
334
+ `;
335
+ }
336
+
151
337
  export function mountWorkTree(targetId, options) {
152
338
  const container = byId(targetId);
153
339
  if (!container) return;
@@ -158,6 +344,11 @@ export function mountWorkTree(targetId, options) {
158
344
  onAddChild,
159
345
  onEdit,
160
346
  onDelete,
347
+ onQuickChat,
348
+ onSelect,
349
+ entitySummaryByNodeId = {},
350
+ renderMode = 'card',
351
+ showAssignee = false,
161
352
  } = options || {};
162
353
 
163
354
  const byParent = new Map();
@@ -173,8 +364,19 @@ export function mountWorkTree(targetId, options) {
173
364
  container.innerHTML = '<div class="empty-state"><p>当前筛选条件下没有任务</p></div>';
174
365
  return;
175
366
  }
176
-
177
- container.innerHTML = roots.map((root) => renderTreeNode(root, byParent, expandedIdSet)).join('');
367
+ if (renderMode === 'dense') {
368
+ container.innerHTML = renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee);
369
+ } else {
370
+ const htmlParts = roots.map((root) => {
371
+ const hasChildren = ((byParent.get(root.id) || []).length > 0);
372
+ return `
373
+ <div class="root-grid-item ${hasChildren ? 'branch' : 'leaf'}" data-root-item-type="${hasChildren ? 'branch' : 'leaf'}">
374
+ ${renderTreeNode(root, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee)}
375
+ </div>
376
+ `;
377
+ });
378
+ container.innerHTML = `<div class="tree-root-mixed-grid">${htmlParts.join('')}</div>`;
379
+ }
178
380
 
179
381
  container.querySelectorAll('[data-action]').forEach((el) => {
180
382
  el.addEventListener('click', (e) => {
@@ -186,6 +388,19 @@ export function mountWorkTree(targetId, options) {
186
388
  if (action === 'add-child') onAddChild?.(id);
187
389
  if (action === 'edit') onEdit?.(id);
188
390
  if (action === 'delete') onDelete?.(id);
391
+ if (action === 'quick-chat') onQuickChat?.(id, el);
189
392
  });
190
393
  });
394
+ container.querySelectorAll('[data-select-id]').forEach((el) => {
395
+ el.addEventListener('click', (e) => {
396
+ e.stopPropagation();
397
+ const id = el.getAttribute('data-select-id');
398
+ if (!id) return;
399
+ onSelect?.(id);
400
+ });
401
+ });
402
+ if (renderMode !== 'dense') {
403
+ applyAdaptiveLeafGridLayout(container);
404
+ applyAdaptiveRootMixedGridLayout(container);
405
+ }
191
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qnote/q-ai-note",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "AI-assisted personal work sandbox and diary system",
6
6
  "main": "dist/server/index.js",