Merge pull request #1308 from danielaskdd/main
Add node types and colors
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
lightrag/api/webui/index.html
generated
4
lightrag/api/webui/index.html
generated
@@ -8,8 +8,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="logo.png" />
|
<link rel="icon" type="image/svg+xml" href="logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Lightrag</title>
|
<title>Lightrag</title>
|
||||||
<script type="module" crossorigin src="/webui/assets/index-DYPw8N_u.js"></script>
|
<script type="module" crossorigin src="/webui/assets/index-Ctzoea9d.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-WCszRhP4.css">
|
<link rel="stylesheet" crossorigin href="/webui/assets/index-CTB4Vp_z.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@@ -16,7 +16,9 @@ const PREDEFINED_TYPES = [
|
|||||||
'weapon',
|
'weapon',
|
||||||
'animal',
|
'animal',
|
||||||
'unknown',
|
'unknown',
|
||||||
'technology'
|
'object',
|
||||||
|
'group',
|
||||||
|
'technology',
|
||||||
]
|
]
|
||||||
|
|
||||||
interface LegendProps {
|
interface LegendProps {
|
||||||
@@ -34,7 +36,7 @@ const Legend: React.FC<LegendProps> = ({ className }) => {
|
|||||||
return (
|
return (
|
||||||
<Card className={`p-2 max-w-xs ${className}`}>
|
<Card className={`p-2 max-w-xs ${className}`}>
|
||||||
<h3 className="text-sm font-medium mb-2">{t('graphPanel.legend')}</h3>
|
<h3 className="text-sm font-medium mb-2">{t('graphPanel.legend')}</h3>
|
||||||
<ScrollArea className="max-h-40">
|
<ScrollArea className="max-h-80">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{Array.from(typeColorMap.entries()).map(([type, color]) => (
|
{Array.from(typeColorMap.entries()).map(([type, color]) => (
|
||||||
<div key={type} className="flex items-center gap-2">
|
<div key={type} className="flex items-center gap-2">
|
||||||
|
@@ -11,112 +11,146 @@ import { useSettingsStore } from '@/stores/settings'
|
|||||||
|
|
||||||
import seedrandom from 'seedrandom'
|
import seedrandom from 'seedrandom'
|
||||||
|
|
||||||
// Predefined node colors - Primary colors
|
const TYPE_SYNONYMS: Record<string, string> = {
|
||||||
const NODE_COLORS = [
|
'unknown': 'unknown',
|
||||||
'#fdd868', // Yellow - UNKNOWN
|
'未知': 'unknown',
|
||||||
'#e3493b', // Google Red - geo
|
'other': 'unknown',
|
||||||
'#1212a1', // Deep Cyan - weapon
|
|
||||||
'#0f705d', // Green - organization
|
|
||||||
'#a64dff', // Purple - technology
|
|
||||||
'#f46a9b', // Magenta
|
|
||||||
'#00bfa0', // Turquoise
|
|
||||||
'#fdcce5', // Pale Pink
|
|
||||||
'#0f558a', // Blue - location
|
|
||||||
'#b2e061', // Yellow Green
|
|
||||||
'#bd7ebe', // Light Violet - event
|
|
||||||
'#439bd6', // Cyan - person
|
|
||||||
'#094338', // Deep Green
|
|
||||||
'#dc0ab4', // Pink Red
|
|
||||||
'#fd7f6f', // Light Red - category
|
|
||||||
'#b04238', // Brown
|
|
||||||
];
|
|
||||||
|
|
||||||
// Extended colors - Used when node types exceed primary colors
|
'category': 'category',
|
||||||
const EXTENDED_COLORS = [
|
'类别': 'category',
|
||||||
'#5ad45a', // Light Green
|
'type': 'category',
|
||||||
'#5a2c6d', // Deep Violet
|
'分类': 'category',
|
||||||
'#6c1313', // Dark Red
|
|
||||||
'#184868', // Dark Cyan
|
|
||||||
'#996600', // Yellow Brown
|
|
||||||
'#4421af', // Deep Purple
|
|
||||||
'#E67E22', // Carrot
|
|
||||||
'#ff1a1a', // Pure Red
|
|
||||||
];
|
|
||||||
|
|
||||||
// All available colors combined
|
'organization': 'organization',
|
||||||
const ALL_COLORS = [...NODE_COLORS, ...EXTENDED_COLORS];
|
'组织': 'organization',
|
||||||
|
'org': 'organization',
|
||||||
|
'company': 'organization',
|
||||||
|
'公司': 'organization',
|
||||||
|
'机构': 'organization',
|
||||||
|
|
||||||
// Helper function to get color based on node type
|
'event': 'event',
|
||||||
const getNodeColorByType = (nodeType: string | undefined): string => {
|
'事件': 'event',
|
||||||
const defaultColor = '#5D6D7E'; // Default color for nodes without a type or undefined type
|
'activity': 'event',
|
||||||
|
'活动': 'event',
|
||||||
|
|
||||||
// Return default color if node type is undefined
|
'person': 'person',
|
||||||
if (!nodeType) {
|
'人物': 'person',
|
||||||
return defaultColor;
|
'people': 'person',
|
||||||
}
|
'human': 'person',
|
||||||
|
'人': 'person',
|
||||||
|
|
||||||
// Get type color map from store
|
'animal': 'animal',
|
||||||
const typeColorMap = useGraphStore.getState().typeColorMap;
|
'动物': 'animal',
|
||||||
|
'creature': 'animal',
|
||||||
|
'生物': 'animal',
|
||||||
|
|
||||||
// If this type already has an assigned color, return it
|
'geo': 'geo',
|
||||||
if (typeColorMap.has(nodeType)) {
|
'地理': 'geo',
|
||||||
return typeColorMap.get(nodeType) || defaultColor;
|
'geography': 'geo',
|
||||||
}
|
'地域': 'geo',
|
||||||
|
|
||||||
// Get all currently used colors
|
'location': 'location',
|
||||||
const usedColors = new Set<string>();
|
'地点': 'location',
|
||||||
typeColorMap.forEach(color => {
|
'place': 'location',
|
||||||
usedColors.add(color);
|
'address': 'location',
|
||||||
});
|
'位置': 'location',
|
||||||
|
'地址': 'location',
|
||||||
|
|
||||||
// Assign color for new node type
|
'technology': 'technology',
|
||||||
// Use a simple hash function to map node type to color index
|
'技术': 'technology',
|
||||||
const getColorIndex = (str: string): number => {
|
'tech': 'technology',
|
||||||
let hash = 0;
|
'科技': 'technology',
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
'equipment': 'equipment',
|
||||||
hash |= 0; // Convert to 32bit integer
|
'设备': 'equipment',
|
||||||
}
|
'device': 'equipment',
|
||||||
// Ensure result is positive and within NODE_COLORS range only
|
'装备': 'equipment',
|
||||||
return Math.abs(hash) % NODE_COLORS.length;
|
|
||||||
|
'weapon': 'weapon',
|
||||||
|
'武器': 'weapon',
|
||||||
|
'arms': 'weapon',
|
||||||
|
'军火': 'weapon',
|
||||||
|
|
||||||
|
'object': 'object',
|
||||||
|
'物品': 'object',
|
||||||
|
'stuff': 'object',
|
||||||
|
'物体': 'object',
|
||||||
|
|
||||||
|
'group': 'group',
|
||||||
|
'群组': 'group',
|
||||||
|
'community': 'group',
|
||||||
|
'社区': 'group'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get initial color index from hash
|
// 节点类型到颜色的映射
|
||||||
const colorIndex = getColorIndex(nodeType);
|
const NODE_TYPE_COLORS: Record<string, string> = {
|
||||||
let newColor = NODE_COLORS[colorIndex];
|
'unknown': '#f4d371', // Yellow
|
||||||
|
'category': '#e3493b', // GoogleRed
|
||||||
|
'organization': '#0f705d', // Green
|
||||||
|
'event': '#00bfa0', // Turquoise
|
||||||
|
'person': '#4169E1', // RoyalBlue
|
||||||
|
'animal': '#84a3e1', // SkyBlue
|
||||||
|
'geo': '#ff99cc', // Pale Pink
|
||||||
|
'location': '#cf6d17', // Carrot
|
||||||
|
'technology': '#b300b3', // Purple
|
||||||
|
'equipment': '#2F4F4F', // DarkSlateGray
|
||||||
|
'weapon': '#4421af', // DeepPurple
|
||||||
|
'object': '#00cc00', // Green
|
||||||
|
'group': '#0f558a', // NavyBlue
|
||||||
|
};
|
||||||
|
|
||||||
// If the color is already used, find the next available color
|
// Extended colors pool - Used for unknown node types
|
||||||
if (usedColors.has(newColor) && usedColors.size < ALL_COLORS.length) {
|
const EXTENDED_COLORS = [
|
||||||
// First try to find an unused color in NODE_COLORS
|
'#5a2c6d', // DeepViolet
|
||||||
let foundUnused = false;
|
'#0000ff', // Blue
|
||||||
for (let i = 0; i < NODE_COLORS.length; i++) {
|
'#cd071e', // ChinaRed
|
||||||
const candidateColor = NODE_COLORS[i];
|
'#00CED1', // DarkTurquoise
|
||||||
if (!usedColors.has(candidateColor)) {
|
'#9b3a31', // DarkBrown
|
||||||
newColor = candidateColor;
|
'#b2e061', // YellowGreen
|
||||||
foundUnused = true;
|
'#bd7ebe', // LightViolet
|
||||||
break;
|
'#6ef7b3', // LightGreen
|
||||||
}
|
'#003366', // DarkBlue
|
||||||
|
'#DEB887', // BurlyWood
|
||||||
|
];
|
||||||
|
|
||||||
|
// Select color based on node type
|
||||||
|
const getNodeColorByType = (nodeType: string | undefined): string => {
|
||||||
|
|
||||||
|
const defaultColor = '#5D6D7E';
|
||||||
|
|
||||||
|
const normalizedType = nodeType ? nodeType.toLowerCase() : 'unknown';
|
||||||
|
const typeColorMap = useGraphStore.getState().typeColorMap;
|
||||||
|
|
||||||
|
// Return previous color if already mapped
|
||||||
|
if (typeColorMap.has(normalizedType)) {
|
||||||
|
return typeColorMap.get(normalizedType) || defaultColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all NODE_COLORS are used, then try EXTENDED_COLORS
|
const standardType = TYPE_SYNONYMS[normalizedType];
|
||||||
if (!foundUnused) {
|
if (standardType) {
|
||||||
newColor = defaultColor;
|
const color = NODE_TYPE_COLORS[standardType];
|
||||||
for (let i = 0; i < EXTENDED_COLORS.length; i++) {
|
// Update color mapping
|
||||||
const candidateColor = EXTENDED_COLORS[i];
|
const newMap = new Map(typeColorMap);
|
||||||
if (!usedColors.has(candidateColor)) {
|
newMap.set(normalizedType, color);
|
||||||
newColor = candidateColor;
|
useGraphStore.setState({ typeColorMap: newMap });
|
||||||
break;
|
return color;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all colors are used, we'll still use the hashed color
|
// For unpredefind nodeTypes, use extended colors
|
||||||
// In a more advanced implementation, we could create color variants here
|
// Find used extended colors
|
||||||
|
const usedExtendedColors = new Set(
|
||||||
|
Array.from(typeColorMap.entries())
|
||||||
|
.filter(([, color]) => !Object.values(NODE_TYPE_COLORS).includes(color))
|
||||||
|
.map(([, color]) => color)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find and use the first unused extended color
|
||||||
|
const unusedColor = EXTENDED_COLORS.find(color => !usedExtendedColors.has(color));
|
||||||
|
const newColor = unusedColor || defaultColor;
|
||||||
|
|
||||||
// Update color mapping
|
// Update color mapping
|
||||||
const newMap = new Map(typeColorMap);
|
const newMap = new Map(typeColorMap);
|
||||||
newMap.set(nodeType, newColor);
|
newMap.set(normalizedType, newColor);
|
||||||
useGraphStore.setState({ typeColorMap: newMap });
|
useGraphStore.setState({ typeColorMap: newMap });
|
||||||
|
|
||||||
return newColor;
|
return newColor;
|
||||||
|
@@ -155,6 +155,8 @@
|
|||||||
"weapon": "سلاح",
|
"weapon": "سلاح",
|
||||||
"animal": "حيوان",
|
"animal": "حيوان",
|
||||||
"unknown": "غير معروف",
|
"unknown": "غير معروف",
|
||||||
|
"object": "مصنوع",
|
||||||
|
"group": "مجموعة",
|
||||||
"technology": "العلوم"
|
"technology": "العلوم"
|
||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
|
@@ -155,6 +155,7 @@
|
|||||||
"weapon": "Weapon",
|
"weapon": "Weapon",
|
||||||
"animal": "Animal",
|
"animal": "Animal",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
|
"object": "Object",
|
||||||
"technology": "Technology"
|
"technology": "Technology"
|
||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
|
@@ -155,6 +155,8 @@
|
|||||||
"weapon": "Arme",
|
"weapon": "Arme",
|
||||||
"animal": "Animal",
|
"animal": "Animal",
|
||||||
"unknown": "Inconnu",
|
"unknown": "Inconnu",
|
||||||
|
"object": "Objet",
|
||||||
|
"group": "Groupe",
|
||||||
"technology": "Technologie"
|
"technology": "Technologie"
|
||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
|
@@ -155,6 +155,8 @@
|
|||||||
"weapon": "武器",
|
"weapon": "武器",
|
||||||
"animal": "动物",
|
"animal": "动物",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
|
"object": "物品",
|
||||||
|
"group": "群组",
|
||||||
"technology": "技术"
|
"technology": "技术"
|
||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
|
Reference in New Issue
Block a user