Merge pull request #1886 from danielaskdd/doc-list-paging
Feat: add document list pagination for webui
This commit is contained in:
commit
5e2b262094
66 changed files with 2195 additions and 351 deletions
|
|
@ -1 +1 @@
|
|||
__api_version__ = "0193"
|
||||
__api_version__ = "0195"
|
||||
|
|
|
|||
|
|
@ -492,6 +492,150 @@ class TrackStatusResponse(BaseModel):
|
|||
}
|
||||
|
||||
|
||||
class DocumentsRequest(BaseModel):
|
||||
"""Request model for paginated document queries
|
||||
|
||||
Attributes:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', 'id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
"""
|
||||
|
||||
status_filter: Optional[DocStatus] = Field(
|
||||
default=None, description="Filter by document status, None for all statuses"
|
||||
)
|
||||
page: int = Field(default=1, ge=1, description="Page number (1-based)")
|
||||
page_size: int = Field(
|
||||
default=50, ge=10, le=200, description="Number of documents per page (10-200)"
|
||||
)
|
||||
sort_field: Literal["created_at", "updated_at", "id", "file_path"] = Field(
|
||||
default="updated_at", description="Field to sort by"
|
||||
)
|
||||
sort_direction: Literal["asc", "desc"] = Field(
|
||||
default="desc", description="Sort direction"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"status_filter": "PROCESSED",
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"sort_field": "updated_at",
|
||||
"sort_direction": "desc",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PaginationInfo(BaseModel):
|
||||
"""Pagination information
|
||||
|
||||
Attributes:
|
||||
page: Current page number
|
||||
page_size: Number of items per page
|
||||
total_count: Total number of items
|
||||
total_pages: Total number of pages
|
||||
has_next: Whether there is a next page
|
||||
has_prev: Whether there is a previous page
|
||||
"""
|
||||
|
||||
page: int = Field(description="Current page number")
|
||||
page_size: int = Field(description="Number of items per page")
|
||||
total_count: int = Field(description="Total number of items")
|
||||
total_pages: int = Field(description="Total number of pages")
|
||||
has_next: bool = Field(description="Whether there is a next page")
|
||||
has_prev: bool = Field(description="Whether there is a previous page")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total_count": 150,
|
||||
"total_pages": 3,
|
||||
"has_next": True,
|
||||
"has_prev": False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PaginatedDocsResponse(BaseModel):
|
||||
"""Response model for paginated document queries
|
||||
|
||||
Attributes:
|
||||
documents: List of documents for the current page
|
||||
pagination: Pagination information
|
||||
status_counts: Count of documents by status for all documents
|
||||
"""
|
||||
|
||||
documents: List[DocStatusResponse] = Field(
|
||||
description="List of documents for the current page"
|
||||
)
|
||||
pagination: PaginationInfo = Field(description="Pagination information")
|
||||
status_counts: Dict[str, int] = Field(
|
||||
description="Count of documents by status for all documents"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"documents": [
|
||||
{
|
||||
"id": "doc_123456",
|
||||
"content_summary": "Research paper on machine learning",
|
||||
"content_length": 15240,
|
||||
"status": "PROCESSED",
|
||||
"created_at": "2025-03-31T12:34:56",
|
||||
"updated_at": "2025-03-31T12:35:30",
|
||||
"track_id": "upload_20250729_170612_abc123",
|
||||
"chunks_count": 12,
|
||||
"error_msg": None,
|
||||
"metadata": {"author": "John Doe", "year": 2025},
|
||||
"file_path": "research_paper.pdf",
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total_count": 150,
|
||||
"total_pages": 3,
|
||||
"has_next": True,
|
||||
"has_prev": False,
|
||||
},
|
||||
"status_counts": {
|
||||
"PENDING": 10,
|
||||
"PROCESSING": 5,
|
||||
"PROCESSED": 130,
|
||||
"FAILED": 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StatusCountsResponse(BaseModel):
|
||||
"""Response model for document status counts
|
||||
|
||||
Attributes:
|
||||
status_counts: Count of documents by status
|
||||
"""
|
||||
|
||||
status_counts: Dict[str, int] = Field(description="Count of documents by status")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"status_counts": {
|
||||
"PENDING": 10,
|
||||
"PROCESSING": 5,
|
||||
"PROCESSED": 130,
|
||||
"FAILED": 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PipelineStatusResponse(BaseModel):
|
||||
"""Response model for pipeline status
|
||||
|
||||
|
|
@ -1863,4 +2007,118 @@ def create_document_routes(
|
|||
logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post(
|
||||
"/paginated",
|
||||
response_model=PaginatedDocsResponse,
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def get_documents_paginated(
|
||||
request: DocumentsRequest,
|
||||
) -> PaginatedDocsResponse:
|
||||
"""
|
||||
Get documents with pagination support.
|
||||
|
||||
This endpoint retrieves documents with pagination, filtering, and sorting capabilities.
|
||||
It provides better performance for large document collections by loading only the
|
||||
requested page of data.
|
||||
|
||||
Args:
|
||||
request (DocumentsRequest): The request body containing pagination parameters
|
||||
|
||||
Returns:
|
||||
PaginatedDocsResponse: A response object containing:
|
||||
- documents: List of documents for the current page
|
||||
- pagination: Pagination information (page, total_count, etc.)
|
||||
- status_counts: Count of documents by status for all documents
|
||||
|
||||
Raises:
|
||||
HTTPException: If an error occurs while retrieving documents (500).
|
||||
"""
|
||||
try:
|
||||
# Get paginated documents and status counts in parallel
|
||||
docs_task = rag.doc_status.get_docs_paginated(
|
||||
status_filter=request.status_filter,
|
||||
page=request.page,
|
||||
page_size=request.page_size,
|
||||
sort_field=request.sort_field,
|
||||
sort_direction=request.sort_direction,
|
||||
)
|
||||
status_counts_task = rag.doc_status.get_all_status_counts()
|
||||
|
||||
# Execute both queries in parallel
|
||||
(documents_with_ids, total_count), status_counts = await asyncio.gather(
|
||||
docs_task, status_counts_task
|
||||
)
|
||||
|
||||
# Convert documents to response format
|
||||
doc_responses = []
|
||||
for doc_id, doc in documents_with_ids:
|
||||
doc_responses.append(
|
||||
DocStatusResponse(
|
||||
id=doc_id,
|
||||
content_summary=doc.content_summary,
|
||||
content_length=doc.content_length,
|
||||
status=doc.status,
|
||||
created_at=format_datetime(doc.created_at),
|
||||
updated_at=format_datetime(doc.updated_at),
|
||||
track_id=doc.track_id,
|
||||
chunks_count=doc.chunks_count,
|
||||
error_msg=doc.error_msg,
|
||||
metadata=doc.metadata,
|
||||
file_path=doc.file_path,
|
||||
)
|
||||
)
|
||||
|
||||
# Calculate pagination info
|
||||
total_pages = (total_count + request.page_size - 1) // request.page_size
|
||||
has_next = request.page < total_pages
|
||||
has_prev = request.page > 1
|
||||
|
||||
pagination = PaginationInfo(
|
||||
page=request.page,
|
||||
page_size=request.page_size,
|
||||
total_count=total_count,
|
||||
total_pages=total_pages,
|
||||
has_next=has_next,
|
||||
has_prev=has_prev,
|
||||
)
|
||||
|
||||
return PaginatedDocsResponse(
|
||||
documents=doc_responses,
|
||||
pagination=pagination,
|
||||
status_counts=status_counts,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting paginated documents: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get(
|
||||
"/status_counts",
|
||||
response_model=StatusCountsResponse,
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def get_document_status_counts() -> StatusCountsResponse:
|
||||
"""
|
||||
Get counts of documents by status.
|
||||
|
||||
This endpoint retrieves the count of documents in each processing status
|
||||
(PENDING, PROCESSING, PROCESSED, FAILED) for all documents in the system.
|
||||
|
||||
Returns:
|
||||
StatusCountsResponse: A response object containing status counts
|
||||
|
||||
Raises:
|
||||
HTTPException: If an error occurs while retrieving status counts (500).
|
||||
"""
|
||||
try:
|
||||
status_counts = await rag.doc_status.get_all_status_counts()
|
||||
return StatusCountsResponse(status_counts=status_counts)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting document status counts: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
return router
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
import{e as v,c as b,g as m,k as O,h as P,j as p,l as w,m as c,n as x,t as A,o as N}from"./_baseUniq-CvCjC6qE.js";import{aU as g,aq as _,aV as $,aW as E,aX as F,aY as I,aZ as M,a_ as y,a$ as B,b0 as T}from"./mermaid-vendor-Bi3TzHdn.js";var S=/\s/;function q(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,q(n)+1).replace(G,"")}var o=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,W=/^0o[0-7]+$/i,X=parseInt;function Y(n){if(typeof n=="number")return n;if(v(n))return o;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||W.test(n)?X(n.slice(2),t?2:8):L.test(n)?o:+n}var z=1/0,C=17976931348623157e292;function K(n){if(!n)return n===0?n:0;if(n=Y(n),n===z||n===-1/0){var r=n<0?-1:1;return r*C}return n===n?n:0}function U(n){var r=K(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,Z=l.hasOwnProperty,dn=_(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&$(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=E(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||F(h,l[u])&&!Z.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function D(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=m(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var J=Math.max;function Q(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:U(t);return a<0&&(a=J(e+a,0)),P(n,m(r),a)}var hn=D(Q);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=M(n)?w:V;return t(n,m(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function mn(n,r){return n!=null&&c(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!v(i):t(i,s)))var s=i,d=f}return d}function on(n){return n&&n.length?tn(n,y,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=A(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function vn(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,vn as d,on as e,fn as f,hn as g,mn as h,dn as i,U as j,un as l,gn as m,K as t};
|
||||
import{e as v,c as b,g as m,k as O,h as P,j as p,l as w,m as c,n as x,t as A,o as N}from"./_baseUniq-Cz3Y9685.js";import{aU as g,aq as _,aV as $,aW as E,aX as F,aY as I,aZ as M,a_ as y,a$ as B,b0 as T}from"./mermaid-vendor-Bq4ke0CV.js";var S=/\s/;function q(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,q(n)+1).replace(G,"")}var o=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,W=/^0o[0-7]+$/i,X=parseInt;function Y(n){if(typeof n=="number")return n;if(v(n))return o;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||W.test(n)?X(n.slice(2),t?2:8):L.test(n)?o:+n}var z=1/0,C=17976931348623157e292;function K(n){if(!n)return n===0?n:0;if(n=Y(n),n===z||n===-1/0){var r=n<0?-1:1;return r*C}return n===n?n:0}function U(n){var r=K(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,Z=l.hasOwnProperty,dn=_(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&$(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=E(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||F(h,l[u])&&!Z.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function D(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=m(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var J=Math.max;function Q(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:U(t);return a<0&&(a=J(e+a,0)),P(n,m(r),a)}var hn=D(Q);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=M(n)?w:V;return t(n,m(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function mn(n,r){return n!=null&&c(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!v(i):t(i,s)))var s=i,d=f}return d}function on(n){return n&&n.length?tn(n,y,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=A(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function vn(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,vn as d,on as e,fn as f,hn as g,mn as h,dn as i,U as j,un as l,gn as m,K as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{_ as l}from"./mermaid-vendor-Bi3TzHdn.js";function m(e,c){var i,t,o;e.accDescr&&((i=c.setAccDescription)==null||i.call(c,e.accDescr)),e.accTitle&&((t=c.setAccTitle)==null||t.call(c,e.accTitle)),e.title&&((o=c.setDiagramTitle)==null||o.call(c,e.title))}l(m,"populateCommonDb");export{m as p};
|
||||
import{_ as l}from"./mermaid-vendor-Bq4ke0CV.js";function m(e,c){var i,t,o;e.accDescr&&((i=c.setAccDescription)==null||i.call(c,e.accDescr)),e.accTitle&&((t=c.setAccTitle)==null||t.call(c,e.accTitle)),e.title&&((o=c.setDiagramTitle)==null||o.call(c,e.title))}l(m,"populateCommonDb");export{m as p};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{_ as n,a1 as x,j as l}from"./mermaid-vendor-Bi3TzHdn.js";var c=n((a,t)=>{const e=a.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((a,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};c(a,e).lower()},"drawBackgroundRect"),g=n((a,t)=>{const e=t.text.replace(x," "),r=a.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const s=r.append("tspan");return s.attr("x",t.x+t.textMargin*2),s.text(e),r},"drawText"),h=n((a,t,e,r)=>{const s=a.append("image");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",i)},"drawImage"),m=n((a,t,e,r)=>{const s=a.append("use");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,c as d,h as e,g as f,y as g};
|
||||
import{_ as n,a1 as x,j as l}from"./mermaid-vendor-Bq4ke0CV.js";var c=n((a,t)=>{const e=a.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((a,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};c(a,e).lower()},"drawBackgroundRect"),g=n((a,t)=>{const e=t.text.replace(x," "),r=a.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const s=r.append("tspan");return s.attr("x",t.x+t.textMargin*2),s.text(e),r},"drawText"),h=n((a,t,e,r)=>{const s=a.append("image");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",i)},"drawImage"),m=n((a,t,e,r)=>{const s=a.append("use");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,c as d,h as e,g as f,y as g};
|
||||
|
|
@ -1 +1 @@
|
|||
import{_ as n,d as r,e as d,l as g}from"./mermaid-vendor-Bi3TzHdn.js";var u=n((e,t)=>{let o;return t==="sandbox"&&(o=r("#i"+e)),(t==="sandbox"?r(o.nodes()[0].contentDocument.body):r("body")).select(`[id="${e}"]`)},"getDiagramElement"),b=n((e,t,o,i)=>{e.attr("class",o);const{width:a,height:s,x:h,y:x}=l(e,t);d(e,s,a,i);const c=w(h,x,a,s,t);e.attr("viewBox",c),g.debug(`viewBox configured: ${c} with padding: ${t}`)},"setupViewPortForSVG"),l=n((e,t)=>{var i;const o=((i=e.node())==null?void 0:i.getBBox())||{width:0,height:0,x:0,y:0};return{width:o.width+t*2,height:o.height+t*2,x:o.x,y:o.y}},"calculateDimensionsWithPadding"),w=n((e,t,o,i,a)=>`${e-a} ${t-a} ${o} ${i}`,"createViewBox");export{u as g,b as s};
|
||||
import{_ as n,d as r,e as d,l as g}from"./mermaid-vendor-Bq4ke0CV.js";var u=n((e,t)=>{let o;return t==="sandbox"&&(o=r("#i"+e)),(t==="sandbox"?r(o.nodes()[0].contentDocument.body):r("body")).select(`[id="${e}"]`)},"getDiagramElement"),b=n((e,t,o,i)=>{e.attr("class",o);const{width:a,height:s,x:h,y:x}=l(e,t);d(e,s,a,i);const c=w(h,x,a,s,t);e.attr("viewBox",c),g.debug(`viewBox configured: ${c} with padding: ${t}`)},"setupViewPortForSVG"),l=n((e,t)=>{var i;const o=((i=e.node())==null?void 0:i.getBBox())||{width:0,height:0,x:0,y:0};return{width:o.width+t*2,height:o.height+t*2,x:o.x,y:o.y}},"calculateDimensionsWithPadding"),w=n((e,t,o,i,a)=>`${e-a} ${t-a} ${o} ${i}`,"createViewBox");export{u as g,b as s};
|
||||
|
|
@ -1 +1 @@
|
|||
import{_ as s}from"./mermaid-vendor-Bi3TzHdn.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I};
|
||||
import{_ as s}from"./mermaid-vendor-Bq4ke0CV.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I};
|
||||
|
|
@ -1 +1 @@
|
|||
import{s as a,c as s,a as e,C as t}from"./chunk-A2AXSNBT-Cn1JMUWB.js";import{_ as i}from"./mermaid-vendor-Bi3TzHdn.js";import"./chunk-RZ5BOZE2-DCsogQCZ.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
||||
import{s as a,c as s,a as e,C as t}from"./chunk-A2AXSNBT-BhjweuTY.js";import{_ as i}from"./mermaid-vendor-Bq4ke0CV.js";import"./chunk-RZ5BOZE2-DujSvYNw.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
||||
|
|
@ -1 +1 @@
|
|||
import{s as a,c as s,a as e,C as t}from"./chunk-A2AXSNBT-Cn1JMUWB.js";import{_ as i}from"./mermaid-vendor-Bi3TzHdn.js";import"./chunk-RZ5BOZE2-DCsogQCZ.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
||||
import{s as a,c as s,a as e,C as t}from"./chunk-A2AXSNBT-BhjweuTY.js";import{_ as i}from"./mermaid-vendor-Bq4ke0CV.js";import"./chunk-RZ5BOZE2-DujSvYNw.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
|
||||
1
lightrag/api/webui/assets/clone-CFB5BEXb.js
generated
1
lightrag/api/webui/assets/clone-CFB5BEXb.js
generated
|
|
@ -1 +0,0 @@
|
|||
import{b as r}from"./_baseUniq-CvCjC6qE.js";var e=4;function a(o){return r(o,e)}export{a as c};
|
||||
1
lightrag/api/webui/assets/clone-CpYqNYda.js
generated
Normal file
1
lightrag/api/webui/assets/clone-CpYqNYda.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{b as r}from"./_baseUniq-Cz3Y9685.js";var e=4;function a(o){return r(o,e)}export{a as c};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
import{p as k}from"./chunk-4BMEZGHF-BSIbM2yC.js";import{_ as l,s as R,g as F,t as I,q as _,a as E,b as D,K as G,z,F as y,G as C,H as P,l as H,Q as V}from"./mermaid-vendor-Bi3TzHdn.js";import{p as W}from"./radar-MK3ICKWK-CxtTBuu1.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-CvCjC6qE.js";import"./_basePickBy-DY0K0qir.js";import"./clone-CFB5BEXb.js";var h={showLegend:!0,ticks:5,max:null,min:0,graticule:"circle"},w={axes:[],curves:[],options:h},g=structuredClone(w),B=P.radar,j=l(()=>y({...B,...C().radar}),"getConfig"),b=l(()=>g.axes,"getAxes"),q=l(()=>g.curves,"getCurves"),K=l(()=>g.options,"getOptions"),N=l(a=>{g.axes=a.map(t=>({name:t.name,label:t.label??t.name}))},"setAxes"),Q=l(a=>{g.curves=a.map(t=>({name:t.name,label:t.label??t.name,entries:U(t.entries)}))},"setCurves"),U=l(a=>{if(a[0].axis==null)return a.map(e=>e.value);const t=b();if(t.length===0)throw new Error("Axes must be populated before curves for reference entries");return t.map(e=>{const r=a.find(s=>{var o;return((o=s.axis)==null?void 0:o.$refText)===e.name});if(r===void 0)throw new Error("Missing entry for axis "+e.label);return r.value})},"computeCurveEntries"),X=l(a=>{var e,r,s,o,i;const t=a.reduce((n,c)=>(n[c.name]=c,n),{});g.options={showLegend:((e=t.showLegend)==null?void 0:e.value)??h.showLegend,ticks:((r=t.ticks)==null?void 0:r.value)??h.ticks,max:((s=t.max)==null?void 0:s.value)??h.max,min:((o=t.min)==null?void 0:o.value)??h.min,graticule:((i=t.graticule)==null?void 0:i.value)??h.graticule}},"setOptions"),Y=l(()=>{z(),g=structuredClone(w)},"clear"),$={getAxes:b,getCurves:q,getOptions:K,setAxes:N,setCurves:Q,setOptions:X,getConfig:j,clear:Y,setAccTitle:D,getAccTitle:E,setDiagramTitle:_,getDiagramTitle:I,getAccDescription:F,setAccDescription:R},Z=l(a=>{k(a,$);const{axes:t,curves:e,options:r}=a;$.setAxes(t),$.setCurves(e),$.setOptions(r)},"populate"),J={parse:l(async a=>{const t=await W("radar",a);H.debug(t),Z(t)},"parse")},tt=l((a,t,e,r)=>{const s=r.db,o=s.getAxes(),i=s.getCurves(),n=s.getOptions(),c=s.getConfig(),d=s.getDiagramTitle(),u=G(t),p=et(u,c),m=n.max??Math.max(...i.map(f=>Math.max(...f.entries))),x=n.min,v=Math.min(c.width,c.height)/2;at(p,o,v,n.ticks,n.graticule),rt(p,o,v,c),M(p,o,i,x,m,n.graticule,c),T(p,i,n.showLegend,c),p.append("text").attr("class","radarTitle").text(d).attr("x",0).attr("y",-c.height/2-c.marginTop)},"draw"),et=l((a,t)=>{const e=t.width+t.marginLeft+t.marginRight,r=t.height+t.marginTop+t.marginBottom,s={x:t.marginLeft+t.width/2,y:t.marginTop+t.height/2};return a.attr("viewbox",`0 0 ${e} ${r}`).attr("width",e).attr("height",r),a.append("g").attr("transform",`translate(${s.x}, ${s.y})`)},"drawFrame"),at=l((a,t,e,r,s)=>{if(s==="circle")for(let o=0;o<r;o++){const i=e*(o+1)/r;a.append("circle").attr("r",i).attr("class","radarGraticule")}else if(s==="polygon"){const o=t.length;for(let i=0;i<r;i++){const n=e*(i+1)/r,c=t.map((d,u)=>{const p=2*u*Math.PI/o-Math.PI/2,m=n*Math.cos(p),x=n*Math.sin(p);return`${m},${x}`}).join(" ");a.append("polygon").attr("points",c).attr("class","radarGraticule")}}},"drawGraticule"),rt=l((a,t,e,r)=>{const s=t.length;for(let o=0;o<s;o++){const i=t[o].label,n=2*o*Math.PI/s-Math.PI/2;a.append("line").attr("x1",0).attr("y1",0).attr("x2",e*r.axisScaleFactor*Math.cos(n)).attr("y2",e*r.axisScaleFactor*Math.sin(n)).attr("class","radarAxisLine"),a.append("text").text(i).attr("x",e*r.axisLabelFactor*Math.cos(n)).attr("y",e*r.axisLabelFactor*Math.sin(n)).attr("class","radarAxisLabel")}},"drawAxes");function M(a,t,e,r,s,o,i){const n=t.length,c=Math.min(i.width,i.height)/2;e.forEach((d,u)=>{if(d.entries.length!==n)return;const p=d.entries.map((m,x)=>{const v=2*Math.PI*x/n-Math.PI/2,f=A(m,r,s,c),O=f*Math.cos(v),S=f*Math.sin(v);return{x:O,y:S}});o==="circle"?a.append("path").attr("d",L(p,i.curveTension)).attr("class",`radarCurve-${u}`):o==="polygon"&&a.append("polygon").attr("points",p.map(m=>`${m.x},${m.y}`).join(" ")).attr("class",`radarCurve-${u}`)})}l(M,"drawCurves");function A(a,t,e,r){const s=Math.min(Math.max(a,t),e);return r*(s-t)/(e-t)}l(A,"relativeRadius");function L(a,t){const e=a.length;let r=`M${a[0].x},${a[0].y}`;for(let s=0;s<e;s++){const o=a[(s-1+e)%e],i=a[s],n=a[(s+1)%e],c=a[(s+2)%e],d={x:i.x+(n.x-o.x)*t,y:i.y+(n.y-o.y)*t},u={x:n.x-(c.x-i.x)*t,y:n.y-(c.y-i.y)*t};r+=` C${d.x},${d.y} ${u.x},${u.y} ${n.x},${n.y}`}return`${r} Z`}l(L,"closedRoundCurve");function T(a,t,e,r){if(!e)return;const s=(r.width/2+r.marginRight)*3/4,o=-(r.height/2+r.marginTop)*3/4,i=20;t.forEach((n,c)=>{const d=a.append("g").attr("transform",`translate(${s}, ${o+c*i})`);d.append("rect").attr("width",12).attr("height",12).attr("class",`radarLegendBox-${c}`),d.append("text").attr("x",16).attr("y",0).attr("class","radarLegendText").text(n.label)})}l(T,"drawLegend");var st={draw:tt},nt=l((a,t)=>{let e="";for(let r=0;r<a.THEME_COLOR_LIMIT;r++){const s=a[`cScale${r}`];e+=`
|
||||
import{p as k}from"./chunk-4BMEZGHF-YbXkQXP1.js";import{_ as l,s as R,g as F,t as I,q as _,a as E,b as D,K as G,z,F as y,G as C,H as P,l as H,Q as V}from"./mermaid-vendor-Bq4ke0CV.js";import{p as W}from"./radar-MK3ICKWK-DShnJK9k.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-Cz3Y9685.js";import"./_basePickBy-CeKYG_LI.js";import"./clone-CpYqNYda.js";var h={showLegend:!0,ticks:5,max:null,min:0,graticule:"circle"},w={axes:[],curves:[],options:h},g=structuredClone(w),B=P.radar,j=l(()=>y({...B,...C().radar}),"getConfig"),b=l(()=>g.axes,"getAxes"),q=l(()=>g.curves,"getCurves"),K=l(()=>g.options,"getOptions"),N=l(a=>{g.axes=a.map(t=>({name:t.name,label:t.label??t.name}))},"setAxes"),Q=l(a=>{g.curves=a.map(t=>({name:t.name,label:t.label??t.name,entries:U(t.entries)}))},"setCurves"),U=l(a=>{if(a[0].axis==null)return a.map(e=>e.value);const t=b();if(t.length===0)throw new Error("Axes must be populated before curves for reference entries");return t.map(e=>{const r=a.find(s=>{var o;return((o=s.axis)==null?void 0:o.$refText)===e.name});if(r===void 0)throw new Error("Missing entry for axis "+e.label);return r.value})},"computeCurveEntries"),X=l(a=>{var e,r,s,o,i;const t=a.reduce((n,c)=>(n[c.name]=c,n),{});g.options={showLegend:((e=t.showLegend)==null?void 0:e.value)??h.showLegend,ticks:((r=t.ticks)==null?void 0:r.value)??h.ticks,max:((s=t.max)==null?void 0:s.value)??h.max,min:((o=t.min)==null?void 0:o.value)??h.min,graticule:((i=t.graticule)==null?void 0:i.value)??h.graticule}},"setOptions"),Y=l(()=>{z(),g=structuredClone(w)},"clear"),$={getAxes:b,getCurves:q,getOptions:K,setAxes:N,setCurves:Q,setOptions:X,getConfig:j,clear:Y,setAccTitle:D,getAccTitle:E,setDiagramTitle:_,getDiagramTitle:I,getAccDescription:F,setAccDescription:R},Z=l(a=>{k(a,$);const{axes:t,curves:e,options:r}=a;$.setAxes(t),$.setCurves(e),$.setOptions(r)},"populate"),J={parse:l(async a=>{const t=await W("radar",a);H.debug(t),Z(t)},"parse")},tt=l((a,t,e,r)=>{const s=r.db,o=s.getAxes(),i=s.getCurves(),n=s.getOptions(),c=s.getConfig(),d=s.getDiagramTitle(),u=G(t),p=et(u,c),m=n.max??Math.max(...i.map(f=>Math.max(...f.entries))),x=n.min,v=Math.min(c.width,c.height)/2;at(p,o,v,n.ticks,n.graticule),rt(p,o,v,c),M(p,o,i,x,m,n.graticule,c),T(p,i,n.showLegend,c),p.append("text").attr("class","radarTitle").text(d).attr("x",0).attr("y",-c.height/2-c.marginTop)},"draw"),et=l((a,t)=>{const e=t.width+t.marginLeft+t.marginRight,r=t.height+t.marginTop+t.marginBottom,s={x:t.marginLeft+t.width/2,y:t.marginTop+t.height/2};return a.attr("viewbox",`0 0 ${e} ${r}`).attr("width",e).attr("height",r),a.append("g").attr("transform",`translate(${s.x}, ${s.y})`)},"drawFrame"),at=l((a,t,e,r,s)=>{if(s==="circle")for(let o=0;o<r;o++){const i=e*(o+1)/r;a.append("circle").attr("r",i).attr("class","radarGraticule")}else if(s==="polygon"){const o=t.length;for(let i=0;i<r;i++){const n=e*(i+1)/r,c=t.map((d,u)=>{const p=2*u*Math.PI/o-Math.PI/2,m=n*Math.cos(p),x=n*Math.sin(p);return`${m},${x}`}).join(" ");a.append("polygon").attr("points",c).attr("class","radarGraticule")}}},"drawGraticule"),rt=l((a,t,e,r)=>{const s=t.length;for(let o=0;o<s;o++){const i=t[o].label,n=2*o*Math.PI/s-Math.PI/2;a.append("line").attr("x1",0).attr("y1",0).attr("x2",e*r.axisScaleFactor*Math.cos(n)).attr("y2",e*r.axisScaleFactor*Math.sin(n)).attr("class","radarAxisLine"),a.append("text").text(i).attr("x",e*r.axisLabelFactor*Math.cos(n)).attr("y",e*r.axisLabelFactor*Math.sin(n)).attr("class","radarAxisLabel")}},"drawAxes");function M(a,t,e,r,s,o,i){const n=t.length,c=Math.min(i.width,i.height)/2;e.forEach((d,u)=>{if(d.entries.length!==n)return;const p=d.entries.map((m,x)=>{const v=2*Math.PI*x/n-Math.PI/2,f=A(m,r,s,c),O=f*Math.cos(v),S=f*Math.sin(v);return{x:O,y:S}});o==="circle"?a.append("path").attr("d",L(p,i.curveTension)).attr("class",`radarCurve-${u}`):o==="polygon"&&a.append("polygon").attr("points",p.map(m=>`${m.x},${m.y}`).join(" ")).attr("class",`radarCurve-${u}`)})}l(M,"drawCurves");function A(a,t,e,r){const s=Math.min(Math.max(a,t),e);return r*(s-t)/(e-t)}l(A,"relativeRadius");function L(a,t){const e=a.length;let r=`M${a[0].x},${a[0].y}`;for(let s=0;s<e;s++){const o=a[(s-1+e)%e],i=a[s],n=a[(s+1)%e],c=a[(s+2)%e],d={x:i.x+(n.x-o.x)*t,y:i.y+(n.y-o.y)*t},u={x:n.x-(c.x-i.x)*t,y:n.y-(c.y-i.y)*t};r+=` C${d.x},${d.y} ${u.x},${u.y} ${n.x},${n.y}`}return`${r} Z`}l(L,"closedRoundCurve");function T(a,t,e,r){if(!e)return;const s=(r.width/2+r.marginRight)*3/4,o=-(r.height/2+r.marginTop)*3/4,i=20;t.forEach((n,c)=>{const d=a.append("g").attr("transform",`translate(${s}, ${o+c*i})`);d.append("rect").attr("width",12).attr("height",12).attr("class",`radarLegendBox-${c}`),d.append("text").attr("x",16).attr("y",0).attr("class","radarLegendText").text(n.label)})}l(T,"drawLegend");var st={draw:tt},nt=l((a,t)=>{let e="";for(let r=0;r<a.THEME_COLOR_LIMIT;r++){const s=a[`cScale${r}`];e+=`
|
||||
.radarCurve-${r} {
|
||||
color: ${s};
|
||||
fill: ${s};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{p as w}from"./chunk-4BMEZGHF-BSIbM2yC.js";import{_ as n,s as B,g as S,t as F,q as z,a as P,b as W,F as x,K as T,e as D,z as _,G as A,H as E,l as v}from"./mermaid-vendor-Bi3TzHdn.js";import{p as N}from"./radar-MK3ICKWK-CxtTBuu1.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-CvCjC6qE.js";import"./_basePickBy-DY0K0qir.js";import"./clone-CFB5BEXb.js";var C={packet:[]},h=structuredClone(C),L=E.packet,Y=n(()=>{const t=x({...L,...A().packet});return t.showBits&&(t.paddingY+=10),t},"getConfig"),G=n(()=>h.packet,"getPacket"),H=n(t=>{t.length>0&&h.packet.push(t)},"pushWord"),I=n(()=>{_(),h=structuredClone(C)},"clear"),m={pushWord:H,getPacket:G,getConfig:Y,clear:I,setAccTitle:W,getAccTitle:P,setDiagramTitle:z,getDiagramTitle:F,getAccDescription:S,setAccDescription:B},K=1e4,M=n(t=>{w(t,m);let e=-1,o=[],s=1;const{bitsPerRow:i}=m.getConfig();for(let{start:a,end:r,label:p}of t.blocks){if(r&&r<a)throw new Error(`Packet block ${a} - ${r} is invalid. End must be greater than start.`);if(a!==e+1)throw new Error(`Packet block ${a} - ${r??a} is not contiguous. It should start from ${e+1}.`);for(e=r??a,v.debug(`Packet block ${a} - ${e} with label ${p}`);o.length<=i+1&&m.getPacket().length<K;){const[b,c]=O({start:a,end:r,label:p},s,i);if(o.push(b),b.end+1===s*i&&(m.pushWord(o),o=[],s++),!c)break;({start:a,end:r,label:p}=c)}}m.pushWord(o)},"populate"),O=n((t,e,o)=>{if(t.end===void 0&&(t.end=t.start),t.start>t.end)throw new Error(`Block start ${t.start} is greater than block end ${t.end}.`);return t.end+1<=e*o?[t,void 0]:[{start:t.start,end:e*o-1,label:t.label},{start:e*o,end:t.end,label:t.label}]},"getNextFittingBlock"),q={parse:n(async t=>{const e=await N("packet",t);v.debug(e),M(e)},"parse")},R=n((t,e,o,s)=>{const i=s.db,a=i.getConfig(),{rowHeight:r,paddingY:p,bitWidth:b,bitsPerRow:c}=a,u=i.getPacket(),l=i.getDiagramTitle(),g=r+p,d=g*(u.length+1)-(l?0:r),k=b*c+2,f=T(e);f.attr("viewbox",`0 0 ${k} ${d}`),D(f,d,k,a.useMaxWidth);for(const[$,y]of u.entries())U(f,y,$,a);f.append("text").text(l).attr("x",k/2).attr("y",d-g/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),U=n((t,e,o,{rowHeight:s,paddingX:i,paddingY:a,bitWidth:r,bitsPerRow:p,showBits:b})=>{const c=t.append("g"),u=o*(s+a)+a;for(const l of e){const g=l.start%p*r+1,d=(l.end-l.start+1)*r-i;if(c.append("rect").attr("x",g).attr("y",u).attr("width",d).attr("height",s).attr("class","packetBlock"),c.append("text").attr("x",g+d/2).attr("y",u+s/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(l.label),!b)continue;const k=l.end===l.start,f=u-2;c.append("text").attr("x",g+(k?d/2:0)).attr("y",f).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",k?"middle":"start").text(l.start),k||c.append("text").attr("x",g+d).attr("y",f).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(l.end)}},"drawWord"),X={draw:R},j={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},J=n(({packet:t}={})=>{const e=x(j,t);return`
|
||||
import{p as w}from"./chunk-4BMEZGHF-YbXkQXP1.js";import{_ as n,s as B,g as S,t as F,q as z,a as P,b as W,F as x,K as T,e as D,z as _,G as A,H as E,l as v}from"./mermaid-vendor-Bq4ke0CV.js";import{p as N}from"./radar-MK3ICKWK-DShnJK9k.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-Cz3Y9685.js";import"./_basePickBy-CeKYG_LI.js";import"./clone-CpYqNYda.js";var C={packet:[]},h=structuredClone(C),L=E.packet,Y=n(()=>{const t=x({...L,...A().packet});return t.showBits&&(t.paddingY+=10),t},"getConfig"),G=n(()=>h.packet,"getPacket"),H=n(t=>{t.length>0&&h.packet.push(t)},"pushWord"),I=n(()=>{_(),h=structuredClone(C)},"clear"),m={pushWord:H,getPacket:G,getConfig:Y,clear:I,setAccTitle:W,getAccTitle:P,setDiagramTitle:z,getDiagramTitle:F,getAccDescription:S,setAccDescription:B},K=1e4,M=n(t=>{w(t,m);let e=-1,o=[],s=1;const{bitsPerRow:i}=m.getConfig();for(let{start:a,end:r,label:p}of t.blocks){if(r&&r<a)throw new Error(`Packet block ${a} - ${r} is invalid. End must be greater than start.`);if(a!==e+1)throw new Error(`Packet block ${a} - ${r??a} is not contiguous. It should start from ${e+1}.`);for(e=r??a,v.debug(`Packet block ${a} - ${e} with label ${p}`);o.length<=i+1&&m.getPacket().length<K;){const[b,c]=O({start:a,end:r,label:p},s,i);if(o.push(b),b.end+1===s*i&&(m.pushWord(o),o=[],s++),!c)break;({start:a,end:r,label:p}=c)}}m.pushWord(o)},"populate"),O=n((t,e,o)=>{if(t.end===void 0&&(t.end=t.start),t.start>t.end)throw new Error(`Block start ${t.start} is greater than block end ${t.end}.`);return t.end+1<=e*o?[t,void 0]:[{start:t.start,end:e*o-1,label:t.label},{start:e*o,end:t.end,label:t.label}]},"getNextFittingBlock"),q={parse:n(async t=>{const e=await N("packet",t);v.debug(e),M(e)},"parse")},R=n((t,e,o,s)=>{const i=s.db,a=i.getConfig(),{rowHeight:r,paddingY:p,bitWidth:b,bitsPerRow:c}=a,u=i.getPacket(),l=i.getDiagramTitle(),g=r+p,d=g*(u.length+1)-(l?0:r),k=b*c+2,f=T(e);f.attr("viewbox",`0 0 ${k} ${d}`),D(f,d,k,a.useMaxWidth);for(const[$,y]of u.entries())U(f,y,$,a);f.append("text").text(l).attr("x",k/2).attr("y",d-g/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),U=n((t,e,o,{rowHeight:s,paddingX:i,paddingY:a,bitWidth:r,bitsPerRow:p,showBits:b})=>{const c=t.append("g"),u=o*(s+a)+a;for(const l of e){const g=l.start%p*r+1,d=(l.end-l.start+1)*r-i;if(c.append("rect").attr("x",g).attr("y",u).attr("width",d).attr("height",s).attr("class","packetBlock"),c.append("text").attr("x",g+d/2).attr("y",u+s/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(l.label),!b)continue;const k=l.end===l.start,f=u-2;c.append("text").attr("x",g+(k?d/2:0)).attr("y",f).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",k?"middle":"start").text(l.start),k||c.append("text").attr("x",g+d).attr("y",f).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(l.end)}},"drawWord"),X={draw:R},j={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},J=n(({packet:t}={})=>{const e=x(j,t);return`
|
||||
.packetByte {
|
||||
font-size: ${e.byteFontSize};
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
87
lightrag/api/webui/assets/feature-documents-D6SbQ2IV.js
generated
Normal file
87
lightrag/api/webui/assets/feature-documents-D6SbQ2IV.js
generated
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10
lightrag/api/webui/assets/feature-retrieval-I90szjD5.js
generated
Normal file
10
lightrag/api/webui/assets/feature-retrieval-I90szjD5.js
generated
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
import{_ as m,o as O1,l as Z,c as Ge,d as Ce,p as H1,r as q1,u as i1,b as X1,s as Q1,q as J1,a as Z1,g as $1,t as et,k as tt,v as st,J as it,x as rt,y as s1,z as nt,A as at,B as ut,C as lt}from"./mermaid-vendor-Bi3TzHdn.js";import{g as ot,s as ct}from"./chunk-RZ5BOZE2-DCsogQCZ.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var ht="flowchart-",Pe,dt=(Pe=class{constructor(){this.vertexCounter=0,this.config=Ge(),this.vertices=new Map,this.edges=[],this.classes=new Map,this.subGraphs=[],this.subGraphLookup=new Map,this.tooltips=new Map,this.subCount=0,this.firstGraphFlag=!0,this.secCount=-1,this.posCrossRef=[],this.funs=[],this.setAccTitle=X1,this.setAccDescription=Q1,this.setDiagramTitle=J1,this.getAccTitle=Z1,this.getAccDescription=$1,this.getDiagramTitle=et,this.funs.push(this.setupToolTips.bind(this)),this.addVertex=this.addVertex.bind(this),this.firstGraph=this.firstGraph.bind(this),this.setDirection=this.setDirection.bind(this),this.addSubGraph=this.addSubGraph.bind(this),this.addLink=this.addLink.bind(this),this.setLink=this.setLink.bind(this),this.updateLink=this.updateLink.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.destructLink=this.destructLink.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setTooltip=this.setTooltip.bind(this),this.updateLinkInterpolate=this.updateLinkInterpolate.bind(this),this.setClickFun=this.setClickFun.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.lex={firstGraph:this.firstGraph.bind(this)},this.clear(),this.setGen("gen-2")}sanitizeText(i){return tt.sanitizeText(i,this.config)}lookUpDomId(i){for(const n of this.vertices.values())if(n.id===i)return n.domId;return i}addVertex(i,n,a,u,l,f,c={},A){var U,T;if(!i||i.trim().length===0)return;let r;if(A!==void 0){let d;A.includes(`
|
||||
import{_ as m,o as O1,l as Z,c as Ge,d as Ce,p as H1,r as q1,u as i1,b as X1,s as Q1,q as J1,a as Z1,g as $1,t as et,k as tt,v as st,J as it,x as rt,y as s1,z as nt,A as at,B as ut,C as lt}from"./mermaid-vendor-Bq4ke0CV.js";import{g as ot,s as ct}from"./chunk-RZ5BOZE2-DujSvYNw.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var ht="flowchart-",Pe,dt=(Pe=class{constructor(){this.vertexCounter=0,this.config=Ge(),this.vertices=new Map,this.edges=[],this.classes=new Map,this.subGraphs=[],this.subGraphLookup=new Map,this.tooltips=new Map,this.subCount=0,this.firstGraphFlag=!0,this.secCount=-1,this.posCrossRef=[],this.funs=[],this.setAccTitle=X1,this.setAccDescription=Q1,this.setDiagramTitle=J1,this.getAccTitle=Z1,this.getAccDescription=$1,this.getDiagramTitle=et,this.funs.push(this.setupToolTips.bind(this)),this.addVertex=this.addVertex.bind(this),this.firstGraph=this.firstGraph.bind(this),this.setDirection=this.setDirection.bind(this),this.addSubGraph=this.addSubGraph.bind(this),this.addLink=this.addLink.bind(this),this.setLink=this.setLink.bind(this),this.updateLink=this.updateLink.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.destructLink=this.destructLink.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setTooltip=this.setTooltip.bind(this),this.updateLinkInterpolate=this.updateLinkInterpolate.bind(this),this.setClickFun=this.setClickFun.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.lex={firstGraph:this.firstGraph.bind(this)},this.clear(),this.setGen("gen-2")}sanitizeText(i){return tt.sanitizeText(i,this.config)}lookUpDomId(i){for(const n of this.vertices.values())if(n.id===i)return n.domId;return i}addVertex(i,n,a,u,l,f,c={},A){var U,T;if(!i||i.trim().length===0)return;let r;if(A!==void 0){let d;A.includes(`
|
||||
`)?d=A+`
|
||||
`:d=`{
|
||||
`+A+`
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +1,2 @@
|
|||
import{_ as e,l as o,K as i,e as n,L as p}from"./mermaid-vendor-Bi3TzHdn.js";import{p as m}from"./radar-MK3ICKWK-CxtTBuu1.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-CvCjC6qE.js";import"./_basePickBy-DY0K0qir.js";import"./clone-CFB5BEXb.js";var g={parse:e(async r=>{const a=await m("info",r);o.debug(a)},"parse")},v={version:p.version},d=e(()=>v.version,"getVersion"),c={getVersion:d},l=e((r,a,s)=>{o.debug(`rendering info diagram
|
||||
import{_ as e,l as o,K as i,e as n,L as p}from"./mermaid-vendor-Bq4ke0CV.js";import{p as m}from"./radar-MK3ICKWK-DShnJK9k.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-Cz3Y9685.js";import"./_basePickBy-CeKYG_LI.js";import"./clone-CpYqNYda.js";var g={parse:e(async r=>{const a=await m("info",r);o.debug(a)},"parse")},v={version:p.version},d=e(()=>v.version,"getVersion"),c={getVersion:d},l=e((r,a,s)=>{o.debug(`rendering info diagram
|
||||
`+r);const t=i(a);n(t,100,400,!0),t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${s}`)},"draw"),f={draw:l},L={parser:g,db:c,renderer:f};export{L as diagram};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{a as pt,g as at,f as gt,d as mt}from"./chunk-D6G4REZN-Ct1-6Dc1.js";import{_ as s,g as xt,s as kt,a as _t,b as bt,t as vt,q as wt,c as A,d as W,e as Tt,z as St,N as tt}from"./mermaid-vendor-Bi3TzHdn.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var H=function(){var t=s(function(g,r,a,l){for(a=a||{},l=g.length;l--;a[g[l]]=r);return a},"o"),e=[6,8,10,11,12,14,16,17,18],i=[1,9],c=[1,10],n=[1,11],u=[1,12],h=[1,13],f=[1,14],d={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:s(function(r,a,l,y,p,o,S){var _=o.length-1;switch(p){case 1:return o[_-1];case 2:this.$=[];break;case 3:o[_-1].push(o[_]),this.$=o[_-1];break;case 4:case 5:this.$=o[_];break;case 6:case 7:this.$=[];break;case 8:y.setDiagramTitle(o[_].substr(6)),this.$=o[_].substr(6);break;case 9:this.$=o[_].trim(),y.setAccTitle(this.$);break;case 10:case 11:this.$=o[_].trim(),y.setAccDescription(this.$);break;case 12:y.addSection(o[_].substr(8)),this.$=o[_].substr(8);break;case 13:y.addTask(o[_-1],o[_]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:i,12:c,14:n,16:u,17:h,18:f},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:i,12:c,14:n,16:u,17:h,18:f},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:s(function(r,a){if(a.recoverable)this.trace(r);else{var l=new Error(r);throw l.hash=a,l}},"parseError"),parse:s(function(r){var a=this,l=[0],y=[],p=[null],o=[],S=this.table,_="",B=0,J=0,ut=2,K=1,yt=o.slice.call(arguments,1),k=Object.create(this.lexer),E={yy:{}};for(var O in this.yy)Object.prototype.hasOwnProperty.call(this.yy,O)&&(E.yy[O]=this.yy[O]);k.setInput(r,E.yy),E.yy.lexer=k,E.yy.parser=this,typeof k.yylloc>"u"&&(k.yylloc={});var Y=k.yylloc;o.push(Y);var dt=k.options&&k.options.ranges;typeof E.yy.parseError=="function"?this.parseError=E.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ft(v){l.length=l.length-2*v,p.length=p.length-v,o.length=o.length-v}s(ft,"popStack");function Q(){var v;return v=y.pop()||k.lex()||K,typeof v!="number"&&(v instanceof Array&&(y=v,v=y.pop()),v=a.symbols_[v]||v),v}s(Q,"lex");for(var b,P,w,q,C={},N,$,D,j;;){if(P=l[l.length-1],this.defaultActions[P]?w=this.defaultActions[P]:((b===null||typeof b>"u")&&(b=Q()),w=S[P]&&S[P][b]),typeof w>"u"||!w.length||!w[0]){var G="";j=[];for(N in S[P])this.terminals_[N]&&N>ut&&j.push("'"+this.terminals_[N]+"'");k.showPosition?G="Parse error on line "+(B+1)+`:
|
||||
import{a as pt,g as at,f as gt,d as mt}from"./chunk-D6G4REZN-CDoqv8o0.js";import{_ as s,g as xt,s as kt,a as _t,b as bt,t as vt,q as wt,c as A,d as W,e as Tt,z as St,N as tt}from"./mermaid-vendor-Bq4ke0CV.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var H=function(){var t=s(function(g,r,a,l){for(a=a||{},l=g.length;l--;a[g[l]]=r);return a},"o"),e=[6,8,10,11,12,14,16,17,18],i=[1,9],c=[1,10],n=[1,11],u=[1,12],h=[1,13],f=[1,14],d={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:s(function(r,a,l,y,p,o,S){var _=o.length-1;switch(p){case 1:return o[_-1];case 2:this.$=[];break;case 3:o[_-1].push(o[_]),this.$=o[_-1];break;case 4:case 5:this.$=o[_];break;case 6:case 7:this.$=[];break;case 8:y.setDiagramTitle(o[_].substr(6)),this.$=o[_].substr(6);break;case 9:this.$=o[_].trim(),y.setAccTitle(this.$);break;case 10:case 11:this.$=o[_].trim(),y.setAccDescription(this.$);break;case 12:y.addSection(o[_].substr(8)),this.$=o[_].substr(8);break;case 13:y.addTask(o[_-1],o[_]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:i,12:c,14:n,16:u,17:h,18:f},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:i,12:c,14:n,16:u,17:h,18:f},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:s(function(r,a){if(a.recoverable)this.trace(r);else{var l=new Error(r);throw l.hash=a,l}},"parseError"),parse:s(function(r){var a=this,l=[0],y=[],p=[null],o=[],S=this.table,_="",B=0,J=0,ut=2,K=1,yt=o.slice.call(arguments,1),k=Object.create(this.lexer),E={yy:{}};for(var O in this.yy)Object.prototype.hasOwnProperty.call(this.yy,O)&&(E.yy[O]=this.yy[O]);k.setInput(r,E.yy),E.yy.lexer=k,E.yy.parser=this,typeof k.yylloc>"u"&&(k.yylloc={});var Y=k.yylloc;o.push(Y);var dt=k.options&&k.options.ranges;typeof E.yy.parseError=="function"?this.parseError=E.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ft(v){l.length=l.length-2*v,p.length=p.length-v,o.length=o.length-v}s(ft,"popStack");function Q(){var v;return v=y.pop()||k.lex()||K,typeof v!="number"&&(v instanceof Array&&(y=v,v=y.pop()),v=a.symbols_[v]||v),v}s(Q,"lex");for(var b,P,w,q,C={},N,$,D,j;;){if(P=l[l.length-1],this.defaultActions[P]?w=this.defaultActions[P]:((b===null||typeof b>"u")&&(b=Q()),w=S[P]&&S[P][b]),typeof w>"u"||!w.length||!w[0]){var G="";j=[];for(N in S[P])this.terminals_[N]&&N>ut&&j.push("'"+this.terminals_[N]+"'");k.showPosition?G="Parse error on line "+(B+1)+`:
|
||||
`+k.showPosition()+`
|
||||
Expecting `+j.join(", ")+", got '"+(this.terminals_[b]||b)+"'":G="Parse error on line "+(B+1)+": Unexpected "+(b==K?"end of input":"'"+(this.terminals_[b]||b)+"'"),this.parseError(G,{text:k.match,token:this.terminals_[b]||b,line:k.yylineno,loc:Y,expected:j})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+P+", token: "+b);switch(w[0]){case 1:l.push(b),p.push(k.yytext),o.push(k.yylloc),l.push(w[1]),b=null,J=k.yyleng,_=k.yytext,B=k.yylineno,Y=k.yylloc;break;case 2:if($=this.productions_[w[1]][1],C.$=p[p.length-$],C._$={first_line:o[o.length-($||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-($||1)].first_column,last_column:o[o.length-1].last_column},dt&&(C._$.range=[o[o.length-($||1)].range[0],o[o.length-1].range[1]]),q=this.performAction.apply(C,[_,J,B,E.yy,w[1],p,o].concat(yt)),typeof q<"u")return q;$&&(l=l.slice(0,-1*$*2),p=p.slice(0,-1*$),o=o.slice(0,-1*$)),l.push(this.productions_[w[1]][0]),p.push(C.$),o.push(C._$),D=S[l[l.length-2]][l[l.length-1]],l.push(D);break;case 3:return!0}}return!0},"parse")},x=function(){var g={EOF:1,parseError:s(function(a,l){if(this.yy.parser)this.yy.parser.parseError(a,l);else throw new Error(a)},"parseError"),setInput:s(function(r,a){return this.yy=a||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:s(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var a=r.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:s(function(r){var a=r.length,l=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var y=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===y.length?this.yylloc.first_column:0)+y[y.length-l.length].length-l[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:s(function(){return this._more=!0,this},"more"),reject:s(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
|
||||
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:s(function(r){this.unput(this.match.slice(r))},"less"),pastInput:s(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:s(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:s(function(){var r=this.pastInput(),a=new Array(r.length+1).join("-");return r+this.upcomingInput()+`
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{_ as c,l as te,c as W,K as fe,a7 as ye,a8 as be,a9 as me,a2 as _e,H as Y,i as G,v as Ee,J as ke,a3 as Se,a4 as le,a5 as ce}from"./mermaid-vendor-Bi3TzHdn.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var $=function(){var t=c(function(_,i,n,a){for(n=n||{},a=_.length;a--;n[_[a]]=i);return n},"o"),g=[1,4],d=[1,13],r=[1,12],p=[1,15],E=[1,16],f=[1,20],h=[1,19],L=[6,7,8],C=[1,26],w=[1,24],N=[1,25],s=[6,7,11],H=[1,31],x=[6,7,11,24],P=[1,6,13,16,17,20,23],M=[1,35],U=[1,36],A=[1,6,7,11,13,16,17,20,23],j=[1,38],V={trace:c(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,KANBAN:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,shapeData:15,ICON:16,CLASS:17,nodeWithId:18,nodeWithoutId:19,NODE_DSTART:20,NODE_DESCR:21,NODE_DEND:22,NODE_ID:23,SHAPE_DATA:24,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"KANBAN",11:"EOF",13:"SPACELIST",16:"ICON",17:"CLASS",20:"NODE_DSTART",21:"NODE_DESCR",22:"NODE_DEND",23:"NODE_ID",24:"SHAPE_DATA"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,3],[12,2],[12,2],[12,2],[12,1],[12,2],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[19,3],[18,1],[18,4],[15,2],[15,1]],performAction:c(function(i,n,a,o,u,e,B){var l=e.length-1;switch(u){case 6:case 7:return o;case 8:o.getLogger().trace("Stop NL ");break;case 9:o.getLogger().trace("Stop EOF ");break;case 11:o.getLogger().trace("Stop NL2 ");break;case 12:o.getLogger().trace("Stop EOF2 ");break;case 15:o.getLogger().info("Node: ",e[l-1].id),o.addNode(e[l-2].length,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 16:o.getLogger().info("Node: ",e[l].id),o.addNode(e[l-1].length,e[l].id,e[l].descr,e[l].type);break;case 17:o.getLogger().trace("Icon: ",e[l]),o.decorateNode({icon:e[l]});break;case 18:case 23:o.decorateNode({class:e[l]});break;case 19:o.getLogger().trace("SPACELIST");break;case 20:o.getLogger().trace("Node: ",e[l-1].id),o.addNode(0,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 21:o.getLogger().trace("Node: ",e[l].id),o.addNode(0,e[l].id,e[l].descr,e[l].type);break;case 22:o.decorateNode({icon:e[l]});break;case 27:o.getLogger().trace("node found ..",e[l-2]),this.$={id:e[l-1],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 28:this.$={id:e[l],descr:e[l],type:0};break;case 29:o.getLogger().trace("node found ..",e[l-3]),this.$={id:e[l-3],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 30:this.$=e[l-1]+e[l];break;case 31:this.$=e[l];break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:g},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:g},{6:d,7:[1,10],9:9,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(L,[2,3]),{1:[2,2]},t(L,[2,4]),t(L,[2,5]),{1:[2,6],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:d,9:22,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:C,7:w,10:23,11:N},t(s,[2,24],{18:17,19:18,14:27,16:[1,28],17:[1,29],20:f,23:h}),t(s,[2,19]),t(s,[2,21],{15:30,24:H}),t(s,[2,22]),t(s,[2,23]),t(x,[2,25]),t(x,[2,26]),t(x,[2,28],{20:[1,32]}),{21:[1,33]},{6:C,7:w,10:34,11:N},{1:[2,7],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(P,[2,14],{7:M,11:U}),t(A,[2,8]),t(A,[2,9]),t(A,[2,10]),t(s,[2,16],{15:37,24:H}),t(s,[2,17]),t(s,[2,18]),t(s,[2,20],{24:j}),t(x,[2,31]),{21:[1,39]},{22:[1,40]},t(P,[2,13],{7:M,11:U}),t(A,[2,11]),t(A,[2,12]),t(s,[2,15],{24:j}),t(x,[2,30]),{22:[1,41]},t(x,[2,27]),t(x,[2,29])],defaultActions:{2:[2,1],6:[2,2]},parseError:c(function(i,n){if(n.recoverable)this.trace(i);else{var a=new Error(i);throw a.hash=n,a}},"parseError"),parse:c(function(i){var n=this,a=[0],o=[],u=[null],e=[],B=this.table,l="",z=0,se=0,ue=2,re=1,ge=e.slice.call(arguments,1),b=Object.create(this.lexer),T={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(T.yy[J]=this.yy[J]);b.setInput(i,T.yy),T.yy.lexer=b,T.yy.parser=this,typeof b.yylloc>"u"&&(b.yylloc={});var q=b.yylloc;e.push(q);var de=b.options&&b.options.ranges;typeof T.yy.parseError=="function"?this.parseError=T.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pe(S){a.length=a.length-2*S,u.length=u.length-S,e.length=e.length-S}c(pe,"popStack");function ae(){var S;return S=o.pop()||b.lex()||re,typeof S!="number"&&(S instanceof Array&&(o=S,S=o.pop()),S=n.symbols_[S]||S),S}c(ae,"lex");for(var k,R,v,Q,F={},K,I,oe,X;;){if(R=a[a.length-1],this.defaultActions[R]?v=this.defaultActions[R]:((k===null||typeof k>"u")&&(k=ae()),v=B[R]&&B[R][k]),typeof v>"u"||!v.length||!v[0]){var Z="";X=[];for(K in B[R])this.terminals_[K]&&K>ue&&X.push("'"+this.terminals_[K]+"'");b.showPosition?Z="Parse error on line "+(z+1)+`:
|
||||
import{_ as c,l as te,c as W,K as fe,a7 as ye,a8 as be,a9 as me,a2 as _e,H as Y,i as G,v as Ee,J as ke,a3 as Se,a4 as le,a5 as ce}from"./mermaid-vendor-Bq4ke0CV.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var $=function(){var t=c(function(_,i,n,a){for(n=n||{},a=_.length;a--;n[_[a]]=i);return n},"o"),g=[1,4],d=[1,13],r=[1,12],p=[1,15],E=[1,16],f=[1,20],h=[1,19],L=[6,7,8],C=[1,26],w=[1,24],N=[1,25],s=[6,7,11],H=[1,31],x=[6,7,11,24],P=[1,6,13,16,17,20,23],M=[1,35],U=[1,36],A=[1,6,7,11,13,16,17,20,23],j=[1,38],V={trace:c(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,KANBAN:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,shapeData:15,ICON:16,CLASS:17,nodeWithId:18,nodeWithoutId:19,NODE_DSTART:20,NODE_DESCR:21,NODE_DEND:22,NODE_ID:23,SHAPE_DATA:24,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"KANBAN",11:"EOF",13:"SPACELIST",16:"ICON",17:"CLASS",20:"NODE_DSTART",21:"NODE_DESCR",22:"NODE_DEND",23:"NODE_ID",24:"SHAPE_DATA"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,3],[12,2],[12,2],[12,2],[12,1],[12,2],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[19,3],[18,1],[18,4],[15,2],[15,1]],performAction:c(function(i,n,a,o,u,e,B){var l=e.length-1;switch(u){case 6:case 7:return o;case 8:o.getLogger().trace("Stop NL ");break;case 9:o.getLogger().trace("Stop EOF ");break;case 11:o.getLogger().trace("Stop NL2 ");break;case 12:o.getLogger().trace("Stop EOF2 ");break;case 15:o.getLogger().info("Node: ",e[l-1].id),o.addNode(e[l-2].length,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 16:o.getLogger().info("Node: ",e[l].id),o.addNode(e[l-1].length,e[l].id,e[l].descr,e[l].type);break;case 17:o.getLogger().trace("Icon: ",e[l]),o.decorateNode({icon:e[l]});break;case 18:case 23:o.decorateNode({class:e[l]});break;case 19:o.getLogger().trace("SPACELIST");break;case 20:o.getLogger().trace("Node: ",e[l-1].id),o.addNode(0,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 21:o.getLogger().trace("Node: ",e[l].id),o.addNode(0,e[l].id,e[l].descr,e[l].type);break;case 22:o.decorateNode({icon:e[l]});break;case 27:o.getLogger().trace("node found ..",e[l-2]),this.$={id:e[l-1],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 28:this.$={id:e[l],descr:e[l],type:0};break;case 29:o.getLogger().trace("node found ..",e[l-3]),this.$={id:e[l-3],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 30:this.$=e[l-1]+e[l];break;case 31:this.$=e[l];break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:g},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:g},{6:d,7:[1,10],9:9,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(L,[2,3]),{1:[2,2]},t(L,[2,4]),t(L,[2,5]),{1:[2,6],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:d,9:22,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:C,7:w,10:23,11:N},t(s,[2,24],{18:17,19:18,14:27,16:[1,28],17:[1,29],20:f,23:h}),t(s,[2,19]),t(s,[2,21],{15:30,24:H}),t(s,[2,22]),t(s,[2,23]),t(x,[2,25]),t(x,[2,26]),t(x,[2,28],{20:[1,32]}),{21:[1,33]},{6:C,7:w,10:34,11:N},{1:[2,7],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(P,[2,14],{7:M,11:U}),t(A,[2,8]),t(A,[2,9]),t(A,[2,10]),t(s,[2,16],{15:37,24:H}),t(s,[2,17]),t(s,[2,18]),t(s,[2,20],{24:j}),t(x,[2,31]),{21:[1,39]},{22:[1,40]},t(P,[2,13],{7:M,11:U}),t(A,[2,11]),t(A,[2,12]),t(s,[2,15],{24:j}),t(x,[2,30]),{22:[1,41]},t(x,[2,27]),t(x,[2,29])],defaultActions:{2:[2,1],6:[2,2]},parseError:c(function(i,n){if(n.recoverable)this.trace(i);else{var a=new Error(i);throw a.hash=n,a}},"parseError"),parse:c(function(i){var n=this,a=[0],o=[],u=[null],e=[],B=this.table,l="",z=0,se=0,ue=2,re=1,ge=e.slice.call(arguments,1),b=Object.create(this.lexer),T={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(T.yy[J]=this.yy[J]);b.setInput(i,T.yy),T.yy.lexer=b,T.yy.parser=this,typeof b.yylloc>"u"&&(b.yylloc={});var q=b.yylloc;e.push(q);var de=b.options&&b.options.ranges;typeof T.yy.parseError=="function"?this.parseError=T.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pe(S){a.length=a.length-2*S,u.length=u.length-S,e.length=e.length-S}c(pe,"popStack");function ae(){var S;return S=o.pop()||b.lex()||re,typeof S!="number"&&(S instanceof Array&&(o=S,S=o.pop()),S=n.symbols_[S]||S),S}c(ae,"lex");for(var k,R,v,Q,F={},K,I,oe,X;;){if(R=a[a.length-1],this.defaultActions[R]?v=this.defaultActions[R]:((k===null||typeof k>"u")&&(k=ae()),v=B[R]&&B[R][k]),typeof v>"u"||!v.length||!v[0]){var Z="";X=[];for(K in B[R])this.terminals_[K]&&K>ue&&X.push("'"+this.terminals_[K]+"'");b.showPosition?Z="Parse error on line "+(z+1)+`:
|
||||
`+b.showPosition()+`
|
||||
Expecting `+X.join(", ")+", got '"+(this.terminals_[k]||k)+"'":Z="Parse error on line "+(z+1)+": Unexpected "+(k==re?"end of input":"'"+(this.terminals_[k]||k)+"'"),this.parseError(Z,{text:b.match,token:this.terminals_[k]||k,line:b.yylineno,loc:q,expected:X})}if(v[0]instanceof Array&&v.length>1)throw new Error("Parse Error: multiple actions possible at state: "+R+", token: "+k);switch(v[0]){case 1:a.push(k),u.push(b.yytext),e.push(b.yylloc),a.push(v[1]),k=null,se=b.yyleng,l=b.yytext,z=b.yylineno,q=b.yylloc;break;case 2:if(I=this.productions_[v[1]][1],F.$=u[u.length-I],F._$={first_line:e[e.length-(I||1)].first_line,last_line:e[e.length-1].last_line,first_column:e[e.length-(I||1)].first_column,last_column:e[e.length-1].last_column},de&&(F._$.range=[e[e.length-(I||1)].range[0],e[e.length-1].range[1]]),Q=this.performAction.apply(F,[l,se,z,T.yy,v[1],u,e].concat(ge)),typeof Q<"u")return Q;I&&(a=a.slice(0,-1*I*2),u=u.slice(0,-1*I),e=e.slice(0,-1*I)),a.push(this.productions_[v[1]][0]),u.push(F.$),e.push(F._$),oe=B[a[a.length-2]][a[a.length-1]],a.push(oe);break;case 3:return!0}}return!0},"parse")},m=function(){var _={EOF:1,parseError:c(function(n,a){if(this.yy.parser)this.yy.parser.parseError(n,a);else throw new Error(n)},"parseError"),setInput:c(function(i,n){return this.yy=n||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:c(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var n=i.match(/(?:\r\n?|\n).*/g);return n?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:c(function(i){var n=i.length,a=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-n),this.offset-=n;var o=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),a.length-1&&(this.yylineno-=a.length-1);var u=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:a?(a.length===o.length?this.yylloc.first_column:0)+o[o.length-a.length].length-a[0].length:this.yylloc.first_column-n},this.options.ranges&&(this.yylloc.range=[u[0],u[0]+this.yyleng-n]),this.yyleng=this.yytext.length,this},"unput"),more:c(function(){return this._more=!0,this},"more"),reject:c(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
|
||||
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:c(function(i){this.unput(this.match.slice(i))},"less"),pastInput:c(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:c(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:c(function(){var i=this.pastInput(),n=new Array(i.length+1).join("-");return i+this.upcomingInput()+`
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
import{p as N}from"./chunk-4BMEZGHF-BSIbM2yC.js";import{_ as i,g as B,s as U,a as q,b as H,t as K,q as V,l as C,c as Z,F as j,K as J,M as Q,N as z,O as X,e as Y,z as tt,P as et,H as at}from"./mermaid-vendor-Bi3TzHdn.js";import{p as rt}from"./radar-MK3ICKWK-CxtTBuu1.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-CvCjC6qE.js";import"./_basePickBy-DY0K0qir.js";import"./clone-CFB5BEXb.js";var it=at.pie,D={sections:new Map,showData:!1},f=D.sections,w=D.showData,st=structuredClone(it),ot=i(()=>structuredClone(st),"getConfig"),nt=i(()=>{f=new Map,w=D.showData,tt()},"clear"),lt=i(({label:t,value:a})=>{f.has(t)||(f.set(t,a),C.debug(`added new section: ${t}, with value: ${a}`))},"addSection"),ct=i(()=>f,"getSections"),pt=i(t=>{w=t},"setShowData"),dt=i(()=>w,"getShowData"),F={getConfig:ot,clear:nt,setDiagramTitle:V,getDiagramTitle:K,setAccTitle:H,getAccTitle:q,setAccDescription:U,getAccDescription:B,addSection:lt,getSections:ct,setShowData:pt,getShowData:dt},gt=i((t,a)=>{N(t,a),a.setShowData(t.showData),t.sections.map(a.addSection)},"populateDb"),ut={parse:i(async t=>{const a=await rt("pie",t);C.debug(a),gt(a,F)},"parse")},mt=i(t=>`
|
||||
import{p as N}from"./chunk-4BMEZGHF-YbXkQXP1.js";import{_ as i,g as B,s as U,a as q,b as H,t as K,q as V,l as C,c as Z,F as j,K as J,M as Q,N as z,O as X,e as Y,z as tt,P as et,H as at}from"./mermaid-vendor-Bq4ke0CV.js";import{p as rt}from"./radar-MK3ICKWK-DShnJK9k.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-Cz3Y9685.js";import"./_basePickBy-CeKYG_LI.js";import"./clone-CpYqNYda.js";var it=at.pie,D={sections:new Map,showData:!1},f=D.sections,w=D.showData,st=structuredClone(it),ot=i(()=>structuredClone(st),"getConfig"),nt=i(()=>{f=new Map,w=D.showData,tt()},"clear"),lt=i(({label:t,value:a})=>{f.has(t)||(f.set(t,a),C.debug(`added new section: ${t}, with value: ${a}`))},"addSection"),ct=i(()=>f,"getSections"),pt=i(t=>{w=t},"setShowData"),dt=i(()=>w,"getShowData"),F={getConfig:ot,clear:nt,setDiagramTitle:V,getDiagramTitle:K,setAccTitle:H,getAccTitle:q,setAccDescription:U,getAccDescription:B,addSection:lt,getSections:ct,setShowData:pt,getShowData:dt},gt=i((t,a)=>{N(t,a),a.setShowData(t.showData),t.sections.map(a.addSection)},"populateDb"),ut={parse:i(async t=>{const a=await rt("pie",t);C.debug(a),gt(a,F)},"parse")},mt=i(t=>`
|
||||
.pieCircle{
|
||||
stroke: ${t.pieStrokeColor};
|
||||
stroke-width : ${t.pieStrokeWidth};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{s as r,b as e,a,S as s}from"./chunk-AEK57VVT-DDDSE95R.js";import{_ as i}from"./mermaid-vendor-Bi3TzHdn.js";import"./chunk-RZ5BOZE2-DCsogQCZ.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var b={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{b as diagram};
|
||||
import{s as r,b as e,a,S as s}from"./chunk-AEK57VVT-DBGH5mHa.js";import{_ as i}from"./mermaid-vendor-Bq4ke0CV.js";import"./chunk-RZ5BOZE2-DujSvYNw.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var b={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{b as diagram};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{_ as s,c as xt,l as T,d as q,a2 as kt,a3 as _t,a4 as bt,a5 as vt,N as nt,D as wt,a6 as St,z as Et}from"./mermaid-vendor-Bi3TzHdn.js";import"./feature-graph-ChwGSzXI.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var X=function(){var n=s(function(f,i,a,d){for(a=a||{},d=f.length;d--;a[f[d]]=i);return a},"o"),t=[6,8,10,11,12,14,16,17,20,21],e=[1,9],l=[1,10],r=[1,11],h=[1,12],c=[1,13],g=[1,16],m=[1,17],p={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:s(function(i,a,d,u,y,o,S){var k=o.length-1;switch(y){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:u.getCommonDb().setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),u.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),u.getCommonDb().setAccDescription(this.$);break;case 12:u.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 15:u.addTask(o[k],0,""),this.$=o[k];break;case 16:u.addEvent(o[k].substr(2)),this.$=o[k];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},n(t,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:e,12:l,14:r,16:h,17:c,18:14,19:15,20:g,21:m},n(t,[2,7],{1:[2,1]}),n(t,[2,3]),{9:18,11:e,12:l,14:r,16:h,17:c,18:14,19:15,20:g,21:m},n(t,[2,5]),n(t,[2,6]),n(t,[2,8]),{13:[1,19]},{15:[1,20]},n(t,[2,11]),n(t,[2,12]),n(t,[2,13]),n(t,[2,14]),n(t,[2,15]),n(t,[2,16]),n(t,[2,4]),n(t,[2,9]),n(t,[2,10])],defaultActions:{},parseError:s(function(i,a){if(a.recoverable)this.trace(i);else{var d=new Error(i);throw d.hash=a,d}},"parseError"),parse:s(function(i){var a=this,d=[0],u=[],y=[null],o=[],S=this.table,k="",M=0,P=0,B=2,J=1,O=o.slice.call(arguments,1),_=Object.create(this.lexer),E={yy:{}};for(var v in this.yy)Object.prototype.hasOwnProperty.call(this.yy,v)&&(E.yy[v]=this.yy[v]);_.setInput(i,E.yy),E.yy.lexer=_,E.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var L=_.yylloc;o.push(L);var A=_.options&&_.options.ranges;typeof E.yy.parseError=="function"?this.parseError=E.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function R(I){d.length=d.length-2*I,y.length=y.length-I,o.length=o.length-I}s(R,"popStack");function z(){var I;return I=u.pop()||_.lex()||J,typeof I!="number"&&(I instanceof Array&&(u=I,I=u.pop()),I=a.symbols_[I]||I),I}s(z,"lex");for(var w,C,N,K,F={},j,$,et,G;;){if(C=d[d.length-1],this.defaultActions[C]?N=this.defaultActions[C]:((w===null||typeof w>"u")&&(w=z()),N=S[C]&&S[C][w]),typeof N>"u"||!N.length||!N[0]){var Q="";G=[];for(j in S[C])this.terminals_[j]&&j>B&&G.push("'"+this.terminals_[j]+"'");_.showPosition?Q="Parse error on line "+(M+1)+`:
|
||||
import{_ as s,c as xt,l as T,d as q,a2 as kt,a3 as _t,a4 as bt,a5 as vt,N as nt,D as wt,a6 as St,z as Et}from"./mermaid-vendor-Bq4ke0CV.js";import"./feature-graph-BDBcJUbn.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var X=function(){var n=s(function(f,i,a,d){for(a=a||{},d=f.length;d--;a[f[d]]=i);return a},"o"),t=[6,8,10,11,12,14,16,17,20,21],e=[1,9],l=[1,10],r=[1,11],h=[1,12],c=[1,13],g=[1,16],m=[1,17],p={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:s(function(i,a,d,u,y,o,S){var k=o.length-1;switch(y){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:u.getCommonDb().setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),u.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),u.getCommonDb().setAccDescription(this.$);break;case 12:u.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 15:u.addTask(o[k],0,""),this.$=o[k];break;case 16:u.addEvent(o[k].substr(2)),this.$=o[k];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},n(t,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:e,12:l,14:r,16:h,17:c,18:14,19:15,20:g,21:m},n(t,[2,7],{1:[2,1]}),n(t,[2,3]),{9:18,11:e,12:l,14:r,16:h,17:c,18:14,19:15,20:g,21:m},n(t,[2,5]),n(t,[2,6]),n(t,[2,8]),{13:[1,19]},{15:[1,20]},n(t,[2,11]),n(t,[2,12]),n(t,[2,13]),n(t,[2,14]),n(t,[2,15]),n(t,[2,16]),n(t,[2,4]),n(t,[2,9]),n(t,[2,10])],defaultActions:{},parseError:s(function(i,a){if(a.recoverable)this.trace(i);else{var d=new Error(i);throw d.hash=a,d}},"parseError"),parse:s(function(i){var a=this,d=[0],u=[],y=[null],o=[],S=this.table,k="",M=0,P=0,B=2,J=1,O=o.slice.call(arguments,1),_=Object.create(this.lexer),E={yy:{}};for(var v in this.yy)Object.prototype.hasOwnProperty.call(this.yy,v)&&(E.yy[v]=this.yy[v]);_.setInput(i,E.yy),E.yy.lexer=_,E.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var L=_.yylloc;o.push(L);var A=_.options&&_.options.ranges;typeof E.yy.parseError=="function"?this.parseError=E.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function R(I){d.length=d.length-2*I,y.length=y.length-I,o.length=o.length-I}s(R,"popStack");function z(){var I;return I=u.pop()||_.lex()||J,typeof I!="number"&&(I instanceof Array&&(u=I,I=u.pop()),I=a.symbols_[I]||I),I}s(z,"lex");for(var w,C,N,K,F={},j,$,et,G;;){if(C=d[d.length-1],this.defaultActions[C]?N=this.defaultActions[C]:((w===null||typeof w>"u")&&(w=z()),N=S[C]&&S[C][w]),typeof N>"u"||!N.length||!N[0]){var Q="";G=[];for(j in S[C])this.terminals_[j]&&j>B&&G.push("'"+this.terminals_[j]+"'");_.showPosition?Q="Parse error on line "+(M+1)+`:
|
||||
`+_.showPosition()+`
|
||||
Expecting `+G.join(", ")+", got '"+(this.terminals_[w]||w)+"'":Q="Parse error on line "+(M+1)+": Unexpected "+(w==J?"end of input":"'"+(this.terminals_[w]||w)+"'"),this.parseError(Q,{text:_.match,token:this.terminals_[w]||w,line:_.yylineno,loc:L,expected:G})}if(N[0]instanceof Array&&N.length>1)throw new Error("Parse Error: multiple actions possible at state: "+C+", token: "+w);switch(N[0]){case 1:d.push(w),y.push(_.yytext),o.push(_.yylloc),d.push(N[1]),w=null,P=_.yyleng,k=_.yytext,M=_.yylineno,L=_.yylloc;break;case 2:if($=this.productions_[N[1]][1],F.$=y[y.length-$],F._$={first_line:o[o.length-($||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-($||1)].first_column,last_column:o[o.length-1].last_column},A&&(F._$.range=[o[o.length-($||1)].range[0],o[o.length-1].range[1]]),K=this.performAction.apply(F,[k,P,M,E.yy,N[1],y,o].concat(O)),typeof K<"u")return K;$&&(d=d.slice(0,-1*$*2),y=y.slice(0,-1*$),o=o.slice(0,-1*$)),d.push(this.productions_[N[1]][0]),y.push(F.$),o.push(F._$),et=S[d[d.length-2]][d[d.length-1]],d.push(et);break;case 3:return!0}}return!0},"parse")},x=function(){var f={EOF:1,parseError:s(function(a,d){if(this.yy.parser)this.yy.parser.parseError(a,d);else throw new Error(a)},"parseError"),setInput:s(function(i,a){return this.yy=a||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:s(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var a=i.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:s(function(i){var a=i.length,d=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var u=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),d.length-1&&(this.yylineno-=d.length-1);var y=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:d?(d.length===u.length?this.yylloc.first_column:0)+u[u.length-d.length].length-d[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[y[0],y[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:s(function(){return this._more=!0,this},"more"),reject:s(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
|
||||
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:s(function(i){this.unput(this.match.slice(i))},"less"),pastInput:s(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:s(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:s(function(){var i=this.pastInput(),a=new Array(i.length+1).join("-");return i+this.upcomingInput()+`
|
||||
File diff suppressed because one or more lines are too long
10
lightrag/api/webui/index.html
generated
10
lightrag/api/webui/index.html
generated
|
|
@ -8,16 +8,16 @@
|
|||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lightrag</title>
|
||||
<script type="module" crossorigin src="/webui/assets/index-BSksJLxv.js"></script>
|
||||
<script type="module" crossorigin src="/webui/assets/index-Dw-8_k1o.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/react-vendor-DEwriMA6.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/ui-vendor-CeCm8EER.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/graph-vendor-B-X5JegA.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/utils-vendor-BysuhMZA.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-graph-ChwGSzXI.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/mermaid-vendor-Bi3TzHdn.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-graph-BDBcJUbn.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-documents-D6SbQ2IV.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/mermaid-vendor-Bq4ke0CV.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/markdown-vendor-DmIvJdn7.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-retrieval-BebE__YQ.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-documents-DFri5mmM.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-retrieval-I90szjD5.js">
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/feature-graph-BipNuM18.css">
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-DwO2XWaU.css">
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -673,6 +673,36 @@ class DocStatusStorage(BaseKVStorage, ABC):
|
|||
) -> dict[str, DocProcessingStatus]:
|
||||
"""Get all documents with a specific track_id"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_docs_paginated(
|
||||
self,
|
||||
status_filter: DocStatus | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
sort_field: str = "updated_at",
|
||||
sort_direction: str = "desc",
|
||||
) -> tuple[list[tuple[str, DocProcessingStatus]], int]:
|
||||
"""Get documents with pagination support
|
||||
|
||||
Args:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', 'id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
|
||||
Returns:
|
||||
Tuple of (list of (doc_id, DocProcessingStatus) tuples, total_count)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_all_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status for all documents
|
||||
|
||||
Returns:
|
||||
Dictionary mapping status names to counts
|
||||
"""
|
||||
|
||||
async def drop_cache_by_modes(self, modes: list[str] | None = None) -> bool:
|
||||
"""Drop cache is not supported for Doc Status storage"""
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -173,6 +173,111 @@ class JsonDocStatusStorage(DocStatusStorage):
|
|||
async with self._storage_lock:
|
||||
return self._data.get(id)
|
||||
|
||||
async def get_docs_paginated(
|
||||
self,
|
||||
status_filter: DocStatus | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
sort_field: str = "updated_at",
|
||||
sort_direction: str = "desc",
|
||||
) -> tuple[list[tuple[str, DocProcessingStatus]], int]:
|
||||
"""Get documents with pagination support
|
||||
|
||||
Args:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', 'id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
|
||||
Returns:
|
||||
Tuple of (list of (doc_id, DocProcessingStatus) tuples, total_count)
|
||||
"""
|
||||
# Validate parameters
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 10:
|
||||
page_size = 10
|
||||
elif page_size > 200:
|
||||
page_size = 200
|
||||
|
||||
if sort_field not in ["created_at", "updated_at", "id", "file_path"]:
|
||||
sort_field = "updated_at"
|
||||
|
||||
if sort_direction.lower() not in ["asc", "desc"]:
|
||||
sort_direction = "desc"
|
||||
|
||||
# For JSON storage, we load all data and sort/filter in memory
|
||||
all_docs = []
|
||||
|
||||
async with self._storage_lock:
|
||||
for doc_id, doc_data in self._data.items():
|
||||
# Apply status filter
|
||||
if (
|
||||
status_filter is not None
|
||||
and doc_data.get("status") != status_filter.value
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
# Prepare document data
|
||||
data = doc_data.copy()
|
||||
data.pop("content", None)
|
||||
if "file_path" not in data:
|
||||
data["file_path"] = "no-file-path"
|
||||
if "metadata" not in data:
|
||||
data["metadata"] = {}
|
||||
if "error_msg" not in data:
|
||||
data["error_msg"] = None
|
||||
|
||||
doc_status = DocProcessingStatus(**data)
|
||||
|
||||
# Add sort key for sorting
|
||||
if sort_field == "id":
|
||||
doc_status._sort_key = doc_id
|
||||
else:
|
||||
doc_status._sort_key = getattr(doc_status, sort_field, "")
|
||||
|
||||
all_docs.append((doc_id, doc_status))
|
||||
|
||||
except KeyError as e:
|
||||
logger.error(f"Error processing document {doc_id}: {e}")
|
||||
continue
|
||||
|
||||
# Sort documents
|
||||
reverse_sort = sort_direction.lower() == "desc"
|
||||
all_docs.sort(
|
||||
key=lambda x: getattr(x[1], "_sort_key", ""), reverse=reverse_sort
|
||||
)
|
||||
|
||||
# Remove sort key from documents
|
||||
for doc_id, doc in all_docs:
|
||||
if hasattr(doc, "_sort_key"):
|
||||
delattr(doc, "_sort_key")
|
||||
|
||||
total_count = len(all_docs)
|
||||
|
||||
# Apply pagination
|
||||
start_idx = (page - 1) * page_size
|
||||
end_idx = start_idx + page_size
|
||||
paginated_docs = all_docs[start_idx:end_idx]
|
||||
|
||||
return paginated_docs, total_count
|
||||
|
||||
async def get_all_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status for all documents
|
||||
|
||||
Returns:
|
||||
Dictionary mapping status names to counts, including 'all' field
|
||||
"""
|
||||
counts = await self.get_status_counts()
|
||||
|
||||
# Add 'all' field with total count
|
||||
total_count = sum(counts.values())
|
||||
counts["all"] = total_count
|
||||
|
||||
return counts
|
||||
|
||||
async def delete(self, doc_ids: list[str]) -> None:
|
||||
"""Delete specific records from storage by their IDs
|
||||
|
||||
|
|
|
|||
|
|
@ -325,6 +325,9 @@ class MongoDocStatusStorage(DocStatusStorage):
|
|||
# Create track_id index for better query performance
|
||||
await self.create_track_id_index_if_not_exists()
|
||||
|
||||
# Create pagination indexes for better query performance
|
||||
await self.create_pagination_indexes_if_not_exists()
|
||||
|
||||
logger.debug(f"Use MongoDB as DocStatus {self._collection_name}")
|
||||
|
||||
async def finalize(self):
|
||||
|
|
@ -363,7 +366,7 @@ class MongoDocStatusStorage(DocStatusStorage):
|
|||
async def get_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status"""
|
||||
pipeline = [{"$group": {"_id": "$status", "count": {"$sum": 1}}}]
|
||||
cursor = self._data.aggregate(pipeline, allowDiskUse=True)
|
||||
cursor = await self._data.aggregate(pipeline, allowDiskUse=True)
|
||||
result = await cursor.to_list()
|
||||
counts = {}
|
||||
for doc in result:
|
||||
|
|
@ -481,6 +484,156 @@ class MongoDocStatusStorage(DocStatusStorage):
|
|||
f"Error creating track_id index for {self._collection_name}: {e}"
|
||||
)
|
||||
|
||||
async def create_pagination_indexes_if_not_exists(self):
|
||||
"""Create indexes to optimize pagination queries"""
|
||||
try:
|
||||
indexes_cursor = await self._data.list_indexes()
|
||||
existing_indexes = await indexes_cursor.to_list(length=None)
|
||||
|
||||
# Define indexes needed for pagination
|
||||
pagination_indexes = [
|
||||
{
|
||||
"name": "status_updated_at",
|
||||
"keys": [("status", 1), ("updated_at", -1)],
|
||||
},
|
||||
{
|
||||
"name": "status_created_at",
|
||||
"keys": [("status", 1), ("created_at", -1)],
|
||||
},
|
||||
{"name": "updated_at", "keys": [("updated_at", -1)]},
|
||||
{"name": "created_at", "keys": [("created_at", -1)]},
|
||||
{"name": "id", "keys": [("_id", 1)]},
|
||||
{"name": "file_path", "keys": [("file_path", 1)]},
|
||||
]
|
||||
|
||||
# Check which indexes already exist
|
||||
existing_index_names = {idx.get("name", "") for idx in existing_indexes}
|
||||
|
||||
for index_info in pagination_indexes:
|
||||
index_name = index_info["name"]
|
||||
if index_name not in existing_index_names:
|
||||
await self._data.create_index(index_info["keys"], name=index_name)
|
||||
logger.info(
|
||||
f"Created pagination index '{index_name}' for collection {self._collection_name}"
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"Pagination index '{index_name}' already exists for collection {self._collection_name}"
|
||||
)
|
||||
|
||||
except PyMongoError as e:
|
||||
logger.error(
|
||||
f"Error creating pagination indexes for {self._collection_name}: {e}"
|
||||
)
|
||||
|
||||
async def get_docs_paginated(
|
||||
self,
|
||||
status_filter: DocStatus | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
sort_field: str = "updated_at",
|
||||
sort_direction: str = "desc",
|
||||
) -> tuple[list[tuple[str, DocProcessingStatus]], int]:
|
||||
"""Get documents with pagination support
|
||||
|
||||
Args:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', '_id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
|
||||
Returns:
|
||||
Tuple of (list of (doc_id, DocProcessingStatus) tuples, total_count)
|
||||
"""
|
||||
# Validate parameters
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 10:
|
||||
page_size = 10
|
||||
elif page_size > 200:
|
||||
page_size = 200
|
||||
|
||||
if sort_field not in ["created_at", "updated_at", "_id", "file_path"]:
|
||||
sort_field = "updated_at"
|
||||
|
||||
if sort_direction.lower() not in ["asc", "desc"]:
|
||||
sort_direction = "desc"
|
||||
|
||||
# Build query filter
|
||||
query_filter = {}
|
||||
if status_filter is not None:
|
||||
query_filter["status"] = status_filter.value
|
||||
|
||||
# Get total count
|
||||
total_count = await self._data.count_documents(query_filter)
|
||||
|
||||
# Calculate skip value
|
||||
skip = (page - 1) * page_size
|
||||
|
||||
# Build sort criteria
|
||||
sort_direction_value = 1 if sort_direction.lower() == "asc" else -1
|
||||
sort_criteria = [(sort_field, sort_direction_value)]
|
||||
|
||||
# Query for paginated data
|
||||
cursor = (
|
||||
self._data.find(query_filter)
|
||||
.sort(sort_criteria)
|
||||
.skip(skip)
|
||||
.limit(page_size)
|
||||
)
|
||||
result = await cursor.to_list(length=page_size)
|
||||
|
||||
# Convert to (doc_id, DocProcessingStatus) tuples
|
||||
documents = []
|
||||
for doc in result:
|
||||
try:
|
||||
doc_id = doc["_id"]
|
||||
|
||||
# Make a copy of the data to avoid modifying the original
|
||||
data = doc.copy()
|
||||
# Remove deprecated content field if it exists
|
||||
data.pop("content", None)
|
||||
# Remove MongoDB _id field if it exists
|
||||
data.pop("_id", None)
|
||||
# If file_path is not in data, use document id as file path
|
||||
if "file_path" not in data:
|
||||
data["file_path"] = "no-file-path"
|
||||
# Ensure new fields exist with default values
|
||||
if "metadata" not in data:
|
||||
data["metadata"] = {}
|
||||
if "error_msg" not in data:
|
||||
data["error_msg"] = None
|
||||
|
||||
doc_status = DocProcessingStatus(**data)
|
||||
documents.append((doc_id, doc_status))
|
||||
except KeyError as e:
|
||||
logger.error(f"Missing required field for document {doc['_id']}: {e}")
|
||||
continue
|
||||
|
||||
return documents, total_count
|
||||
|
||||
async def get_all_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status for all documents
|
||||
|
||||
Returns:
|
||||
Dictionary mapping status names to counts, including 'all' field
|
||||
"""
|
||||
pipeline = [{"$group": {"_id": "$status", "count": {"$sum": 1}}}]
|
||||
cursor = await self._data.aggregate(pipeline, allowDiskUse=True)
|
||||
result = await cursor.to_list()
|
||||
|
||||
counts = {}
|
||||
total_count = 0
|
||||
for doc in result:
|
||||
counts[doc["_id"]] = doc["count"]
|
||||
total_count += doc["count"]
|
||||
|
||||
# Add 'all' field with total count
|
||||
counts["all"] = total_count
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
@final
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -914,6 +914,69 @@ class PostgreSQLDB:
|
|||
f"PostgreSQL, Failed to migrate doc status metadata/error_msg fields: {e}"
|
||||
)
|
||||
|
||||
# Create pagination optimization indexes for LIGHTRAG_DOC_STATUS
|
||||
try:
|
||||
await self._create_pagination_indexes()
|
||||
except Exception as e:
|
||||
logger.error(f"PostgreSQL, Failed to create pagination indexes: {e}")
|
||||
|
||||
async def _create_pagination_indexes(self):
|
||||
"""Create indexes to optimize pagination queries for LIGHTRAG_DOC_STATUS"""
|
||||
indexes = [
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_status_updated_at",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_status_updated_at ON LIGHTRAG_DOC_STATUS (workspace, status, updated_at DESC)",
|
||||
"description": "Composite index for workspace + status + updated_at pagination",
|
||||
},
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_status_created_at",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_status_created_at ON LIGHTRAG_DOC_STATUS (workspace, status, created_at DESC)",
|
||||
"description": "Composite index for workspace + status + created_at pagination",
|
||||
},
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_updated_at",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_updated_at ON LIGHTRAG_DOC_STATUS (workspace, updated_at DESC)",
|
||||
"description": "Index for workspace + updated_at pagination (all statuses)",
|
||||
},
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_created_at",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_created_at ON LIGHTRAG_DOC_STATUS (workspace, created_at DESC)",
|
||||
"description": "Index for workspace + created_at pagination (all statuses)",
|
||||
},
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_id",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_id ON LIGHTRAG_DOC_STATUS (workspace, id)",
|
||||
"description": "Index for workspace + id sorting",
|
||||
},
|
||||
{
|
||||
"name": "idx_lightrag_doc_status_workspace_file_path",
|
||||
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_lightrag_doc_status_workspace_file_path ON LIGHTRAG_DOC_STATUS (workspace, file_path)",
|
||||
"description": "Index for workspace + file_path sorting",
|
||||
},
|
||||
]
|
||||
|
||||
for index in indexes:
|
||||
try:
|
||||
# Check if index already exists
|
||||
check_sql = """
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE tablename = 'lightrag_doc_status'
|
||||
AND indexname = $1
|
||||
"""
|
||||
|
||||
existing = await self.query(check_sql, {"indexname": index["name"]})
|
||||
|
||||
if not existing:
|
||||
logger.info(f"Creating pagination index: {index['description']}")
|
||||
await self.execute(index["sql"])
|
||||
logger.info(f"Successfully created index: {index['name']}")
|
||||
else:
|
||||
logger.debug(f"Index already exists: {index['name']}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to create index {index['name']}: {e}")
|
||||
|
||||
async def query(
|
||||
self,
|
||||
sql: str,
|
||||
|
|
@ -1980,6 +2043,141 @@ class PGDocStatusStorage(DocStatusStorage):
|
|||
|
||||
return docs_by_track_id
|
||||
|
||||
async def get_docs_paginated(
|
||||
self,
|
||||
status_filter: DocStatus | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
sort_field: str = "updated_at",
|
||||
sort_direction: str = "desc",
|
||||
) -> tuple[list[tuple[str, DocProcessingStatus]], int]:
|
||||
"""Get documents with pagination support
|
||||
|
||||
Args:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', 'id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
|
||||
Returns:
|
||||
Tuple of (list of (doc_id, DocProcessingStatus) tuples, total_count)
|
||||
"""
|
||||
# Validate parameters
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 10:
|
||||
page_size = 10
|
||||
elif page_size > 200:
|
||||
page_size = 200
|
||||
|
||||
if sort_field not in ["created_at", "updated_at", "id", "file_path"]:
|
||||
sort_field = "updated_at"
|
||||
|
||||
if sort_direction.lower() not in ["asc", "desc"]:
|
||||
sort_direction = "desc"
|
||||
|
||||
# Calculate offset
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
# Build WHERE clause
|
||||
where_clause = "WHERE workspace=$1"
|
||||
params = {"workspace": self.db.workspace}
|
||||
param_count = 1
|
||||
|
||||
if status_filter is not None:
|
||||
param_count += 1
|
||||
where_clause += f" AND status=${param_count}"
|
||||
params["status"] = status_filter.value
|
||||
|
||||
# Build ORDER BY clause
|
||||
order_clause = f"ORDER BY {sort_field} {sort_direction.upper()}"
|
||||
|
||||
# Query for total count
|
||||
count_sql = f"SELECT COUNT(*) as total FROM LIGHTRAG_DOC_STATUS {where_clause}"
|
||||
count_result = await self.db.query(count_sql, params)
|
||||
total_count = count_result["total"] if count_result else 0
|
||||
|
||||
# Query for paginated data
|
||||
data_sql = f"""
|
||||
SELECT * FROM LIGHTRAG_DOC_STATUS
|
||||
{where_clause}
|
||||
{order_clause}
|
||||
LIMIT ${param_count + 1} OFFSET ${param_count + 2}
|
||||
"""
|
||||
params["limit"] = page_size
|
||||
params["offset"] = offset
|
||||
|
||||
result = await self.db.query(data_sql, params, True)
|
||||
|
||||
# Convert to (doc_id, DocProcessingStatus) tuples
|
||||
documents = []
|
||||
for element in result:
|
||||
doc_id = element["id"]
|
||||
|
||||
# Parse chunks_list JSON string back to list
|
||||
chunks_list = element.get("chunks_list", [])
|
||||
if isinstance(chunks_list, str):
|
||||
try:
|
||||
chunks_list = json.loads(chunks_list)
|
||||
except json.JSONDecodeError:
|
||||
chunks_list = []
|
||||
|
||||
# Parse metadata JSON string back to dict
|
||||
metadata = element.get("metadata", {})
|
||||
if isinstance(metadata, str):
|
||||
try:
|
||||
metadata = json.loads(metadata)
|
||||
except json.JSONDecodeError:
|
||||
metadata = {}
|
||||
|
||||
# Convert datetime objects to ISO format strings with timezone info
|
||||
created_at = self._format_datetime_with_timezone(element["created_at"])
|
||||
updated_at = self._format_datetime_with_timezone(element["updated_at"])
|
||||
|
||||
doc_status = DocProcessingStatus(
|
||||
content_summary=element["content_summary"],
|
||||
content_length=element["content_length"],
|
||||
status=element["status"],
|
||||
created_at=created_at,
|
||||
updated_at=updated_at,
|
||||
chunks_count=element["chunks_count"],
|
||||
file_path=element["file_path"],
|
||||
chunks_list=chunks_list,
|
||||
track_id=element.get("track_id"),
|
||||
metadata=metadata,
|
||||
error_msg=element.get("error_msg"),
|
||||
)
|
||||
documents.append((doc_id, doc_status))
|
||||
|
||||
return documents, total_count
|
||||
|
||||
async def get_all_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status for all documents
|
||||
|
||||
Returns:
|
||||
Dictionary mapping status names to counts, including 'all' field
|
||||
"""
|
||||
sql = """
|
||||
SELECT status, COUNT(*) as count
|
||||
FROM LIGHTRAG_DOC_STATUS
|
||||
WHERE workspace=$1
|
||||
GROUP BY status
|
||||
"""
|
||||
params = {"workspace": self.db.workspace}
|
||||
result = await self.db.query(sql, params, True)
|
||||
|
||||
counts = {}
|
||||
total_count = 0
|
||||
for row in result:
|
||||
counts[row["status"]] = row["count"]
|
||||
total_count += row["count"]
|
||||
|
||||
# Add 'all' field with total count
|
||||
counts["all"] = total_count
|
||||
|
||||
return counts
|
||||
|
||||
async def index_done_callback(self) -> None:
|
||||
# PG handles persistence automatically
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -919,6 +919,138 @@ class RedisDocStatusStorage(DocStatusStorage):
|
|||
f"Deleted {deleted_count} of {len(doc_ids)} doc status entries from {self.namespace}"
|
||||
)
|
||||
|
||||
async def get_docs_paginated(
|
||||
self,
|
||||
status_filter: DocStatus | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
sort_field: str = "updated_at",
|
||||
sort_direction: str = "desc",
|
||||
) -> tuple[list[tuple[str, DocProcessingStatus]], int]:
|
||||
"""Get documents with pagination support
|
||||
|
||||
Args:
|
||||
status_filter: Filter by document status, None for all statuses
|
||||
page: Page number (1-based)
|
||||
page_size: Number of documents per page (10-200)
|
||||
sort_field: Field to sort by ('created_at', 'updated_at', 'id')
|
||||
sort_direction: Sort direction ('asc' or 'desc')
|
||||
|
||||
Returns:
|
||||
Tuple of (list of (doc_id, DocProcessingStatus) tuples, total_count)
|
||||
"""
|
||||
# Validate parameters
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 10:
|
||||
page_size = 10
|
||||
elif page_size > 200:
|
||||
page_size = 200
|
||||
|
||||
if sort_field not in ["created_at", "updated_at", "id", "file_path"]:
|
||||
sort_field = "updated_at"
|
||||
|
||||
if sort_direction.lower() not in ["asc", "desc"]:
|
||||
sort_direction = "desc"
|
||||
|
||||
# For Redis, we need to load all data and sort/filter in memory
|
||||
all_docs = []
|
||||
total_count = 0
|
||||
|
||||
async with self._get_redis_connection() as redis:
|
||||
try:
|
||||
# Use SCAN to iterate through all keys in the namespace
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, keys = await redis.scan(
|
||||
cursor, match=f"{self.namespace}:*", count=1000
|
||||
)
|
||||
if keys:
|
||||
# Get all values in batch
|
||||
pipe = redis.pipeline()
|
||||
for key in keys:
|
||||
pipe.get(key)
|
||||
values = await pipe.execute()
|
||||
|
||||
# Process documents
|
||||
for key, value in zip(keys, values):
|
||||
if value:
|
||||
try:
|
||||
doc_data = json.loads(value)
|
||||
|
||||
# Apply status filter
|
||||
if (
|
||||
status_filter is not None
|
||||
and doc_data.get("status")
|
||||
!= status_filter.value
|
||||
):
|
||||
continue
|
||||
|
||||
# Extract document ID from key
|
||||
doc_id = key.split(":", 1)[1]
|
||||
|
||||
# Prepare document data
|
||||
data = doc_data.copy()
|
||||
data.pop("content", None)
|
||||
if "file_path" not in data:
|
||||
data["file_path"] = "no-file-path"
|
||||
if "metadata" not in data:
|
||||
data["metadata"] = {}
|
||||
if "error_msg" not in data:
|
||||
data["error_msg"] = None
|
||||
|
||||
# Calculate sort key for sorting (but don't add to data)
|
||||
if sort_field == "id":
|
||||
sort_key = doc_id
|
||||
else:
|
||||
sort_key = data.get(sort_field, "")
|
||||
|
||||
doc_status = DocProcessingStatus(**data)
|
||||
all_docs.append((doc_id, doc_status, sort_key))
|
||||
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.error(
|
||||
f"Error processing document {key}: {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
if cursor == 0:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting paginated docs: {e}")
|
||||
return [], 0
|
||||
|
||||
# Sort documents using the separate sort key
|
||||
reverse_sort = sort_direction.lower() == "desc"
|
||||
all_docs.sort(key=lambda x: x[2], reverse=reverse_sort)
|
||||
|
||||
# Remove sort key from tuples and keep only (doc_id, doc_status)
|
||||
all_docs = [(doc_id, doc_status) for doc_id, doc_status, _ in all_docs]
|
||||
|
||||
total_count = len(all_docs)
|
||||
|
||||
# Apply pagination
|
||||
start_idx = (page - 1) * page_size
|
||||
end_idx = start_idx + page_size
|
||||
paginated_docs = all_docs[start_idx:end_idx]
|
||||
|
||||
return paginated_docs, total_count
|
||||
|
||||
async def get_all_status_counts(self) -> dict[str, int]:
|
||||
"""Get counts of documents in each status for all documents
|
||||
|
||||
Returns:
|
||||
Dictionary mapping status names to counts, including 'all' field
|
||||
"""
|
||||
counts = await self.get_status_counts()
|
||||
|
||||
# Add 'all' field with total count
|
||||
total_count = sum(counts.values())
|
||||
counts["all"] = total_count
|
||||
|
||||
return counts
|
||||
|
||||
async def drop(self) -> dict[str, str]:
|
||||
"""Drop all document status data from storage and clean up resources"""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ class LightragPathFilter(logging.Filter):
|
|||
# Define paths to be filtered
|
||||
self.filtered_paths = [
|
||||
"/documents",
|
||||
"/documents/paginated",
|
||||
"/health",
|
||||
"/webui/",
|
||||
"/documents/pipeline_status",
|
||||
|
|
@ -145,6 +146,7 @@ class LightragPathFilter(logging.Filter):
|
|||
# Filter out successful GET requests to filtered paths
|
||||
if (
|
||||
method == "GET"
|
||||
or method == "POST"
|
||||
and (status == 200 or status == 304)
|
||||
and path in self.filtered_paths
|
||||
):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ThemeProvider from '@/components/ThemeProvider'
|
|||
import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
|
||||
import ApiKeyAlert from '@/components/ApiKeyAlert'
|
||||
import StatusIndicator from '@/components/status/StatusIndicator'
|
||||
import { healthCheckInterval, SiteInfo, webuiPrefix } from '@/lib/constants'
|
||||
import { SiteInfo, webuiPrefix } from '@/lib/constants'
|
||||
import { useBackendState, useAuthStore } from '@/stores/state'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { getAuthStatus } from '@/api/lightrag'
|
||||
|
|
@ -56,9 +56,6 @@ function App() {
|
|||
|
||||
// Health check - can be disabled
|
||||
useEffect(() => {
|
||||
// Only execute if health check is enabled and ApiKeyAlert is closed
|
||||
if (!enableHealthCheck || apiKeyAlertOpen) return;
|
||||
|
||||
// Health check function
|
||||
const performHealthCheck = async () => {
|
||||
try {
|
||||
|
|
@ -71,17 +68,27 @@ function App() {
|
|||
}
|
||||
};
|
||||
|
||||
// On first mount or when enableHealthCheck becomes true and apiKeyAlertOpen is false,
|
||||
// perform an immediate health check
|
||||
if (!healthCheckInitializedRef.current) {
|
||||
healthCheckInitializedRef.current = true;
|
||||
// Immediate health check on first load
|
||||
performHealthCheck();
|
||||
// Set health check function in the store
|
||||
useBackendState.getState().setHealthCheckFunction(performHealthCheck);
|
||||
|
||||
if (!enableHealthCheck || apiKeyAlertOpen) {
|
||||
useBackendState.getState().clearHealthCheckTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set interval for periodic execution
|
||||
const interval = setInterval(performHealthCheck, healthCheckInterval * 1000);
|
||||
return () => clearInterval(interval);
|
||||
// On first mount or when enableHealthCheck becomes true and apiKeyAlertOpen is false,
|
||||
// perform an immediate health check and start the timer
|
||||
if (!healthCheckInitializedRef.current) {
|
||||
healthCheckInitializedRef.current = true;
|
||||
}
|
||||
|
||||
// Start/reset the health check timer using the store
|
||||
useBackendState.getState().resetHealthCheckTimer();
|
||||
|
||||
// Component unmount cleanup
|
||||
return () => {
|
||||
useBackendState.getState().clearHealthCheckTimer();
|
||||
};
|
||||
}, [enableHealthCheck, apiKeyAlertOpen]);
|
||||
|
||||
// Version check - independent and executed only once
|
||||
|
|
|
|||
|
|
@ -185,6 +185,33 @@ export type TrackStatusResponse = {
|
|||
status_summary: Record<string, number>
|
||||
}
|
||||
|
||||
export type DocumentsRequest = {
|
||||
status_filter?: DocStatus | null
|
||||
page: number
|
||||
page_size: number
|
||||
sort_field: 'created_at' | 'updated_at' | 'id' | 'file_path'
|
||||
sort_direction: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
export type PaginationInfo = {
|
||||
page: number
|
||||
page_size: number
|
||||
total_count: number
|
||||
total_pages: number
|
||||
has_next: boolean
|
||||
has_prev: boolean
|
||||
}
|
||||
|
||||
export type PaginatedDocsResponse = {
|
||||
documents: DocStatusResponse[]
|
||||
pagination: PaginationInfo
|
||||
status_counts: Record<string, number>
|
||||
}
|
||||
|
||||
export type StatusCountsResponse = {
|
||||
status_counts: Record<string, number>
|
||||
}
|
||||
|
||||
export type AuthStatusResponse = {
|
||||
auth_configured: boolean
|
||||
access_token?: string
|
||||
|
|
@ -714,3 +741,22 @@ export const getTrackStatus = async (trackId: string): Promise<TrackStatusRespon
|
|||
const response = await axiosInstance.get(`/documents/track_status/${encodeURIComponent(trackId)}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents with pagination support
|
||||
* @param request The pagination request parameters
|
||||
* @returns Promise with paginated documents response
|
||||
*/
|
||||
export const getDocumentsPaginated = async (request: DocumentsRequest): Promise<PaginatedDocsResponse> => {
|
||||
const response = await axiosInstance.post('/documents/paginated', request)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get counts of documents by status
|
||||
* @returns Promise with status counts response
|
||||
*/
|
||||
export const getDocumentStatusCounts = async (): Promise<StatusCountsResponse> => {
|
||||
const response = await axiosInstance.get('/documents/status_counts')
|
||||
return response.data
|
||||
}
|
||||
|
|
|
|||
259
lightrag_webui/src/components/ui/PaginationControls.tsx
Normal file
259
lightrag_webui/src/components/ui/PaginationControls.tsx
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from './Button'
|
||||
import Input from './Input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon } from 'lucide-react'
|
||||
|
||||
export type PaginationControlsProps = {
|
||||
currentPage: number
|
||||
totalPages: number
|
||||
pageSize: number
|
||||
totalCount: number
|
||||
onPageChange: (page: number) => void
|
||||
onPageSizeChange: (pageSize: number) => void
|
||||
isLoading?: boolean
|
||||
compact?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [
|
||||
{ value: 10, label: '10' },
|
||||
{ value: 20, label: '20' },
|
||||
{ value: 50, label: '50' },
|
||||
{ value: 100, label: '100' },
|
||||
{ value: 200, label: '200' }
|
||||
]
|
||||
|
||||
export default function PaginationControls({
|
||||
currentPage,
|
||||
totalPages,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
isLoading = false,
|
||||
compact = false,
|
||||
className
|
||||
}: PaginationControlsProps) {
|
||||
const { t } = useTranslation()
|
||||
const [inputPage, setInputPage] = useState(currentPage.toString())
|
||||
|
||||
// Update input when currentPage changes
|
||||
useEffect(() => {
|
||||
setInputPage(currentPage.toString())
|
||||
}, [currentPage])
|
||||
|
||||
// Handle page input change with debouncing
|
||||
const handlePageInputChange = useCallback((value: string) => {
|
||||
setInputPage(value)
|
||||
}, [])
|
||||
|
||||
// Handle page input submit
|
||||
const handlePageInputSubmit = useCallback(() => {
|
||||
const pageNum = parseInt(inputPage, 10)
|
||||
if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {
|
||||
onPageChange(pageNum)
|
||||
} else {
|
||||
// Reset to current page if invalid
|
||||
setInputPage(currentPage.toString())
|
||||
}
|
||||
}, [inputPage, totalPages, onPageChange, currentPage])
|
||||
|
||||
// Handle page input key press
|
||||
const handlePageInputKeyPress = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handlePageInputSubmit()
|
||||
}
|
||||
}, [handlePageInputSubmit])
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = useCallback((value: string) => {
|
||||
const newPageSize = parseInt(value, 10)
|
||||
if (!isNaN(newPageSize)) {
|
||||
onPageSizeChange(newPageSize)
|
||||
}
|
||||
}, [onPageSizeChange])
|
||||
|
||||
// Navigation handlers
|
||||
const goToFirstPage = useCallback(() => {
|
||||
if (currentPage > 1 && !isLoading) {
|
||||
onPageChange(1)
|
||||
}
|
||||
}, [currentPage, onPageChange, isLoading])
|
||||
|
||||
const goToPrevPage = useCallback(() => {
|
||||
if (currentPage > 1 && !isLoading) {
|
||||
onPageChange(currentPage - 1)
|
||||
}
|
||||
}, [currentPage, onPageChange, isLoading])
|
||||
|
||||
const goToNextPage = useCallback(() => {
|
||||
if (currentPage < totalPages && !isLoading) {
|
||||
onPageChange(currentPage + 1)
|
||||
}
|
||||
}, [currentPage, totalPages, onPageChange, isLoading])
|
||||
|
||||
const goToLastPage = useCallback(() => {
|
||||
if (currentPage < totalPages && !isLoading) {
|
||||
onPageChange(totalPages)
|
||||
}
|
||||
}, [currentPage, totalPages, onPageChange, isLoading])
|
||||
|
||||
if (totalPages <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToPrevPage}
|
||||
disabled={currentPage <= 1 || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="text"
|
||||
value={inputPage}
|
||||
onChange={(e) => handlePageInputChange(e.target.value)}
|
||||
onBlur={handlePageInputSubmit}
|
||||
onKeyPress={handlePageInputKeyPress}
|
||||
disabled={isLoading}
|
||||
className="h-8 w-12 text-center text-sm"
|
||||
/>
|
||||
<span className="text-sm text-gray-500">/ {totalPages}</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToNextPage}
|
||||
disabled={currentPage >= totalPages || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={handlePageSizeChange}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-16">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PAGE_SIZE_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value.toString()}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center justify-between gap-4', className)}>
|
||||
<div className="text-sm text-gray-500">
|
||||
{t('pagination.showing', {
|
||||
start: Math.min((currentPage - 1) * pageSize + 1, totalCount),
|
||||
end: Math.min(currentPage * pageSize, totalCount),
|
||||
total: totalCount
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToFirstPage}
|
||||
disabled={currentPage <= 1 || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
tooltip={t('pagination.firstPage')}
|
||||
>
|
||||
<ChevronsLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToPrevPage}
|
||||
disabled={currentPage <= 1 || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
tooltip={t('pagination.prevPage')}
|
||||
>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{t('pagination.page')}</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={inputPage}
|
||||
onChange={(e) => handlePageInputChange(e.target.value)}
|
||||
onBlur={handlePageInputSubmit}
|
||||
onKeyPress={handlePageInputKeyPress}
|
||||
disabled={isLoading}
|
||||
className="h-8 w-16 text-center text-sm"
|
||||
/>
|
||||
<span className="text-sm">/ {totalPages}</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToNextPage}
|
||||
disabled={currentPage >= totalPages || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
tooltip={t('pagination.nextPage')}
|
||||
>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToLastPage}
|
||||
disabled={currentPage >= totalPages || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
tooltip={t('pagination.lastPage')}
|
||||
>
|
||||
<ChevronsRightIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">{t('pagination.pageSize')}</span>
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={handlePageSizeChange}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-16">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PAGE_SIZE_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value.toString()}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -18,13 +18,22 @@ import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
|||
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
||||
import DeleteDocumentsDialog from '@/components/documents/DeleteDocumentsDialog'
|
||||
import DeselectDocumentsDialog from '@/components/documents/DeselectDocumentsDialog'
|
||||
import PaginationControls from '@/components/ui/PaginationControls'
|
||||
|
||||
import { getDocuments, scanNewDocuments, DocsStatusesResponse, DocStatus, DocStatusResponse } from '@/api/lightrag'
|
||||
import {
|
||||
scanNewDocuments,
|
||||
getDocumentsPaginated,
|
||||
DocsStatusesResponse,
|
||||
DocStatus,
|
||||
DocStatusResponse,
|
||||
DocumentsRequest,
|
||||
PaginationInfo
|
||||
} from '@/api/lightrag'
|
||||
import { errorMessage } from '@/lib/utils'
|
||||
import { toast } from 'sonner'
|
||||
import { useBackendState } from '@/stores/state'
|
||||
|
||||
import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, FilterIcon } from 'lucide-react'
|
||||
import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, RotateCcwIcon } from 'lucide-react'
|
||||
import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
|
||||
|
||||
type StatusFilter = DocStatus | 'all';
|
||||
|
|
@ -136,7 +145,7 @@ const pulseStyle = `
|
|||
`;
|
||||
|
||||
// Type definitions for sort field and direction
|
||||
type SortField = 'created_at' | 'updated_at' | 'id';
|
||||
type SortField = 'created_at' | 'updated_at' | 'id' | 'file_path';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export default function DocumentManager() {
|
||||
|
|
@ -164,10 +173,28 @@ export default function DocumentManager() {
|
|||
const { t, i18n } = useTranslation()
|
||||
const health = useBackendState.use.health()
|
||||
const pipelineBusy = useBackendState.use.pipelineBusy()
|
||||
|
||||
// Legacy state for backward compatibility
|
||||
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
|
||||
|
||||
const currentTab = useSettingsStore.use.currentTab()
|
||||
const showFileName = useSettingsStore.use.showFileName()
|
||||
const setShowFileName = useSettingsStore.use.setShowFileName()
|
||||
const documentsPageSize = useSettingsStore.use.documentsPageSize()
|
||||
const setDocumentsPageSize = useSettingsStore.use.setDocumentsPageSize()
|
||||
|
||||
// New pagination state
|
||||
const [, setCurrentPageDocs] = useState<DocStatusResponse[]>([])
|
||||
const [pagination, setPagination] = useState<PaginationInfo>({
|
||||
page: 1,
|
||||
page_size: documentsPageSize,
|
||||
total_count: 0,
|
||||
total_pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false
|
||||
})
|
||||
const [statusCounts, setStatusCounts] = useState<Record<string, number>>({ all: 0 })
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
|
||||
// Sort state
|
||||
const [sortField, setSortField] = useState<SortField>('updated_at')
|
||||
|
|
@ -176,6 +203,15 @@ export default function DocumentManager() {
|
|||
// State for document status filter
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
|
||||
|
||||
// State to store page number for each status filter
|
||||
const [pageByStatus, setPageByStatus] = useState<Record<StatusFilter, number>>({
|
||||
all: 1,
|
||||
processed: 1,
|
||||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
});
|
||||
|
||||
// State for document selection
|
||||
const [selectedDocIds, setSelectedDocIds] = useState<string[]>([])
|
||||
const isSelectionMode = selectedDocIds.length > 0
|
||||
|
|
@ -198,15 +234,30 @@ export default function DocumentManager() {
|
|||
|
||||
// Handle sort column click
|
||||
const handleSort = (field: SortField) => {
|
||||
if (sortField === field) {
|
||||
// Toggle sort direction if clicking the same field
|
||||
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
// Set new sort field with default desc direction
|
||||
setSortField(field)
|
||||
setSortDirection('desc')
|
||||
let actualField = field;
|
||||
|
||||
// When clicking the first column, determine the actual sort field based on showFileName
|
||||
if (field === 'id') {
|
||||
actualField = showFileName ? 'file_path' : 'id';
|
||||
}
|
||||
}
|
||||
|
||||
const newDirection = (sortField === actualField && sortDirection === 'desc') ? 'asc' : 'desc';
|
||||
|
||||
setSortField(actualField);
|
||||
setSortDirection(newDirection);
|
||||
|
||||
// Reset page to 1 when sorting changes
|
||||
setPagination(prev => ({ ...prev, page: 1 }));
|
||||
|
||||
// Reset all status filters' page memory since sorting affects all
|
||||
setPageByStatus({
|
||||
all: 1,
|
||||
processed: 1,
|
||||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
});
|
||||
};
|
||||
|
||||
// Sort documents based on current sort field and direction
|
||||
const sortDocuments = useCallback((documents: DocStatusResponse[]) => {
|
||||
|
|
@ -373,47 +424,97 @@ export default function DocumentManager() {
|
|||
};
|
||||
}, [docs]);
|
||||
|
||||
const fetchDocuments = useCallback(async () => {
|
||||
// New paginated data fetching function
|
||||
const fetchPaginatedDocuments = useCallback(async (
|
||||
page: number,
|
||||
pageSize: number,
|
||||
statusFilter: StatusFilter
|
||||
) => {
|
||||
try {
|
||||
// Check if component is still mounted before starting the request
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
const docs = await getDocuments();
|
||||
setIsRefreshing(true);
|
||||
|
||||
// Prepare request parameters
|
||||
const request: DocumentsRequest = {
|
||||
status_filter: statusFilter === 'all' ? null : statusFilter,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
sort_field: sortField,
|
||||
sort_direction: sortDirection
|
||||
};
|
||||
|
||||
const response = await getDocumentsPaginated(request);
|
||||
|
||||
// Check again if component is still mounted after the request completes
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
// Only update state if component is still mounted
|
||||
// Update pagination state
|
||||
setPagination(response.pagination);
|
||||
setCurrentPageDocs(response.documents);
|
||||
setStatusCounts(response.status_counts);
|
||||
|
||||
// Update legacy docs state for backward compatibility
|
||||
const legacyDocs: DocsStatusesResponse = {
|
||||
statuses: {
|
||||
processed: response.documents.filter(doc => doc.status === 'processed'),
|
||||
processing: response.documents.filter(doc => doc.status === 'processing'),
|
||||
pending: response.documents.filter(doc => doc.status === 'pending'),
|
||||
failed: response.documents.filter(doc => doc.status === 'failed')
|
||||
}
|
||||
};
|
||||
|
||||
if (response.pagination.total_count > 0) {
|
||||
setDocs(legacyDocs);
|
||||
} else {
|
||||
setDocs(null);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
if (isMountedRef.current) {
|
||||
// Update docs state
|
||||
if (docs && docs.statuses) {
|
||||
const numDocuments = Object.values(docs.statuses).reduce(
|
||||
(acc, status) => acc + status.length,
|
||||
0
|
||||
)
|
||||
if (numDocuments > 0) {
|
||||
setDocs(docs)
|
||||
} else {
|
||||
setDocs(null)
|
||||
}
|
||||
} else {
|
||||
setDocs(null)
|
||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }));
|
||||
}
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}
|
||||
}, [sortField, sortDirection, t]);
|
||||
|
||||
// Legacy fetchDocuments function for backward compatibility
|
||||
const fetchDocuments = useCallback(async () => {
|
||||
await fetchPaginatedDocuments(pagination.page, pagination.page_size, statusFilter);
|
||||
}, [fetchPaginatedDocuments, pagination.page, pagination.page_size, statusFilter]);
|
||||
|
||||
// Add refs to track previous pipelineBusy state and current interval
|
||||
const prevPipelineBusyRef = useRef<boolean | undefined>(undefined);
|
||||
const pollingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// Function to clear current polling interval
|
||||
const clearPollingInterval = useCallback(() => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Function to start polling with given interval
|
||||
const startPollingInterval = useCallback((intervalMs: number) => {
|
||||
clearPollingInterval();
|
||||
|
||||
pollingIntervalRef.current = setInterval(async () => {
|
||||
try {
|
||||
// Only perform fetch if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
await fetchDocuments()
|
||||
}
|
||||
} catch (err) {
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }))
|
||||
}
|
||||
}
|
||||
}, [setDocs, t])
|
||||
|
||||
// Fetch documents when the tab becomes visible
|
||||
useEffect(() => {
|
||||
if (currentTab === 'documents') {
|
||||
fetchDocuments()
|
||||
}
|
||||
}, [currentTab, fetchDocuments])
|
||||
}, intervalMs);
|
||||
}, [fetchDocuments, t, clearPollingInterval]);
|
||||
|
||||
const scanDocuments = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -427,38 +528,130 @@ export default function DocumentManager() {
|
|||
|
||||
// Note: _track_id is available for future use (e.g., progress tracking)
|
||||
toast.message(message || status);
|
||||
|
||||
// Reset health check timer with 1 second delay to avoid race condition
|
||||
useBackendState.getState().resetHealthCheckTimerDelayed(1000);
|
||||
|
||||
// Schedule a health check 2 seconds after successful scan
|
||||
startPollingInterval(2000);
|
||||
} catch (err) {
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }));
|
||||
}
|
||||
}
|
||||
}, [t])
|
||||
}, [t, startPollingInterval])
|
||||
|
||||
// Set up polling when the documents tab is active and health is good
|
||||
// Handle page size change - update state and save to store
|
||||
const handlePageSizeChange = useCallback((newPageSize: number) => {
|
||||
if (newPageSize === pagination.page_size) return;
|
||||
|
||||
// Save the new page size to the store
|
||||
setDocumentsPageSize(newPageSize);
|
||||
|
||||
// Reset all status filters to page 1 when page size changes
|
||||
setPageByStatus({
|
||||
all: 1,
|
||||
processed: 1,
|
||||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
});
|
||||
|
||||
setPagination(prev => ({ ...prev, page: 1, page_size: newPageSize }));
|
||||
}, [pagination.page_size, setDocumentsPageSize]);
|
||||
|
||||
// Handle manual refresh with pagination reset logic
|
||||
const handleManualRefresh = useCallback(async () => {
|
||||
try {
|
||||
setIsRefreshing(true);
|
||||
|
||||
// Fetch documents from the first page
|
||||
const request: DocumentsRequest = {
|
||||
status_filter: statusFilter === 'all' ? null : statusFilter,
|
||||
page: 1,
|
||||
page_size: pagination.page_size,
|
||||
sort_field: sortField,
|
||||
sort_direction: sortDirection
|
||||
};
|
||||
|
||||
const response = await getDocumentsPaginated(request);
|
||||
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
// Check if total count is less than current page size and page size is not already 10
|
||||
if (response.pagination.total_count < pagination.page_size && pagination.page_size !== 10) {
|
||||
// Reset page size to 10 which will trigger a new fetch
|
||||
handlePageSizeChange(10);
|
||||
} else {
|
||||
// Update pagination state
|
||||
setPagination(response.pagination);
|
||||
setCurrentPageDocs(response.documents);
|
||||
setStatusCounts(response.status_counts);
|
||||
|
||||
// Update legacy docs state for backward compatibility
|
||||
const legacyDocs: DocsStatusesResponse = {
|
||||
statuses: {
|
||||
processed: response.documents.filter(doc => doc.status === 'processed'),
|
||||
processing: response.documents.filter(doc => doc.status === 'processing'),
|
||||
pending: response.documents.filter(doc => doc.status === 'pending'),
|
||||
failed: response.documents.filter(doc => doc.status === 'failed')
|
||||
}
|
||||
};
|
||||
|
||||
if (response.pagination.total_count > 0) {
|
||||
setDocs(legacyDocs);
|
||||
} else {
|
||||
setDocs(null);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }));
|
||||
}
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}
|
||||
}, [statusFilter, pagination.page_size, sortField, sortDirection, handlePageSizeChange, t]);
|
||||
|
||||
// Monitor pipelineBusy changes and trigger immediate refresh with timer reset
|
||||
useEffect(() => {
|
||||
// Skip the first render when prevPipelineBusyRef is undefined
|
||||
if (prevPipelineBusyRef.current !== undefined && prevPipelineBusyRef.current !== pipelineBusy) {
|
||||
// pipelineBusy state has changed, trigger immediate refresh
|
||||
if (currentTab === 'documents' && health && isMountedRef.current) {
|
||||
handleManualRefresh();
|
||||
|
||||
// Reset polling timer after manual refresh
|
||||
const hasActiveDocuments = (statusCounts.processing || 0) > 0 || (statusCounts.pending || 0) > 0;
|
||||
const pollingInterval = hasActiveDocuments ? 5000 : 30000;
|
||||
startPollingInterval(pollingInterval);
|
||||
}
|
||||
}
|
||||
// Update the previous state
|
||||
prevPipelineBusyRef.current = pipelineBusy;
|
||||
}, [pipelineBusy, currentTab, health, handleManualRefresh, statusCounts.processing, statusCounts.pending, startPollingInterval]);
|
||||
|
||||
// Set up intelligent polling with dynamic interval based on document status
|
||||
useEffect(() => {
|
||||
if (currentTab !== 'documents' || !health) {
|
||||
clearPollingInterval();
|
||||
return
|
||||
}
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
// Only perform fetch if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
await fetchDocuments()
|
||||
}
|
||||
} catch (err) {
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) }))
|
||||
}
|
||||
}
|
||||
}, 5000)
|
||||
// Determine polling interval based on document status
|
||||
const hasActiveDocuments = (statusCounts.processing || 0) > 0 || (statusCounts.pending || 0) > 0;
|
||||
const pollingInterval = hasActiveDocuments ? 5000 : 30000; // 5s if active, 30s if idle
|
||||
|
||||
startPollingInterval(pollingInterval);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
clearPollingInterval();
|
||||
}
|
||||
}, [health, fetchDocuments, t, currentTab])
|
||||
}, [health, t, currentTab, statusCounts.processing, statusCounts.pending, startPollingInterval, clearPollingInterval])
|
||||
|
||||
// Monitor docs changes to check status counts and trigger health check if needed
|
||||
useEffect(() => {
|
||||
|
|
@ -486,16 +679,73 @@ export default function DocumentManager() {
|
|||
prevStatusCounts.current = newStatusCounts
|
||||
}, [docs]);
|
||||
|
||||
// Handle page change - only update state
|
||||
const handlePageChange = useCallback((newPage: number) => {
|
||||
if (newPage === pagination.page) return;
|
||||
|
||||
// Save the new page for current status filter
|
||||
setPageByStatus(prev => ({ ...prev, [statusFilter]: newPage }));
|
||||
setPagination(prev => ({ ...prev, page: newPage }));
|
||||
}, [pagination.page, statusFilter]);
|
||||
|
||||
// Handle status filter change - only update state
|
||||
const handleStatusFilterChange = useCallback((newStatusFilter: StatusFilter) => {
|
||||
if (newStatusFilter === statusFilter) return;
|
||||
|
||||
// Save current page for the current status filter
|
||||
setPageByStatus(prev => ({ ...prev, [statusFilter]: pagination.page }));
|
||||
|
||||
// Get the saved page for the new status filter
|
||||
const newPage = pageByStatus[newStatusFilter];
|
||||
|
||||
// Update status filter and restore the saved page
|
||||
setStatusFilter(newStatusFilter);
|
||||
setPagination(prev => ({ ...prev, page: newPage }));
|
||||
}, [statusFilter, pagination.page, pageByStatus]);
|
||||
|
||||
// Handle documents deleted callback
|
||||
const handleDocumentsDeleted = useCallback(async () => {
|
||||
setSelectedDocIds([])
|
||||
await fetchDocuments()
|
||||
}, [fetchDocuments])
|
||||
|
||||
// Add dependency on sort state to re-render when sort changes
|
||||
// Reset health check timer with 1 second delay to avoid race condition
|
||||
useBackendState.getState().resetHealthCheckTimerDelayed(1000)
|
||||
|
||||
// Schedule a health check 2 seconds after successful clear
|
||||
startPollingInterval(2000)
|
||||
}, [startPollingInterval])
|
||||
|
||||
// Handle documents cleared callback with delayed health check timer reset
|
||||
const handleDocumentsCleared = useCallback(async () => {
|
||||
// Schedule a health check 0.5 seconds after successful clear
|
||||
startPollingInterval(500)
|
||||
}, [startPollingInterval])
|
||||
|
||||
|
||||
// Handle showFileName change - switch sort field if currently sorting by first column
|
||||
useEffect(() => {
|
||||
// This effect ensures the component re-renders when sort state changes
|
||||
}, [sortField, sortDirection]);
|
||||
// Only switch if currently sorting by the first column (id or file_path)
|
||||
if (sortField === 'id' || sortField === 'file_path') {
|
||||
const newSortField = showFileName ? 'file_path' : 'id';
|
||||
if (sortField !== newSortField) {
|
||||
setSortField(newSortField);
|
||||
}
|
||||
}
|
||||
}, [showFileName, sortField]);
|
||||
|
||||
// Central effect to handle all data fetching
|
||||
useEffect(() => {
|
||||
if (currentTab === 'documents') {
|
||||
fetchPaginatedDocuments(pagination.page, pagination.page_size, statusFilter);
|
||||
}
|
||||
}, [
|
||||
currentTab,
|
||||
pagination.page,
|
||||
pagination.page_size,
|
||||
statusFilter,
|
||||
sortField,
|
||||
sortDirection,
|
||||
fetchPaginatedDocuments
|
||||
]);
|
||||
|
||||
return (
|
||||
<Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
|
||||
|
|
@ -503,7 +753,7 @@ export default function DocumentManager() {
|
|||
<CardTitle className="text-lg">{t('documentPanel.documentManager.title')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 flex flex-col min-h-0 overflow-auto">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<div className="flex justify-between items-center gap-2 mb-2">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -527,27 +777,43 @@ export default function DocumentManager() {
|
|||
<ActivityIcon /> {t('documentPanel.documentManager.pipelineStatusButton')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
{isSelectionMode && (
|
||||
<DeleteDocumentsDialog
|
||||
selectedDocIds={selectedDocIds}
|
||||
totalCompletedCount={documentCounts.processed || 0}
|
||||
onDocumentsDeleted={handleDocumentsDeleted}
|
||||
|
||||
{/* Pagination Controls in the middle */}
|
||||
{pagination.total_pages > 1 && (
|
||||
<PaginationControls
|
||||
currentPage={pagination.page}
|
||||
totalPages={pagination.total_pages}
|
||||
pageSize={pagination.page_size}
|
||||
totalCount={pagination.total_count}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
isLoading={isRefreshing}
|
||||
compact={true}
|
||||
/>
|
||||
)}
|
||||
{isSelectionMode ? (
|
||||
<DeselectDocumentsDialog
|
||||
selectedCount={selectedDocIds.length}
|
||||
onDeselect={handleDeselectAll}
|
||||
|
||||
<div className="flex gap-2">
|
||||
{isSelectionMode && (
|
||||
<DeleteDocumentsDialog
|
||||
selectedDocIds={selectedDocIds}
|
||||
totalCompletedCount={documentCounts.processed || 0}
|
||||
onDocumentsDeleted={handleDocumentsDeleted}
|
||||
/>
|
||||
)}
|
||||
{isSelectionMode ? (
|
||||
<DeselectDocumentsDialog
|
||||
selectedCount={selectedDocIds.length}
|
||||
onDeselect={handleDeselectAll}
|
||||
/>
|
||||
) : (
|
||||
<ClearDocumentsDialog onDocumentsCleared={handleDocumentsCleared} />
|
||||
)}
|
||||
<UploadDocumentsDialog onDocumentsUploaded={fetchDocuments} />
|
||||
<PipelineStatusDialog
|
||||
open={showPipelineStatus}
|
||||
onOpenChange={setShowPipelineStatus}
|
||||
/>
|
||||
) : (
|
||||
<ClearDocumentsDialog onDocumentsCleared={fetchDocuments} />
|
||||
)}
|
||||
<UploadDocumentsDialog onDocumentsUploaded={fetchDocuments} />
|
||||
<PipelineStatusDialog
|
||||
open={showPipelineStatus}
|
||||
onOpenChange={setShowPipelineStatus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="flex-1 flex flex-col border rounded-md min-h-0 mb-2">
|
||||
|
|
@ -555,63 +821,77 @@ export default function DocumentManager() {
|
|||
<div className="flex justify-between items-center">
|
||||
<CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<FilterIcon className="h-4 w-4" />
|
||||
<div className="flex gap-1" dir={i18n.dir()}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'all' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('all')}
|
||||
onClick={() => handleStatusFilterChange('all')}
|
||||
disabled={isRefreshing}
|
||||
className={cn(
|
||||
statusFilter === 'all' && 'bg-gray-100 dark:bg-gray-900 font-medium border border-gray-400 dark:border-gray-500 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.all')} ({documentCounts.all})
|
||||
{t('documentPanel.documentManager.status.all')} ({statusCounts.all || documentCounts.all})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'processed' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('processed')}
|
||||
onClick={() => handleStatusFilterChange('processed')}
|
||||
disabled={isRefreshing}
|
||||
className={cn(
|
||||
documentCounts.processed > 0 ? 'text-green-600' : 'text-gray-500',
|
||||
(statusCounts.PROCESSED || statusCounts.processed || documentCounts.processed) > 0 ? 'text-green-600' : 'text-gray-500',
|
||||
statusFilter === 'processed' && 'bg-green-100 dark:bg-green-900/30 font-medium border border-green-400 dark:border-green-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.completed')} ({documentCounts.processed || 0})
|
||||
{t('documentPanel.documentManager.status.completed')} ({statusCounts.PROCESSED || statusCounts.processed || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'processing' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('processing')}
|
||||
onClick={() => handleStatusFilterChange('processing')}
|
||||
disabled={isRefreshing}
|
||||
className={cn(
|
||||
documentCounts.processing > 0 ? 'text-blue-600' : 'text-gray-500',
|
||||
(statusCounts.PROCESSING || statusCounts.processing || documentCounts.processing) > 0 ? 'text-blue-600' : 'text-gray-500',
|
||||
statusFilter === 'processing' && 'bg-blue-100 dark:bg-blue-900/30 font-medium border border-blue-400 dark:border-blue-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.processing')} ({documentCounts.processing || 0})
|
||||
{t('documentPanel.documentManager.status.processing')} ({statusCounts.PROCESSING || statusCounts.processing || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'pending' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('pending')}
|
||||
onClick={() => handleStatusFilterChange('pending')}
|
||||
disabled={isRefreshing}
|
||||
className={cn(
|
||||
documentCounts.pending > 0 ? 'text-yellow-600' : 'text-gray-500',
|
||||
(statusCounts.PENDING || statusCounts.pending || documentCounts.pending) > 0 ? 'text-yellow-600' : 'text-gray-500',
|
||||
statusFilter === 'pending' && 'bg-yellow-100 dark:bg-yellow-900/30 font-medium border border-yellow-400 dark:border-yellow-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.pending')} ({documentCounts.pending || 0})
|
||||
{t('documentPanel.documentManager.status.pending')} ({statusCounts.PENDING || statusCounts.pending || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'failed' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('failed')}
|
||||
onClick={() => handleStatusFilterChange('failed')}
|
||||
disabled={isRefreshing}
|
||||
className={cn(
|
||||
documentCounts.failed > 0 ? 'text-red-600' : 'text-gray-500',
|
||||
(statusCounts.FAILED || statusCounts.failed || documentCounts.failed) > 0 ? 'text-red-600' : 'text-gray-500',
|
||||
statusFilter === 'failed' && 'bg-red-100 dark:bg-red-900/30 font-medium border border-red-400 dark:border-red-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.failed')} ({documentCounts.failed || 0})
|
||||
{t('documentPanel.documentManager.status.failed')} ({statusCounts.FAILED || statusCounts.failed || 0})
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleManualRefresh}
|
||||
disabled={isRefreshing}
|
||||
side="bottom"
|
||||
tooltip={t('documentPanel.documentManager.refreshTooltip')}
|
||||
>
|
||||
<RotateCcwIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<label
|
||||
|
|
@ -657,8 +937,11 @@ export default function DocumentManager() {
|
|||
className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 select-none"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{t('documentPanel.documentManager.columns.id')}
|
||||
{sortField === 'id' && (
|
||||
{showFileName
|
||||
? t('documentPanel.documentManager.columns.fileName')
|
||||
: t('documentPanel.documentManager.columns.id')
|
||||
}
|
||||
{((sortField === 'id' && !showFileName) || (sortField === 'file_path' && showFileName)) && (
|
||||
<span className="ml-1">
|
||||
{sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
"title": "إدارة المستندات",
|
||||
"scanButton": "مسح ضوئي",
|
||||
"scanTooltip": "مسح المستندات ضوئيًا في مجلد الإدخال",
|
||||
"refreshTooltip": "إعادة تعيين قائمة المستندات",
|
||||
"pipelineStatusButton": "حالة خط المعالجة",
|
||||
"pipelineStatusTooltip": "عرض حالة خط المعالجة",
|
||||
"uploadedTitle": "المستندات المرفوعة",
|
||||
|
|
@ -125,6 +126,7 @@
|
|||
"emptyDescription": "لا توجد مستندات مرفوعة بعد.",
|
||||
"columns": {
|
||||
"id": "المعرف",
|
||||
"fileName": "اسم الملف",
|
||||
"summary": "الملخص",
|
||||
"status": "الحالة",
|
||||
"length": "الطول",
|
||||
|
|
@ -401,5 +403,14 @@
|
|||
"description": "الرجاء إدخال مفتاح واجهة برمجة التطبيقات للوصول إلى الخدمة",
|
||||
"placeholder": "أدخل مفتاح واجهة برمجة التطبيقات",
|
||||
"save": "حفظ"
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "عرض {{start}} إلى {{end}} من أصل {{total}} إدخالات",
|
||||
"page": "الصفحة",
|
||||
"pageSize": "حجم الصفحة",
|
||||
"firstPage": "الصفحة الأولى",
|
||||
"prevPage": "الصفحة السابقة",
|
||||
"nextPage": "الصفحة التالية",
|
||||
"lastPage": "الصفحة الأخيرة"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
"title": "Document Management",
|
||||
"scanButton": "Scan",
|
||||
"scanTooltip": "Scan documents in input folder",
|
||||
"refreshTooltip": "Reset document list",
|
||||
"pipelineStatusButton": "Pipeline Status",
|
||||
"pipelineStatusTooltip": "View pipeline status",
|
||||
"uploadedTitle": "Uploaded Documents",
|
||||
|
|
@ -125,6 +126,7 @@
|
|||
"emptyDescription": "There are no uploaded documents yet.",
|
||||
"columns": {
|
||||
"id": "ID",
|
||||
"fileName": "File Name",
|
||||
"summary": "Summary",
|
||||
"status": "Status",
|
||||
"length": "Length",
|
||||
|
|
@ -401,5 +403,14 @@
|
|||
"description": "Please enter your API key to access the service",
|
||||
"placeholder": "Enter your API key",
|
||||
"save": "Save"
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "Showing {{start}} to {{end}} of {{total}} entries",
|
||||
"page": "Page",
|
||||
"pageSize": "Page Size",
|
||||
"firstPage": "First Page",
|
||||
"prevPage": "Previous Page",
|
||||
"nextPage": "Next Page",
|
||||
"lastPage": "Last Page"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
"title": "Gestion des documents",
|
||||
"scanButton": "Scanner",
|
||||
"scanTooltip": "Scanner les documents dans le dossier d'entrée",
|
||||
"refreshTooltip": "Réinitialiser la liste des documents",
|
||||
"pipelineStatusButton": "État du Pipeline",
|
||||
"pipelineStatusTooltip": "Voir l'état du pipeline",
|
||||
"uploadedTitle": "Documents téléchargés",
|
||||
|
|
@ -125,6 +126,7 @@
|
|||
"emptyDescription": "Il n'y a pas encore de documents téléchargés.",
|
||||
"columns": {
|
||||
"id": "ID",
|
||||
"fileName": "Nom du fichier",
|
||||
"summary": "Résumé",
|
||||
"status": "Statut",
|
||||
"length": "Longueur",
|
||||
|
|
@ -401,5 +403,14 @@
|
|||
"description": "Veuillez entrer votre clé API pour accéder au service",
|
||||
"placeholder": "Entrez votre clé API",
|
||||
"save": "Sauvegarder"
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "Affichage de {{start}} à {{end}} sur {{total}} entrées",
|
||||
"page": "Page",
|
||||
"pageSize": "Taille de la page",
|
||||
"firstPage": "Première page",
|
||||
"prevPage": "Page précédente",
|
||||
"nextPage": "Page suivante",
|
||||
"lastPage": "Dernière page"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
"title": "文档管理",
|
||||
"scanButton": "扫描",
|
||||
"scanTooltip": "扫描输入目录中的文档",
|
||||
"refreshTooltip": "复位文档清单",
|
||||
"pipelineStatusButton": "流水线状态",
|
||||
"pipelineStatusTooltip": "查看流水线状态",
|
||||
"uploadedTitle": "已上传文档",
|
||||
|
|
@ -125,6 +126,7 @@
|
|||
"emptyDescription": "还没有上传任何文档",
|
||||
"columns": {
|
||||
"id": "ID",
|
||||
"fileName": "文件名",
|
||||
"summary": "摘要",
|
||||
"status": "状态",
|
||||
"length": "长度",
|
||||
|
|
@ -401,5 +403,14 @@
|
|||
"description": "请输入您的 API Key 以访问服务",
|
||||
"placeholder": "请输入 API Key",
|
||||
"save": "保存"
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "显示第 {{start}} 到 {{end}} 条,共 {{total}} 条记录",
|
||||
"page": "页",
|
||||
"pageSize": "每页显示",
|
||||
"firstPage": "首页",
|
||||
"prevPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"lastPage": "末页"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
"title": "文件管理",
|
||||
"scanButton": "掃描",
|
||||
"scanTooltip": "掃描輸入目錄中的文件",
|
||||
"refreshTooltip": "重設文件清單",
|
||||
"pipelineStatusButton": "pipeline 狀態",
|
||||
"pipelineStatusTooltip": "查看pipeline 狀態",
|
||||
"uploadedTitle": "已上傳文件",
|
||||
|
|
@ -125,6 +126,7 @@
|
|||
"emptyDescription": "尚未上傳任何文件",
|
||||
"columns": {
|
||||
"id": "ID",
|
||||
"fileName": "檔案名稱",
|
||||
"summary": "摘要",
|
||||
"status": "狀態",
|
||||
"length": "長度",
|
||||
|
|
@ -401,5 +403,14 @@
|
|||
"description": "請輸入您的 API key 以存取服務",
|
||||
"placeholder": "請輸入 API key",
|
||||
"save": "儲存"
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "顯示第 {{start}} 到 {{end}} 筆,共 {{total}} 筆記錄",
|
||||
"page": "頁",
|
||||
"pageSize": "每頁顯示",
|
||||
"firstPage": "第一頁",
|
||||
"prevPage": "上一頁",
|
||||
"nextPage": "下一頁",
|
||||
"lastPage": "最後一頁"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ interface SettingsState {
|
|||
showFileName: boolean
|
||||
setShowFileName: (show: boolean) => void
|
||||
|
||||
documentsPageSize: number
|
||||
setDocumentsPageSize: (size: number) => void
|
||||
|
||||
// Graph viewer settings
|
||||
showPropertyPanel: boolean
|
||||
showNodeSearchBar: boolean
|
||||
|
|
@ -104,6 +107,7 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|||
|
||||
currentTab: 'documents',
|
||||
showFileName: false,
|
||||
documentsPageSize: 10,
|
||||
|
||||
retrievalHistory: [],
|
||||
|
||||
|
|
@ -187,12 +191,13 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|||
})),
|
||||
|
||||
setShowFileName: (show: boolean) => set({ showFileName: show }),
|
||||
setShowLegend: (show: boolean) => set({ showLegend: show })
|
||||
setShowLegend: (show: boolean) => set({ showLegend: show }),
|
||||
setDocumentsPageSize: (size: number) => set({ documentsPageSize: size })
|
||||
}),
|
||||
{
|
||||
name: 'settings-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
version: 15,
|
||||
version: 16,
|
||||
migrate: (state: any, version: number) => {
|
||||
if (version < 2) {
|
||||
state.showEdgeLabel = false
|
||||
|
|
@ -275,6 +280,10 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|||
history_turns: 0,
|
||||
}
|
||||
}
|
||||
if (version < 16) {
|
||||
// Add documentsPageSize field for older versions
|
||||
state.documentsPageSize = 10
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { create } from 'zustand'
|
|||
import { createSelectors } from '@/lib/utils'
|
||||
import { checkHealth, LightragStatus } from '@/api/lightrag'
|
||||
import { useSettingsStore } from './settings'
|
||||
import { healthCheckInterval } from '@/lib/constants'
|
||||
|
||||
interface BackendState {
|
||||
health: boolean
|
||||
|
|
@ -10,11 +11,18 @@ interface BackendState {
|
|||
status: LightragStatus | null
|
||||
lastCheckTime: number
|
||||
pipelineBusy: boolean
|
||||
healthCheckIntervalId: ReturnType<typeof setInterval> | null
|
||||
healthCheckFunction: (() => void) | null
|
||||
healthCheckIntervalValue: number
|
||||
|
||||
check: () => Promise<boolean>
|
||||
clear: () => void
|
||||
setErrorMessage: (message: string, messageTitle: string) => void
|
||||
setPipelineBusy: (busy: boolean) => void
|
||||
setHealthCheckFunction: (fn: () => void) => void
|
||||
resetHealthCheckTimer: () => void
|
||||
resetHealthCheckTimerDelayed: (delayMs: number) => void
|
||||
clearHealthCheckTimer: () => void
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
|
|
@ -32,13 +40,16 @@ interface AuthState {
|
|||
setCustomTitle: (webuiTitle: string | null, webuiDescription: string | null) => void;
|
||||
}
|
||||
|
||||
const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
||||
const useBackendStateStoreBase = create<BackendState>()((set, get) => ({
|
||||
health: true,
|
||||
message: null,
|
||||
messageTitle: null,
|
||||
lastCheckTime: Date.now(),
|
||||
status: null,
|
||||
pipelineBusy: false,
|
||||
healthCheckIntervalId: null,
|
||||
healthCheckFunction: null,
|
||||
healthCheckIntervalValue: healthCheckInterval * 1000, // Use constant from lib/constants
|
||||
|
||||
check: async () => {
|
||||
const health = await checkHealth()
|
||||
|
|
@ -108,6 +119,36 @@ const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
|||
|
||||
setPipelineBusy: (busy: boolean) => {
|
||||
set({ pipelineBusy: busy })
|
||||
},
|
||||
|
||||
setHealthCheckFunction: (fn: () => void) => {
|
||||
set({ healthCheckFunction: fn })
|
||||
},
|
||||
|
||||
resetHealthCheckTimer: () => {
|
||||
const { healthCheckIntervalId, healthCheckFunction, healthCheckIntervalValue } = get()
|
||||
if (healthCheckIntervalId) {
|
||||
clearInterval(healthCheckIntervalId)
|
||||
}
|
||||
if (healthCheckFunction) {
|
||||
healthCheckFunction() // run health check immediately
|
||||
const newIntervalId = setInterval(healthCheckFunction, healthCheckIntervalValue)
|
||||
set({ healthCheckIntervalId: newIntervalId })
|
||||
}
|
||||
},
|
||||
|
||||
resetHealthCheckTimerDelayed: (delayMs: number) => {
|
||||
setTimeout(() => {
|
||||
get().resetHealthCheckTimer()
|
||||
}, delayMs)
|
||||
},
|
||||
|
||||
clearHealthCheckTimer: () => {
|
||||
const { healthCheckIntervalId } = get()
|
||||
if (healthCheckIntervalId) {
|
||||
clearInterval(healthCheckIntervalId)
|
||||
set({ healthCheckIntervalId: null })
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
|
|
|
|||
251
paging.md
Normal file
251
paging.md
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
# 文档列表页面分页显示功能改造方案
|
||||
|
||||
## 一、改造目标
|
||||
|
||||
### 问题现状
|
||||
- 当前文档页面一次性加载所有文档,导致大量文档时界面加载慢
|
||||
- 前端内存占用过大,用户操作体验差
|
||||
- 状态过滤和排序都在前端进行,效率低下
|
||||
|
||||
### 改造目标
|
||||
- 实现后端分页查询,减少单次数据传输量
|
||||
- 添加分页控制组件,支持翻页和跳转功能
|
||||
- 允许用户设置每页显示行数(10-200条)
|
||||
- 保持现有状态过滤和排序功能不变
|
||||
- 提升大数据量场景下的性能表现
|
||||
|
||||
## 二、总体架构设计
|
||||
|
||||
### 设计原则
|
||||
1. **统一分页接口**:后端提供统一的分页API,支持状态过滤和排序
|
||||
2. **智能刷新策略**:根据处理状态选择合适的刷新频率和范围
|
||||
3. **即时用户反馈**:状态切换、分页操作提供立即响应
|
||||
4. **向后兼容**:保持现有功能完整性,不影响现有操作流程
|
||||
5. **性能优化**:减少内存占用,优化网络请求
|
||||
|
||||
### 技术方案
|
||||
- **后端**:在现有存储层基础上添加分页查询接口
|
||||
- **前端**:改造DocumentManager组件,添加分页控制
|
||||
- **数据流**:统一分页查询 + 独立状态计数查询
|
||||
|
||||
## 三、后端改造步骤
|
||||
|
||||
### 步骤1:存储层接口扩展
|
||||
|
||||
**改动文件**:`lightrag/kg/base.py`
|
||||
|
||||
**关键思路**:
|
||||
- 在BaseDocStatusStorage抽象类中添加分页查询方法
|
||||
- 设计统一的分页接口,支持状态过滤、排序、分页参数
|
||||
- 返回文档列表和总数量的元组
|
||||
|
||||
**接口设计要点**:
|
||||
```
|
||||
get_docs_paginated(status_filter, page, page_size, sort_field, sort_direction) -> (documents, total_count)
|
||||
count_by_status(status) -> int
|
||||
get_all_status_counts() -> Dict[str, int]
|
||||
```
|
||||
|
||||
### 步骤2:各存储后端实现
|
||||
|
||||
**改动文件**:
|
||||
- `lightrag/kg/postgres_impl.py`
|
||||
- `lightrag/kg/mongo_impl.py`
|
||||
- `lightrag/kg/redis_impl.py`
|
||||
- `lightrag/kg/json_doc_status_impl.py`
|
||||
|
||||
**PostgreSQL实现要点**:
|
||||
- 使用LIMIT和OFFSET实现分页
|
||||
- 构建动态WHERE条件支持状态过滤
|
||||
- 使用COUNT查询获取总数量
|
||||
- 添加合适的数据库索引优化查询性能
|
||||
|
||||
**MongoDB实现要点**:
|
||||
- 使用skip()和limit()实现分页
|
||||
- 使用聚合管道进行状态统计
|
||||
- 优化查询条件和索引
|
||||
|
||||
**Redis 与 Json实现要点:**
|
||||
|
||||
* 考虑先用简单的方式实现,即把所有文件清单读到内存中后进行过滤和排序
|
||||
|
||||
**关键考虑**:
|
||||
|
||||
- 确保各存储后端的分页逻辑一致性
|
||||
- 处理边界情况(空结果、超出页码范围等)
|
||||
- 优化查询性能,避免全表扫描
|
||||
|
||||
### 步骤3:API路由层改造
|
||||
|
||||
**改动文件**:`lightrag/api/routers/document_routes.py`
|
||||
|
||||
**新增接口**:
|
||||
1. `POST /documents/paginated` - 分页查询文档
|
||||
2. `GET /documents/status_counts` - 获取状态计数
|
||||
|
||||
**数据模型设计**:
|
||||
- DocumentsRequest:分页请求参数
|
||||
- PaginatedDocsResponse:分页响应数据
|
||||
- PaginationInfo:分页元信息
|
||||
|
||||
**关键逻辑**:
|
||||
- 参数验证(页码范围、页面大小限制)
|
||||
- 并行查询分页数据和状态计数
|
||||
- 错误处理和异常响应
|
||||
|
||||
### 步骤4:数据库优化
|
||||
|
||||
**索引策略**:
|
||||
- 为workspace + status + updated_at创建复合索引
|
||||
- 为workspace + status + created_at创建复合索引
|
||||
- 为workspace + updated_at创建索引
|
||||
- 为workspace + created_at创建索引
|
||||
|
||||
**性能考虑**:
|
||||
- 避免深度分页的性能问题
|
||||
- 考虑添加缓存层优化状态计数查询
|
||||
- 监控查询性能,必要时调整索引策略
|
||||
|
||||
## 四、前端改造步骤
|
||||
|
||||
### 步骤1:API客户端扩展
|
||||
|
||||
**改动文件**:`lightrag_webui/src/api/lightrag.ts`
|
||||
|
||||
**新增函数**:
|
||||
- `getDocumentsPaginated()` - 分页查询文档
|
||||
- `getDocumentStatusCounts()` - 获取状态计数
|
||||
|
||||
**类型定义**:
|
||||
- 定义分页请求和响应的TypeScript类型
|
||||
- 确保类型安全和代码提示
|
||||
|
||||
### 步骤2:分页控制组件开发
|
||||
|
||||
**新增文件**:`lightrag_webui/src/components/ui/PaginationControls.tsx`
|
||||
|
||||
**组件功能**:
|
||||
- 支持紧凑模式和完整模式
|
||||
- 页码输入和跳转功能
|
||||
- 每页显示数量选择(10-200)
|
||||
- 总数信息显示
|
||||
- 禁用状态处理
|
||||
|
||||
**设计要点**:
|
||||
- 响应式设计,适配不同屏幕尺寸
|
||||
- 防抖处理,避免频繁请求
|
||||
- 错误处理和状态回滚
|
||||
- 组件摆放位置:目前状态按钮上方,与scan按钮同一层,居中摆放
|
||||
|
||||
### 步骤3:状态过滤按钮优化
|
||||
|
||||
**改动文件**:现有状态过滤相关组件
|
||||
|
||||
**优化要点**:
|
||||
|
||||
- 添加加载状态指示
|
||||
- 数据不足时的智能提示
|
||||
- 定期刷新数据,状态切换时如果最先的状态数据距离上次刷新数据超过5秒应即时刷新数据
|
||||
- 防止重复点击和并发请求
|
||||
|
||||
### 步骤4:主组件DocumentManager改造
|
||||
|
||||
**改动文件**:`lightrag_webui/src/features/DocumentManager.tsx`
|
||||
|
||||
**核心改动**:
|
||||
|
||||
**状态管理重构**:
|
||||
- 将docs状态改为currentPageDocs(仅存储当前页数据)
|
||||
- 添加pagination状态管理分页信息
|
||||
- 添加statusCounts状态独立管理状态计数
|
||||
- 添加加载状态管理(isStatusChanging, isRefreshing)
|
||||
|
||||
**数据获取策略**:
|
||||
- 实现智能刷新:活跃期完整刷新,稳定期轻量刷新
|
||||
- 状态切换时立即刷新数据
|
||||
- 分页操作时立即更新数据
|
||||
- 定期刷新与手动操作协调
|
||||
|
||||
**布局调整**:
|
||||
- 将分页控制组件放置在顶部操作栏中间位置
|
||||
- 保持状态过滤按钮在表格上方
|
||||
- 确保响应式布局适配
|
||||
|
||||
**事件处理优化**:
|
||||
- 状态切换时,如果当前页码数据不足,则重置到第一页
|
||||
- 页面大小变更时智能计算新页码
|
||||
- 错误时状态回滚机制
|
||||
|
||||
## 五、用户体验优化
|
||||
|
||||
### 即时反馈机制
|
||||
- 状态切换时显示加载动画
|
||||
- 分页操作时提供视觉反馈
|
||||
- 数据不足时智能提示用户
|
||||
|
||||
### 错误处理策略
|
||||
- 网络错误时自动重试
|
||||
- 操作失败时状态回滚
|
||||
- 友好的错误提示信息
|
||||
|
||||
### 性能优化措施
|
||||
- 防抖处理频繁操作
|
||||
- 智能刷新策略减少不必要请求
|
||||
- 组件卸载时清理定时器和请求
|
||||
|
||||
## 六、兼容性保障
|
||||
|
||||
### 向后兼容
|
||||
- 保留原有的/documents接口作为备用
|
||||
- 现有功能(排序、过滤、选择)保持不变
|
||||
- 渐进式升级,支持配置开关
|
||||
|
||||
### 数据一致性
|
||||
- 确保分页数据与状态计数同步
|
||||
- 处理并发更新的数据一致性问题
|
||||
- 定期刷新保持数据最新
|
||||
|
||||
## 七、测试策略
|
||||
|
||||
### 功能测试
|
||||
- 各种分页场景测试
|
||||
- 状态过滤组合测试
|
||||
- 排序功能验证
|
||||
- 边界条件测试
|
||||
|
||||
### 性能测试
|
||||
- 大数据量场景测试
|
||||
- 并发访问压力测试
|
||||
- 内存使用情况监控
|
||||
- 响应时间测试
|
||||
|
||||
### 兼容性测试
|
||||
- 不同存储后端测试
|
||||
- 不同浏览器兼容性
|
||||
- 移动端响应式测试
|
||||
|
||||
## 八、关键实现细节
|
||||
|
||||
### 后端分页查询设计
|
||||
- **统一接口**:所有存储后端实现相同的分页接口签名
|
||||
- **参数验证**:严格验证页码、页面大小、排序参数的合法性
|
||||
- **性能优化**:使用数据库原生分页功能,避免应用层分页
|
||||
- **错误处理**:统一的错误响应格式和异常处理机制
|
||||
|
||||
### 前端状态管理策略
|
||||
- **数据分离**:当前页数据与状态计数分别管理
|
||||
- **智能刷新**:根据文档处理状态选择刷新策略
|
||||
- **状态同步**:确保UI状态与后端数据保持一致
|
||||
- **错误恢复**:操作失败时自动回滚到之前状态
|
||||
|
||||
### 分页控制组件设计
|
||||
- **紧凑布局**:适配顶部操作栏的空间限制
|
||||
- **响应式设计**:在不同屏幕尺寸下自适应布局
|
||||
- **交互优化**:防抖处理、加载状态、禁用状态管理
|
||||
- **可访问性**:支持键盘导航和屏幕阅读器
|
||||
|
||||
### 数据库索引优化
|
||||
- **复合索引**:workspace + status + sort_field的组合索引
|
||||
- **覆盖索引**:尽可能使用覆盖索引减少回表查询
|
||||
- **索引监控**:定期监控索引使用情况和查询性能
|
||||
- **渐进优化**:根据实际使用情况调整索引策略
|
||||
Loading…
Add table
Reference in a new issue