update
This commit is contained in:
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -96,6 +96,9 @@ importers:
|
|||||||
events:
|
events:
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
|
graphology:
|
||||||
|
specifier: ^0.26.0
|
||||||
|
version: 0.26.0(graphology-types@0.24.8)
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: ^11.11.1
|
specifier: ^11.11.1
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
@@ -129,6 +132,9 @@ importers:
|
|||||||
react-toastify:
|
react-toastify:
|
||||||
specifier: ^11.0.5
|
specifier: ^11.0.5
|
||||||
version: 11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 11.0.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
sigma:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2(graphology-types@0.24.8)
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
@@ -1614,6 +1620,19 @@ packages:
|
|||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
graphology-types@0.24.8:
|
||||||
|
resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==}
|
||||||
|
|
||||||
|
graphology-utils@2.5.2:
|
||||||
|
resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==}
|
||||||
|
peerDependencies:
|
||||||
|
graphology-types: '>=0.23.0'
|
||||||
|
|
||||||
|
graphology@0.26.0:
|
||||||
|
resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==}
|
||||||
|
peerDependencies:
|
||||||
|
graphology-types: '>=0.24.0'
|
||||||
|
|
||||||
guid-typescript@1.0.9:
|
guid-typescript@1.0.9:
|
||||||
resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
|
resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
|
||||||
|
|
||||||
@@ -2612,6 +2631,9 @@ packages:
|
|||||||
shiki@3.13.0:
|
shiki@3.13.0:
|
||||||
resolution: {integrity: sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==}
|
resolution: {integrity: sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==}
|
||||||
|
|
||||||
|
sigma@3.0.2:
|
||||||
|
resolution: {integrity: sha512-/BUbeOwPGruiBOm0YQQ6ZMcLIZ6tf/W+Jcm7dxZyAX0tK3WP9/sq7/NAWBxPIxVahdGjCJoGwej0Gdrv0DxlQQ==}
|
||||||
|
|
||||||
sisteransi@1.0.5:
|
sisteransi@1.0.5:
|
||||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
@@ -4604,6 +4626,17 @@ snapshots:
|
|||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
|
graphology-types@0.24.8: {}
|
||||||
|
|
||||||
|
graphology-utils@2.5.2(graphology-types@0.24.8):
|
||||||
|
dependencies:
|
||||||
|
graphology-types: 0.24.8
|
||||||
|
|
||||||
|
graphology@0.26.0(graphology-types@0.24.8):
|
||||||
|
dependencies:
|
||||||
|
events: 3.3.0
|
||||||
|
graphology-types: 0.24.8
|
||||||
|
|
||||||
guid-typescript@1.0.9: {}
|
guid-typescript@1.0.9: {}
|
||||||
|
|
||||||
h3@1.15.4:
|
h3@1.15.4:
|
||||||
@@ -6082,6 +6115,13 @@ snapshots:
|
|||||||
'@shikijs/vscode-textmate': 10.0.2
|
'@shikijs/vscode-textmate': 10.0.2
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
sigma@3.0.2(graphology-types@0.24.8):
|
||||||
|
dependencies:
|
||||||
|
events: 3.3.0
|
||||||
|
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- graphology-types
|
||||||
|
|
||||||
sisteransi@1.0.5: {}
|
sisteransi@1.0.5: {}
|
||||||
|
|
||||||
sitemap@8.0.0:
|
sitemap@8.0.0:
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"es-toolkit": "^1.40.0",
|
"es-toolkit": "^1.40.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
|
"graphology": "^0.26.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "^0.545.0",
|
"lucide-react": "^0.545.0",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-resizable-panels": "^3.0.6",
|
"react-resizable-panels": "^3.0.6",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
"sigma": "^3.0.2",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"three": "^0.180.0",
|
"three": "^0.180.0",
|
||||||
"wavesurfer.js": "^7.11.0",
|
"wavesurfer.js": "^7.11.0",
|
||||||
|
|||||||
127
web/src/apps/muse/base/graph/sigma/index.tsx
Normal file
127
web/src/apps/muse/base/graph/sigma/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import Graph from 'graphology';
|
||||||
|
import Sigma from 'sigma';
|
||||||
|
import { Mark } from '../../../modules/mark';
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dataSource?: Mark[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SigmaGraph = (props: Props) => {
|
||||||
|
const { dataSource = [] } = props;
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const sigmaRef = useRef<Sigma | null>(null);
|
||||||
|
const graphRef = useRef<Graph | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
// 创建图实例
|
||||||
|
const graph = new Graph();
|
||||||
|
graphRef.current = graph;
|
||||||
|
|
||||||
|
// 创建 Sigma 实例
|
||||||
|
const sigma = new Sigma(graph, containerRef.current, {
|
||||||
|
renderLabels: true,
|
||||||
|
labelRenderedSizeThreshold: 8,
|
||||||
|
labelDensity: 8,
|
||||||
|
});
|
||||||
|
sigmaRef.current = sigma;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sigma.kill();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!graphRef.current || !dataSource.length) return;
|
||||||
|
|
||||||
|
const graph = graphRef.current;
|
||||||
|
|
||||||
|
// 清空现有图数据
|
||||||
|
graph.clear();
|
||||||
|
|
||||||
|
// 添加节点
|
||||||
|
dataSource.forEach((mark, index) => {
|
||||||
|
graph.addNode(`node-${index}`, {
|
||||||
|
x: Math.random() * 800,
|
||||||
|
y: Math.random() * 600,
|
||||||
|
size: Math.random() * 20 + 5,
|
||||||
|
label: mark.title || `Mark ${index}`,
|
||||||
|
color: `#${Math.floor(Math.random() * 16777215).toString(16)}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// // 添加一些随机边(基于数据关系或随机生成)
|
||||||
|
// for (let i = 0; i < Math.min(dataSource.length - 1, 20); i++) {
|
||||||
|
// const sourceIndex = Math.floor(Math.random() * dataSource.length);
|
||||||
|
// const targetIndex = Math.floor(Math.random() * dataSource.length);
|
||||||
|
|
||||||
|
// if (sourceIndex !== targetIndex) {
|
||||||
|
// try {
|
||||||
|
// graph.addEdge(`node-${sourceIndex}`, `node-${targetIndex}`, {
|
||||||
|
// size: Math.random() * 5 + 1,
|
||||||
|
// color: '#999',
|
||||||
|
// });
|
||||||
|
// } catch (error) {
|
||||||
|
// // 边已存在,忽略错误
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 刷新 Sigma 渲染
|
||||||
|
if (sigmaRef.current) {
|
||||||
|
sigmaRef.current.refresh();
|
||||||
|
}
|
||||||
|
}, [dataSource]);
|
||||||
|
|
||||||
|
// 添加交互事件处理
|
||||||
|
useEffect(() => {
|
||||||
|
if (!sigmaRef.current) return;
|
||||||
|
|
||||||
|
const sigma = sigmaRef.current;
|
||||||
|
|
||||||
|
// 节点点击事件
|
||||||
|
const handleNodeClick = (event: any) => {
|
||||||
|
console.log('Node clicked:', event.node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 节点悬停事件
|
||||||
|
const handleNodeHover = (event: any) => {
|
||||||
|
console.log('Node hovered:', event.node);
|
||||||
|
};
|
||||||
|
|
||||||
|
sigma.on('clickNode', handleNodeClick);
|
||||||
|
sigma.on('enterNode', handleNodeHover);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sigma.off('clickNode', handleNodeClick);
|
||||||
|
sigma.off('enterNode', handleNodeHover);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', height: '100vh', position: 'relative' }}>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background: '#f5f5f5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
|
background: 'rgba(255,255,255,0.8)',
|
||||||
|
padding: '10px',
|
||||||
|
borderRadius: '4px'
|
||||||
|
}}>
|
||||||
|
<p>节点数量: {dataSource.length}</p>
|
||||||
|
<p>使用鼠标拖拽和缩放来探索图形</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Base } from "./table/index";
|
import { Base } from "./table/index";
|
||||||
|
import { markService } from "../modules/mark-service";
|
||||||
|
import { SigmaGraph } from "./graph/sigma/index";
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
key: 'table',
|
key: 'table',
|
||||||
@@ -22,15 +23,22 @@ const tabs = [
|
|||||||
|
|
||||||
export const BaseApp = () => {
|
export const BaseApp = () => {
|
||||||
const [activeTab, setActiveTab] = useState('table');
|
const [activeTab, setActiveTab] = useState('table');
|
||||||
|
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getMarks();
|
||||||
|
}, []);
|
||||||
|
const getMarks = async () => {
|
||||||
|
const marks = await markService.getAllMarks();
|
||||||
|
setDataSource(marks);
|
||||||
|
}
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'table':
|
case 'table':
|
||||||
return <Base />;
|
return <Base dataSource={dataSource} />;
|
||||||
case 'graph':
|
case 'graph':
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-96 text-gray-500">
|
<div className="w-full h-96">
|
||||||
关系图模块暂未实现
|
<SigmaGraph dataSource={dataSource} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case 'world':
|
case 'world':
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { Table } from './Table';
|
import { Table } from './Table';
|
||||||
import { DetailModal } from './DetailModal';
|
import { DetailModal } from './DetailModal';
|
||||||
import { mockMarks, Mark } from '../mock/collection';
|
import { Mark } from '../mock/collection';
|
||||||
import { TableColumn, ActionButton } from './types';
|
import { TableColumn, ActionButton } from './types';
|
||||||
|
|
||||||
export const Base = () => {
|
type Props = {
|
||||||
|
dataSource?: Mark[];
|
||||||
|
}
|
||||||
|
export const Base = (props: Props) => {
|
||||||
|
const { dataSource = [] } = props;
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [data, setData] = useState<Mark[]>(mockMarks);
|
const [data, setData] = useState<Mark[]>([]);
|
||||||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||||
const [currentRecord, setCurrentRecord] = useState<Mark | null>(null);
|
const [currentRecord, setCurrentRecord] = useState<Mark | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataSource) {
|
||||||
|
setData(dataSource);
|
||||||
|
}
|
||||||
|
}, [dataSource]);
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns: TableColumn<Mark>[] = [
|
const columns: TableColumn<Mark>[] = [
|
||||||
{
|
{
|
||||||
@@ -193,7 +202,7 @@ export const Base = () => {
|
|||||||
// 排序处理
|
// 排序处理
|
||||||
const handleSort = (field: string, order: 'asc' | 'desc' | null) => {
|
const handleSort = (field: string, order: 'asc' | 'desc' | null) => {
|
||||||
if (!order) {
|
if (!order) {
|
||||||
setData(mockMarks); // 重置为原始顺序
|
setData(dataSource); // 重置为原始顺序
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,27 +29,42 @@ export class MarkDB {
|
|||||||
|
|
||||||
// 创建索引以支持查询
|
// 创建索引以支持查询
|
||||||
async createIndexes() {
|
async createIndexes() {
|
||||||
// 检查索引是否已存在,而不是尝试创建
|
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
throw new Error('数据库未初始化');
|
throw new Error('数据库未初始化');
|
||||||
}
|
}
|
||||||
|
|
||||||
const transaction = this.db.transaction([STORE_NAME], 'readonly');
|
try {
|
||||||
const store = transaction.objectStore(STORE_NAME);
|
// PouchDB 创建索引的正确方式
|
||||||
|
const indexes = [
|
||||||
|
{ index: { fields: ['uid'] } },
|
||||||
|
{ index: { fields: ['markType'] } },
|
||||||
|
{ index: { fields: ['tags'] } },
|
||||||
|
{ index: { fields: ['createdAt'] } },
|
||||||
|
{ index: { fields: ['title'] } },
|
||||||
|
{ index: { fields: ['uid', 'markType'] } },
|
||||||
|
{ index: { fields: ['createdAt', 'uid'] } }
|
||||||
|
];
|
||||||
|
|
||||||
// 检查索引是否存在
|
const results = await Promise.allSettled(
|
||||||
const indexNames = ['uid', 'markType', 'tags', 'createdAt', 'title'];
|
indexes.map(indexDef => this.db.createIndex(indexDef))
|
||||||
const existingIndexes = Array.from(store.indexNames);
|
);
|
||||||
|
|
||||||
console.log('现有索引:', existingIndexes);
|
// 检查索引创建结果
|
||||||
|
results.forEach((result, index) => {
|
||||||
const missingIndexes = indexNames.filter(name => !existingIndexes.includes(name));
|
if (result.status === 'fulfilled') {
|
||||||
|
console.log(`索引 ${index + 1} 创建成功:`, result.value);
|
||||||
if (missingIndexes.length > 0) {
|
|
||||||
console.warn('缺少索引:', missingIndexes);
|
|
||||||
console.log('请删除数据库并重新初始化以创建缺少的索引');
|
|
||||||
} else {
|
} else {
|
||||||
console.log('所有索引都已存在');
|
// 如果索引已存在,PouchDB 会返回错误,这是正常的
|
||||||
|
if (result.reason?.status !== 409) {
|
||||||
|
console.warn(`索引 ${index + 1} 创建失败:`, result.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('索引初始化完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建索引失败:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const ChatInterface: React.FC = () => {
|
|||||||
<Bot className="w-6 h-6 text-white" />
|
<Bot className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">AI 助手</h1>
|
<h1 className="text-xl font-semibold text-gray-900">智能工作台</h1>
|
||||||
<p className="text-sm text-gray-500">在线 · 随时为您服务</p>
|
<p className="text-sm text-gray-500">在线 · 随时为您服务</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user