add keyword group

main
reng 3 weeks ago
parent 788d5998f7
commit 4573d34fb9
  1. 2
      Assets/Qdrant/storage/collections/data-v1/0/newest_clocks.json
  2. 2
      Assets/Qdrant/storage/collections/data-v2/0/newest_clocks.json
  3. 2
      Assets/Qdrant/storage/collections/data-v3/0/newest_clocks.json
  4. 2
      v2/app/src-tauri/Cargo.toml
  5. 3
      v2/app/src/App.css
  6. 15
      v2/app/src/App.jsx
  7. 7
      v2/app/src/components/graph.jsx
  8. 72
      v2/app/src/components/point.jsx
  9. 17
      v2/app/src/utils/group.js
  10. 36
      v2/app/src/utils/parsing.js

@ -1 +1 @@
{"clocks":[{"peer_id":2200039024721645,"clock_id":2,"current_tick":13,"token":18171945371147883509},{"peer_id":2200039024721645,"clock_id":0,"current_tick":31,"token":14274923382223712766},{"peer_id":2200039024721645,"clock_id":1,"current_tick":19,"token":13530503213804333634},{"peer_id":2200039024721645,"clock_id":6,"current_tick":1,"token":9225203475475803001},{"peer_id":2200039024721645,"clock_id":3,"current_tick":5,"token":12159487072447257415},{"peer_id":2200039024721645,"clock_id":4,"current_tick":3,"token":3225682797052271524},{"peer_id":2200039024721645,"clock_id":5,"current_tick":3,"token":16737324663161229622}]} {"clocks":[{"peer_id":2200039024721645,"clock_id":5,"current_tick":3,"token":16737324663161229622},{"peer_id":2200039024721645,"clock_id":1,"current_tick":19,"token":13530503213804333634},{"peer_id":2200039024721645,"clock_id":3,"current_tick":5,"token":12159487072447257415},{"peer_id":2200039024721645,"clock_id":2,"current_tick":13,"token":18171945371147883509},{"peer_id":2200039024721645,"clock_id":0,"current_tick":31,"token":14274923382223712766},{"peer_id":2200039024721645,"clock_id":6,"current_tick":1,"token":9225203475475803001},{"peer_id":2200039024721645,"clock_id":4,"current_tick":3,"token":3225682797052271524}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -29,4 +29,4 @@ rosc = "0.11.4"
tokio = { version = "1.45.1", features = ["net"] } tokio = { version = "1.45.1", features = ["net"] }
dotenv = "0.15.0" dotenv = "0.15.0"
tauri-plugin-log = "2" tauri-plugin-log = "2"
log = "0.4.27" log = "0.4.27"

@ -15,4 +15,7 @@ form{
/* filter: blur(3px); */ /* filter: blur(3px); */
/* @apply text-sm opacity-50; */ /* @apply text-sm opacity-50; */
}
.keyword > span {
@apply bg-gray-200 text-black rounded-full p-2;
} }

@ -154,6 +154,7 @@ function App() {
<div className="col-span-2">Content</div> <div className="col-span-2">Content</div>
<div className="bg-pink-200 text-xs self-center">Summary</div> <div className="bg-pink-200 text-xs self-center">Summary</div>
<div className="flex flex-row gap-2 flex-wrap items-center text-xs">Keywords</div> <div className="flex flex-row gap-2 flex-wrap items-center text-xs">Keywords</div>
<div className="text-xs">Distance to Keywords</div>
</div> </div>
{results?.length>0 && results.map((item, index)=>{ {results?.length>0 && results.map((item, index)=>{
let output={}; let output={};
@ -161,9 +162,18 @@ function App() {
return ( return (
<div key={index} className="border-b p-2 my-2 grid grid-cols-6 gap-2"> <div key={index} className="border-b p-2 my-2 grid grid-cols-6 gap-2">
<div>#{index+1}</div> <div>#{index+1}</div>
<p>score={item.score.toFixed(4)}</p> <p>score={item.score?.toFixed(4)}</p>
{(()=>{ {(()=>{
if(item.type==='keyword'){
const payload=JSON.parse(item.payload.text);
return (<>
<div colSpan={2} className="col-span-2 font-bold">{payload?.title}</div>
<div className="col-span-3">--</div>
<div className="bg-pink-200 text-xs self-center">--</div>
<div className="flex flex-row gap-2 flex-wrap items-center text-xs">--</div>
</>);
}
let p={ let p={
...item.payload, ...item.payload,
metadata: JSON.parse(item.payload.metadata), metadata: JSON.parse(item.payload.metadata),
@ -174,6 +184,9 @@ function App() {
<div className="col-span-2">{p.metadata.text}</div> <div className="col-span-2">{p.metadata.text}</div>
<div className="bg-pink-200 text-xs self-center">{p.summry}</div> <div className="bg-pink-200 text-xs self-center">{p.summry}</div>
<div className="flex flex-row gap-2 flex-wrap items-center text-xs">{p.keywords.map(el=><span className="bg-gray-200 rounded-full px-2" key={el}>{el}</span>)}</div> <div className="flex flex-row gap-2 flex-wrap items-center text-xs">{p.keywords.map(el=><span className="bg-gray-200 rounded-full px-2" key={el}>{el}</span>)}</div>
<div className="text-xs">{p.group.map(el=>(<>
<span>{el.title}({el.distance.toFixed(2)}) </span>
</>))}</div>
</>); </>);
})()} })()}
</div> </div>

@ -90,8 +90,11 @@ export default function Graph({results}){
<Bounds fit clip observe margin={1.2}> <Bounds fit clip observe margin={1.2}>
<group> <group>
{Array.isArray(points) && points?.map((point, index)=>( {Array.isArray(points) && points?.map((point, index)=>(
<Point key={index} point={point} index={index} totalPoints={points.length} result={results?.[index]} <Point key={index} point={point} index={index} totalPoints={points.length}
showContent={showContent} showKeyword={showKeywords} contentType={contentType} textToShow={textToShow} /> result={results?.[index]}
showContent={showContent} showKeyword={showKeywords} contentType={contentType}
textToShow={textToShow}
/>
))} ))}
</group> </group>

@ -59,16 +59,21 @@ export default function Point({point, index, totalPoints, result, showContent, s
function getContent(){ function getContent(){
if(!payload) return ''; if(!payload) return '';
// console.log("Point contentType:", contentType, payload);
// if(payload.normalized_time===-1){
// return <span>{payload.text}</span>
// }
switch(contentType){ switch(contentType){
case PointContentType.Full: case PointContentType.Full:
return payload.content.slice(0, textToShow) + (payload.content.length > textToShow ? '...' : ''); return payload.content.slice(0, textToShow) + (payload.content.length > textToShow ? '...' : '');
case PointContentType.Teaser: case PointContentType.Teaser:
return payload.teaser; return `<span>${payload.teaser}</span>`;
case PointContentType.Blur: case PointContentType.Blur:
case PointContentType.Symbol: case PointContentType.Symbol:
const start=payload.content.indexOf(payload.teaser); const start=payload.content.indexOf(payload.teaser);
if(start===-1) return payload.teaser; if(start===-1)
return `<span>${payload.teaser}</span>`;
const before=payload.content.substring(0, start); const before=payload.content.substring(0, start);
const after=payload.content.substring(start + payload.teaser.length); const after=payload.content.substring(start + payload.teaser.length);
@ -81,6 +86,7 @@ export default function Point({point, index, totalPoints, result, showContent, s
// //
useFrame((state) => { useFrame((state) => {
if (!meshRef.current || !refText.current || !payload) return; if (!meshRef.current || !refText.current || !payload) return;
const time = state.clock.elapsedTime; const time = state.clock.elapsedTime;
const lifeTimeOffset = (payload.number / payload.total) || 0; const lifeTimeOffset = (payload.number / payload.total) || 0;
@ -100,21 +106,29 @@ export default function Point({point, index, totalPoints, result, showContent, s
// alpha (0 1) // alpha (0 1)
let alpha = 1 - (distance - DEPTH_CONFIG.NEAR) / (DEPTH_CONFIG.FAR - DEPTH_CONFIG.NEAR); let alpha = 1 - (distance - DEPTH_CONFIG.NEAR) / (DEPTH_CONFIG.FAR - DEPTH_CONFIG.NEAR);
alpha = THREE.MathUtils.clamp(alpha, 0.2, 1); alpha = THREE.MathUtils.clamp(alpha, 0.2, 1);
// 調 () // 調 ()
const depthScaleFactor = THREE.MathUtils.clamp(1.5 - (distance / 300), 0.5, 1.2); const depthScaleFactor = THREE.MathUtils.clamp(1.5 - (distance / 300), 0.5, 1.2);
// HTML // HTML
if (refText.current) { if (refText.current) {
refText.current.style.opacity *= alpha;
// //
const finalHtmlScale = depthScaleFactor * (currentMeshScale / PointSize); const finalHtmlScale = depthScaleFactor * (currentMeshScale / PointSize);
refText.current.style.transform = `translate(-50%, -50%) scale(${finalHtmlScale})`; refText.current.style.transform = `translate(-50%, -50%) scale(${finalHtmlScale})`;
// CSS if(payload.normalized_time===-1){
const blurAmount = distance > DEPTH_CONFIG.FADE_START ? (distance - DEPTH_CONFIG.FADE_START) / 20 : 0; // meshRef.current.scale.set(PointSize, PointSize, PointSize);
refText.current.style.filter = `blur(${Math.min(blurAmount, 4)}px)`; refText.current.style.opacity = 1;
refText.current.style.transform = `translate(-50%, -50%) scale(1)`;
}else{
refText.current.style.opacity *= alpha;
// CSS
const blurAmount = distance > DEPTH_CONFIG.FADE_START ? (distance - DEPTH_CONFIG.FADE_START) / 20 : 0;
refText.current.style.filter = `blur(${Math.min(blurAmount, 4)}px)`;
}
// //
// refText.current.style.display = alpha <= 0.01 ? 'none' : 'block'; // refText.current.style.display = alpha <= 0.01 ? 'none' : 'block';
} }
@ -130,8 +144,11 @@ export default function Point({point, index, totalPoints, result, showContent, s
// return; // return;
if(!payload) return; if(!payload) return;
const lifeTime=payload.normalized_time || 0;
console.log("Point lifeTime:", lifeTime); const lifeTime=payload.normalized_time;
if(lifeTime===-1) return; // keyword
// console.log("Point lifeTime:", lifeTime);
// animate point size based on lifeTime // animate point size based on lifeTime
const targetSize=1; const targetSize=1;
@ -210,17 +227,36 @@ export default function Point({point, index, totalPoints, result, showContent, s
useEffect(()=>{ useEffect(()=>{
if(!result) return;
console.log("Point result:", result);
if(result?.type==='keyword'){
const title=JSON.parse(result.payload.text??'{}').title;
const payloadKeyword={
...JSON.parse(result.payload.text??'{}'),
keywords: [title],
number: 0,
total: 1,
content: title || '',
teaser: title ||'',
normalized_time: -1,
type: result.type || 'keyword',
};
setPayload(payloadKeyword);
return;
}
setPayload({ setPayload({
...JSON.parse(result.payload.metadata), ...JSON.parse(result.payload.metadata??'{}'),
keywords: result.payload.keywords || '[]', keywords: result.payload.keywords || [],
number: result.payload.number || '0', number: result.payload.number || 0,
total: result.payload.total || '1', total: result.payload.total || 1,
content: JSON.parse(result.payload.metadata).text || '', content: JSON.parse(result.payload.metadata??'{}').text || '',
teaser: result.payload.teaser || '', teaser: result.payload.teaser || '',
normalized_time: result.payload.normalized_time || 0, normalized_time: result.payload.normalized_time || 0,
type: result.type || 'data',
}); });
},[point]); },[result]);
return ( return (
@ -233,11 +269,11 @@ export default function Point({point, index, totalPoints, result, showContent, s
/> />
<Html> <Html>
<div ref={refText} className='text-white p-2 select-none text-center opacity-0'> <div ref={refText} className='text-white p-2 select-none text-center opacity-0'>
{<div className={`flex flex-row justify-center flex-wrap text-[1rem] ${showKeyword ? 'block' : 'hidden'}`}> {payload?.keywords && <div className={`flex flex-row justify-center flex-wrap text-[1rem] ${showKeyword ? 'block' : 'hidden'}`}>
{payload?.keywords.map((el, index)=><span key={index} className='px-2' style={{transform: `translate(${Math.random() * KeywordOffset}px,${Math.random() * KeywordOffset}px)`}}>{el}</span>)} {payload?.keywords.map((el, index)=><span key={index} className='px-2' style={{transform: `translate(${Math.random() * KeywordOffset}px,${Math.random() * KeywordOffset}px)`}}>{el}</span>)}
</div>} </div>}
{<div {<div
className={`text-[2rem] text-white rounded p-2 text-center w-[50vw] splittext ${showContent ? 'block' : 'hidden'}`} className={`text-[2rem] text-white rounded p-2 text-center max-w-[50vw] min-w-[20vw] splittext ${showContent ? 'block' : 'hidden'} ${payload?.type??''}`}
// style={{width: `${Math.random()*(payload?.content.length/5 || 10) + 20}vw`}} // style={{width: `${Math.random()*(payload?.content.length/5 || 10) + 20}vw`}}
dangerouslySetInnerHTML={{__html: getContent()}} dangerouslySetInnerHTML={{__html: getContent()}}
></div>} ></div>}

@ -0,0 +1,17 @@
export function distanceToKeywords(vector, keywordsEmbeddings) {
const distances = keywordsEmbeddings.map(keyword => {
return {
title: keyword.title,
id: keyword.id,
distance: euclideanDistance(vector, keyword.embedding)
};
});
distances.sort((a, b) => a.distance - b.distance);
return distances;
}
function euclideanDistance(vec1, vec2) {
if (vec1.length !== vec2.length) {
throw new Error("Vectors must be of same length");
}
return Math.sqrt(vec1.reduce((sum, val, i) => sum + Math.pow(val - vec2[i], 2), 0));
}

@ -1,4 +1,5 @@
import { fetch } from '@tauri-apps/plugin-http'; import { fetch } from '@tauri-apps/plugin-http';
import { distanceToKeywords } from './group.js';
const COLLECTION_DATA = 'data-v4'; const COLLECTION_DATA = 'data-v4';
const COLLECTION_THEME = 'theme-v1'; const COLLECTION_THEME = 'theme-v1';
@ -139,6 +140,7 @@ export async function searchByTheme(themeId, limit){
export async function searchByKeywords(keywordIds, limit){ export async function searchByKeywords(keywordIds, limit){
// get keyword embeddings from Qdrant // get keyword embeddings from Qdrant
let allResults = []; let allResults = [];
let keywords=[];
for(const keywordId of keywordIds){ for(const keywordId of keywordIds){
const res=await fetch(`http://localhost:6333/collections/${COLLECTION_KEYWORD}/points/${parseInt(keywordId)-1}`, { const res=await fetch(`http://localhost:6333/collections/${COLLECTION_KEYWORD}/points/${parseInt(keywordId)-1}`, {
method: 'GET', method: 'GET',
@ -153,14 +155,36 @@ export async function searchByKeywords(keywordIds, limit){
} }
const data = await res.json(); const data = await res.json();
console.log(data);
const results = await searchQdrant(data.result.vector, Math.floor(limit/keywordIds.length)); const results = await searchQdrant(data.result.vector, Math.floor(limit/keywordIds.length));
allResults.push(...results); allResults.push(...results);
const keypoint={
type: 'keyword',
id: keywordId,
payload: data.result.payload,
vector: data.result.vector
};
keywords.push(keypoint);
allResults.push(keypoint);
} }
normalizeResultTime(allResults); normalizeResultTime(allResults);
// calculate distance to each keyword
allResults.forEach(item=>{
if(item.type==='keyword') return;
item.payload.group = distanceToKeywords(item.vector, keywords.map(kw=>({
title: JSON.parse(kw.payload.text).title,
embedding: kw.vector,
id: kw.id
})));
});
return allResults; return allResults;
} }
@ -193,8 +217,11 @@ async function searchQdrant(query_embeddings, limit=10){
function normalizeResultTime(results){ function normalizeResultTime(results){
let min, max; let min, max;
results.forEach(item=>{ results.forEach(item=>{
if(item.type==='keyword') return;
const time = JSON.parse(item.payload.metadata).published_on; const time = JSON.parse(item.payload.metadata).published_on;
if(!min || time<min) min=time; if(!min || time<min) min=time;
if(!max || time>max) max=time; if(!max || time>max) max=time;
@ -204,12 +231,19 @@ function normalizeResultTime(results){
// results sort by time // results sort by time
results.sort((a, b) => { results.sort((a, b) => {
if(a.type==='keyword') return -1;
if(b.type==='keyword') return 1;
const timeA = JSON.parse(a.payload.metadata).published_on; const timeA = JSON.parse(a.payload.metadata).published_on;
const timeB = JSON.parse(b.payload.metadata).published_on; const timeB = JSON.parse(b.payload.metadata).published_on;
return timeA - timeB; return timeA - timeB;
}); });
results.forEach((item, index)=>{ results.forEach((item, index)=>{
if(item.type==='keyword') {
item.payload.normalized_time = -1;
return;
}
const time = JSON.parse(item.payload.metadata).published_on; const time = JSON.parse(item.payload.metadata).published_on;
item.payload.normalized_time = index;//Math.floor((time - min) / (max - min)*results.length); item.payload.normalized_time = index;//Math.floor((time - min) / (max - min)*results.length);
}); });

Loading…
Cancel
Save