Merge pull request #1308 from danielaskdd/main

Add node types and colors
This commit is contained in:
Daniel.y
2025-04-08 13:43:18 +08:00
committed by GitHub
9 changed files with 273 additions and 230 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@
<link rel="icon" type="image/svg+xml" href="logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lightrag</title>
<script type="module" crossorigin src="/webui/assets/index-DYPw8N_u.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-WCszRhP4.css">
<script type="module" crossorigin src="/webui/assets/index-Ctzoea9d.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-CTB4Vp_z.css">
</head>
<body>
<div id="root"></div>

View File

@@ -16,7 +16,9 @@ const PREDEFINED_TYPES = [
'weapon',
'animal',
'unknown',
'technology'
'object',
'group',
'technology',
]
interface LegendProps {
@@ -34,7 +36,7 @@ const Legend: React.FC<LegendProps> = ({ className }) => {
return (
<Card className={`p-2 max-w-xs ${className}`}>
<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">
{Array.from(typeColorMap.entries()).map(([type, color]) => (
<div key={type} className="flex items-center gap-2">

View File

@@ -11,112 +11,146 @@ import { useSettingsStore } from '@/stores/settings'
import seedrandom from 'seedrandom'
// Predefined node colors - Primary colors
const NODE_COLORS = [
'#fdd868', // Yellow - UNKNOWN
'#e3493b', // Google Red - geo
'#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
];
const TYPE_SYNONYMS: Record<string, string> = {
'unknown': 'unknown',
'未知': 'unknown',
'other': 'unknown',
// Extended colors - Used when node types exceed primary colors
const EXTENDED_COLORS = [
'#5ad45a', // Light Green
'#5a2c6d', // Deep Violet
'#6c1313', // Dark Red
'#184868', // Dark Cyan
'#996600', // Yellow Brown
'#4421af', // Deep Purple
'#E67E22', // Carrot
'#ff1a1a', // Pure Red
];
'category': 'category',
'类别': 'category',
'type': 'category',
'分类': 'category',
// All available colors combined
const ALL_COLORS = [...NODE_COLORS, ...EXTENDED_COLORS];
'organization': 'organization',
'组织': 'organization',
'org': 'organization',
'company': 'organization',
'公司': 'organization',
'机构': 'organization',
// Helper function to get color based on node type
const getNodeColorByType = (nodeType: string | undefined): string => {
const defaultColor = '#5D6D7E'; // Default color for nodes without a type or undefined type
'event': 'event',
'事件': 'event',
'activity': 'event',
'活动': 'event',
// Return default color if node type is undefined
if (!nodeType) {
return defaultColor;
}
'person': 'person',
'人物': 'person',
'people': 'person',
'human': 'person',
'人': 'person',
// Get type color map from store
const typeColorMap = useGraphStore.getState().typeColorMap;
'animal': 'animal',
'动物': 'animal',
'creature': 'animal',
'生物': 'animal',
// If this type already has an assigned color, return it
if (typeColorMap.has(nodeType)) {
return typeColorMap.get(nodeType) || defaultColor;
}
'geo': 'geo',
'地理': 'geo',
'geography': 'geo',
'地域': 'geo',
// Get all currently used colors
const usedColors = new Set<string>();
typeColorMap.forEach(color => {
usedColors.add(color);
});
'location': 'location',
'地点': 'location',
'place': 'location',
'address': 'location',
'位置': 'location',
'地址': 'location',
// Assign color for new node type
// Use a simple hash function to map node type to color index
const getColorIndex = (str: string): number => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
// Ensure result is positive and within NODE_COLORS range only
return Math.abs(hash) % NODE_COLORS.length;
'technology': 'technology',
'技术': 'technology',
'tech': 'technology',
'科技': 'technology',
'equipment': 'equipment',
'设备': 'equipment',
'device': 'equipment',
'装备': 'equipment',
'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);
let newColor = NODE_COLORS[colorIndex];
// 节点类型到颜色的映射
const NODE_TYPE_COLORS: Record<string, string> = {
'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
if (usedColors.has(newColor) && usedColors.size < ALL_COLORS.length) {
// First try to find an unused color in NODE_COLORS
let foundUnused = false;
for (let i = 0; i < NODE_COLORS.length; i++) {
const candidateColor = NODE_COLORS[i];
if (!usedColors.has(candidateColor)) {
newColor = candidateColor;
foundUnused = true;
break;
}
// Extended colors pool - Used for unknown node types
const EXTENDED_COLORS = [
'#5a2c6d', // DeepViolet
'#0000ff', // Blue
'#cd071e', // ChinaRed
'#00CED1', // DarkTurquoise
'#9b3a31', // DarkBrown
'#b2e061', // YellowGreen
'#bd7ebe', // LightViolet
'#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
if (!foundUnused) {
newColor = defaultColor;
for (let i = 0; i < EXTENDED_COLORS.length; i++) {
const candidateColor = EXTENDED_COLORS[i];
if (!usedColors.has(candidateColor)) {
newColor = candidateColor;
break;
}
}
}
const standardType = TYPE_SYNONYMS[normalizedType];
if (standardType) {
const color = NODE_TYPE_COLORS[standardType];
// Update color mapping
const newMap = new Map(typeColorMap);
newMap.set(normalizedType, color);
useGraphStore.setState({ typeColorMap: newMap });
return color;
}
// If all colors are used, we'll still use the hashed color
// In a more advanced implementation, we could create color variants here
// For unpredefind nodeTypes, use extended colors
// 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
const newMap = new Map(typeColorMap);
newMap.set(nodeType, newColor);
newMap.set(normalizedType, newColor);
useGraphStore.setState({ typeColorMap: newMap });
return newColor;

View File

@@ -155,6 +155,8 @@
"weapon": "سلاح",
"animal": "حيوان",
"unknown": "غير معروف",
"object": "مصنوع",
"group": "مجموعة",
"technology": "العلوم"
},
"sideBar": {

View File

@@ -155,6 +155,7 @@
"weapon": "Weapon",
"animal": "Animal",
"unknown": "Unknown",
"object": "Object",
"technology": "Technology"
},
"sideBar": {

View File

@@ -155,6 +155,8 @@
"weapon": "Arme",
"animal": "Animal",
"unknown": "Inconnu",
"object": "Objet",
"group": "Groupe",
"technology": "Technologie"
},
"sideBar": {

View File

@@ -155,6 +155,8 @@
"weapon": "武器",
"animal": "动物",
"unknown": "未知",
"object": "物品",
"group": "群组",
"technology": "技术"
},
"sideBar": {