Nothing
## UI components for content analysis in Biblioshiny ----
#' Content Analysis Tab Item with two main tabs for Biblioshiny UI
#'
#' This creates a tabItem with 2 main tabs:
#' 1. Import PDF File - Contains the PDF upload interface and analysis results (with 3 sub-tabs)
#' 2. Info & References - Contains help documentation
#'
#' @param id Character, the tab item ID (default: "content_analysis")
#' @return A tabItem object for biblioshiny
content_analysis_tab <- function(id = "content_analysis") {
tabItem(
tabName = id,
# Main tabsetPanel with Import and Info & References
tabsetPanel(
id = "content_analysis_main_tabs",
type = "tabs",
# ===========================================
# MAIN TAB 1: IMPORT PDF FILE
# ===========================================
tabPanel(
title = "Import and Analyze PDF File",
value = "tab_import_pdf",
icon = icon("upload"),
br(),
# Tab header
fluidRow(
column(
7,
div(
class = "page-header",
h2(
"Scientific Article Content Analysis",
style = "color: #2E86AB; margin-bottom: 10px;"
),
p(
"Upload a PDF file and analyze citation patterns, context, and co-occurrence networks.",
style = "color: #666; font-size: 16px; margin-bottom: 20px;"
)
)
),
column(
5,
div(
style = "text-align: right; padding-top: 15px;",
# Document metadata display (shown after text extraction)
conditionalPanel(
condition = "output.metadata_available",
uiOutput("pdf_metadata_display")
),
# Export button (shown after analysis completion)
conditionalPanel(
condition = "output.analysis_completed",
downloadButton(
"download_all_results",
"Export All Results",
icon = icon("file-excel"),
class = "btn-success",
style = "font-weight: bold; margin-top: 10px;"
)
)
)
)
),
fluidRow(
# ===========================================
# RIGHT PANEL: TABBED RESULTS (column 10)
# ===========================================
column(
10,
# NEW: Button to open modal and DOI field (visible only after extraction)
conditionalPanel(
condition = "output.text_extracted && !output.analysis_completed",
div(
style = "margin-bottom: 15px; padding: 15px; background-color: #e8f4f8; border-radius: 5px;",
# First row: preview button and message
fluidRow(
column(
6,
align = "center",
actionButton(
"open_preview_btn",
"View Extracted Text & PDF Preview",
icon = icon("file-alt"),
class = "btn-info",
onclick = "$('#previewModal').modal('show');",
style = "width: 70%;"
)
),
column(
6,
align = "center",
downloadButton(
"save_text_file",
"Save Extracted Text",
icon = icon("save"),
class = "btn-success",
style = "width: 70%;"
)
)
),
fluidRow(
column(
12,
div(
style = "margin-top: 10px;",
span(
icon("check-circle", style = "color: #27ae60;"),
" Text extracted successfully!",
style = "color: #555; font-weight: 500;"
)
),
helpText(
"💾 The 'Save' button will save the text with DOI and citation format info for easy reloading.",
style = "margin-top: 5px; font-size: 11px; color: #666; font-style: italic;"
)
)
),
# Second row: DOI field
fluidRow(
style = "margin-top: 15px;",
column(
12,
div(
style = "background-color: white; padding: 10px; border-radius: 4px; border: 1px solid #d1e7f0;",
fluidRow(
column(
2,
tags$label(
"Document DOI:",
style = "margin-top: 8px; font-weight: bold; color: #2E86AB;"
)
),
column(
4,
textInput(
"pdf_doi_input",
label = NULL,
value = "",
placeholder = "Enter or edit DOI (e.g., 10.1234/example)",
width = "100%"
)
),
column(
6,
div(
style = "margin-top: 8px;",
uiOutput("doi_status_icon")
)
)
),
helpText(
"The DOI is used to fetch reference details from Crossref. Edit if needed or add manually if not detected.",
style = "margin-bottom: 0px; margin-top: -5px; font-size: 11px; color: #666;"
)
)
)
)
)
),
conditionalPanel(
condition = "output.analysis_completed",
# NEW: Info after analysis
div(
style = "margin-bottom: 10px;",
fluidRow(
column(
4,
actionButton(
"reopen_preview_btn",
"View Original Text",
icon = icon("file-alt"),
class = "btn-default btn-sm",
onclick = "$('#previewModal').modal('show');"
)
),
column(
8,
div(
style = "text-align: right; padding-top: 5px;",
tags$strong("DOI: ", style = "color: #2E86AB;"),
tags$span(
textOutput("doi_display_after_analysis", inline = TRUE),
style = "font-family: 'Courier New', monospace; color: #555; background-color: #f8f9fa; padding: 4px 8px; border-radius: 3px; border: 1px solid #dee2e6;"
)
)
)
)
),
tabsetPanel(
id = "content_tabs",
type = "tabs",
# ===========================================
# TAB 1: DESCRIPTIVE STATISTICS
# ===========================================
tabPanel(
title = "Descriptive Statistics",
value = "tab_stats",
br(),
# Summary Statistics Cards
fluidRow(
column(
3,
div(
class = "info-box bg-aqua",
span(class = "info-box-icon", icon("file-text")),
div(
class = "info-box-content",
span("Total Words", class = "info-box-text"),
span(
textOutput("total_words", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-green",
span(class = "info-box-icon", icon("quote-right")),
div(
class = "info-box-content",
span("Citations Found", class = "info-box-text"),
span(
textOutput("total_citations", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-yellow",
span(class = "info-box-icon", icon("users")),
div(
class = "info-box-content",
span("Narrative Citations", class = "info-box-text"),
span(
textOutput("narrative_citations", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-red",
span(class = "info-box-icon", icon("chart-line")),
div(
class = "info-box-content",
span("Citation Density", class = "info-box-text"),
span(
textOutput("citation_density", inline = TRUE),
class = "info-box-number"
),
span("/1000 words", class = "info-box-more")
)
)
)
),
fluidRow(
column(
3,
div(
class = "info-box bg-purple",
span(class = "info-box-icon", icon("graduation-cap")),
div(
class = "info-box-content",
span("Flesch-Kincaid Grade", class = "info-box-text"),
span(
textOutput("flesch_kincaid_grade", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-blue",
span(class = "info-box-icon", icon("book-reader")),
div(
class = "info-box-content",
span("Reading Ease", class = "info-box-text"),
span(
textOutput("flesch_reading_ease", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-teal",
span(class = "info-box-icon", icon("robot")),
div(
class = "info-box-content",
span("ARI Index", class = "info-box-text"),
span(
textOutput("ari_index", inline = TRUE),
class = "info-box-number"
)
)
)
),
column(
3,
div(
class = "info-box bg-maroon",
span(class = "info-box-icon", icon("cloud")),
div(
class = "info-box-content",
span("Gunning Fog Index", class = "info-box-text"),
span(
textOutput("gunning_fog_index", inline = TRUE),
class = "info-box-number"
)
)
)
)
),
# Readability Indices Details
fluidRow(
column(
6,
div(
class = "box box-info",
div(
class = "box-header with-border",
h5("Readability Indices", style = "color: #3498db;")
),
div(
class = "box-body",
conditionalPanel(
condition = "output.analysis_completed",
uiOutput("readability_details_html")
),
conditionalPanel(
condition = "!output.analysis_completed",
div(
style = "text-align: center; padding: 20px; color: #999;",
p(
"Readability indices will appear here after analysis."
)
)
)
)
)
),
column(
6,
div(
class = "box box-primary",
div(
class = "box-header with-border",
h5("Text Statistics", style = "color: #2E86AB;")
),
div(
class = "box-body",
conditionalPanel(
condition = "output.analysis_completed",
uiOutput("text_stats")
),
conditionalPanel(
condition = "!output.analysis_completed",
div(
style = "text-align: center; padding: 20px; color: #999;",
p(
"Text statistics will appear here after analysis."
)
)
)
)
)
)
),
# Detailed Analysis Tables
fluidRow(
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"N-grams Analysis",
class = "box-title",
style = "color: #2E86AB;"
)
),
column(
4,
h5(
"Top Unigrams",
class = "box-title",
style = "color: #2E86AB;"
),
DT::dataTableOutput("frequent_words_table")
),
column(
4,
h5("Top Bigrams", style = "color: #2E86AB;"),
DT::dataTableOutput("bigrams_table")
),
column(
4,
h5("Top Trigrams", style = "color: #2E86AB;"),
DT::dataTableOutput("trigrams_table")
)
)
),
hr(),
# N-grams Analysis
fluidRow(
column(
12,
div(
class = "box-body",
fluidRow(
column(
6,
div(
class = "box box-primary",
h5(
"Citation Types Distribution",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-header with-border",
div(
class = "box-body",
DT::dataTableOutput("citation_types_table")
)
)
)
),
column(
6,
div(
class = "box box-success",
h5(
"Citations by Section",
class = "box-title",
style = "color: #27ae60;"
),
div(
class = "box-header with-border",
div(
class = "box-body",
DT::dataTableOutput("citation_sections_table")
)
)
)
)
)
)
)
)
),
# Add this new tabPanel AFTER the "Network Analysis" tab in the content_analysis_tab function
tabPanel(
title = "Word Trends",
value = "tab_trends",
br(),
# Word Selection and Configuration
fluidRow(
column(
12,
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"Word Distribution Analysis",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-tools pull-right",
downloadButton(
"download_word_trends",
"Export Data",
class = "btn btn-primary btn-sm",
icon = icon("download")
)
)
),
div(
class = "box-body",
fluidRow(
column(
4,
selectizeInput(
"trend_words",
"Select words to track:",
choices = NULL,
selected = NULL,
multiple = TRUE,
options = list(
placeholder = 'Type or select words...',
maxItems = 10,
plugins = list('remove_button')
)
),
helpText(
"Select up to 10 words from the most frequent terms in your document."
)
),
column(
3,
radioButtons(
"segmentation_type",
"Segmentation:",
choices = list(
"Auto (use sections if available)" = "auto",
"Document sections" = "sections",
"Equal-length segments" = "segments"
),
selected = "auto"
)
),
column(
2,
conditionalPanel(
condition = "input.segmentation_type == 'segments'",
numericInput(
"n_segments",
"Number of segments:",
value = 10,
min = 5,
max = 20,
step = 1
)
)
),
column(
3,
radioButtons(
"trend_plot_type",
"Visualization type:",
choices = list(
"Line chart" = "line",
"Area chart" = "area"
),
selected = "line"
),
checkboxInput(
"trend_show_points",
"Show data points",
value = TRUE
)
# ,checkboxInput("trend_smooth",
# "Smooth lines",
# value = TRUE
# )
)
),
hr(),
fluidRow(
column(
12,
actionButton(
"update_trends",
"Update Visualization",
icon = icon("refresh"),
class = "btn-info",
style = "margin-bottom: 10px;"
),
conditionalPanel(
condition = "input.segmentation_type == 'sections' && !output.sections_available",
div(
style = "background-color: #fff3cd; padding: 10px; border-radius: 5px; margin-top: 10px; border-left: 4px solid #ffc107;",
icon(
"exclamation-triangle",
style = "color: #856404;"
),
span(
" Document sections are not available. The analysis will use equal-length segments instead.",
style = "color: #856404; margin-left: 8px;"
)
)
)
)
)
)
)
)
),
# Visualization
fluidRow(
column(
12,
div(
class = "box box-success",
div(
class = "box-header with-border",
h4(
"Word Distribution Over Document",
class = "box-title",
style = "color: #27ae60;"
)
),
div(
class = "box-body",
conditionalPanel(
condition = "output.trends_available",
plotlyOutput("word_trends_plot", height = "600px")
),
conditionalPanel(
condition = "!output.trends_available",
div(
style = "text-align: center; padding: 60px; color: #999;",
icon(
"chart-line",
style = "font-size: 48px; margin-bottom: 20px;"
),
h4(
"No visualization available",
style = "color: #666;"
),
p(
"Select words from the list above and click 'Update Visualization' to see their distribution across the document.",
style = "font-size: 14px;"
)
)
)
)
)
)
),
# Statistics Table
fluidRow(
column(
12,
div(
class = "box box-info",
div(
class = "box-header with-border",
h4(
"Distribution Statistics",
class = "box-title",
style = "color: #3498db;"
)
),
div(
class = "box-body",
conditionalPanel(
condition = "output.trends_available",
DT::dataTableOutput("word_trends_table")
),
conditionalPanel(
condition = "!output.trends_available",
div(
style = "text-align: center; padding: 20px; color: #999;",
p(
"Statistics will appear here after visualization is generated."
)
)
)
)
)
)
)
),
# ===========================================
# TAB 2: IN-CONTEXT CITATION ANALYSIS
# ===========================================
tabPanel(
title = "In-Context Citations",
value = "tab_contexts",
br(),
# Search and Filter Controls - SINGLE ROW
fluidRow(
column(
12,
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"In-Context Citation Analysis",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-tools pull-right",
downloadButton(
"download_contexts",
"Export Contexts",
class = "btn btn-primary btn-sm",
icon = icon("download")
)
)
),
div(
class = "box-body",
# SINGLE ROW with all controls
fluidRow(
column(
4,
textInput(
"context_search",
"Search in contexts:",
placeholder = "Type to search citations or context...",
width = "100%"
)
),
column(
2,
numericInput(
"context_min_words",
"Min context words:",
value = 10,
min = 5,
max = 100,
width = "100%"
)
),
column(
2,
radioButtons(
"citation_segmentation",
"Group citations by:",
choices = list(
"Auto (use sections if available)" = "auto",
"Document sections" = "sections",
"Equal-length segments" = "segments"
),
selected = "auto",
inline = FALSE
)
),
column(
2,
conditionalPanel(
condition = "input.citation_segmentation == 'segments'",
numericInput(
"n_segments_citations",
"Number of segments:",
value = 10,
min = 5,
max = 20,
step = 1
)
)
),
column(
2,
br(),
actionButton(
"update_citation_grouping",
"Update Grouping",
icon = icon("refresh"),
class = "btn-info btn-block",
style = "margin-top: 5px;"
),
conditionalPanel(
condition = "input.citation_segmentation == 'sections' && !output.sections_available",
div(
style = "background-color: #fff3cd; padding: 5px; border-radius: 3px; margin-top: 8px; border-left: 3px solid #ffc107; font-size: 11px;",
icon(
"exclamation-triangle",
style = "color: #856404; font-size: 10px;"
),
span(
" Sections not available",
style = "color: #856404; margin-left: 5px;"
)
)
),
conditionalPanel(
condition = "$('html').hasClass('shiny-busy')",
div(
style = "margin-top: 8px; text-align: center;",
icon(
"spinner",
class = "fa-spin",
style = "color: #3498db; font-size: 12px;"
),
span(
"Updating...",
style = "color: #3498db; font-style: italic; margin-left: 3px; font-size: 11px;"
)
)
)
)
)
)
)
)
),
# Citation Contexts Display
fluidRow(
column(
12,
div(
class = "box box-success",
div(
class = "box-header with-border",
h4(
"Citation Contexts Visualization",
class = "box-title",
style = "color: #27ae60;"
)
),
div(
class = "box-body",
uiOutput("citation_contexts_html")
)
)
)
)
),
# ===========================================
# TAB 3: NETWORK ANALYSIS
# ===========================================
tabPanel(
title = "Network Analysis",
value = "tab_network",
br(),
# Network Visualization
fluidRow(
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"Citation Co-occurrence Network",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-tools pull-right",
downloadButton(
"download_network",
"Export Network",
class = "btn btn-primary btn-sm",
icon = icon("download")
)
)
),
div(
class = "box-body",
div(
style = "margin-bottom: 15px; padding: 10px; background-color: #f0f8ff; border-radius: 5px;",
icon("info-circle", style = "color: #3498db;"),
span(
" Nodes are colored by paper section. Legend visible on the right side of the network.",
style = "color: #555; font-size: 13px; margin-left: 8px;"
)
),
div(
style = "height: 75vh; min-height: 700px; border: 1px solid #ddd; border-radius: 5px;",
visNetworkOutput(
"citation_network",
width = "100%",
height = "100%"
)
)
)
)
),
fluidRow(
column(
6,
div(
class = "box box-info",
div(
class = "box-header with-border",
h4(
"Network Information",
class = "box-title",
style = "color: #3498db;"
)
),
div(
class = "box-body",
verbatimTextOutput("network_info")
)
)
),
column(
6,
div(
class = "box box-success",
div(
class = "box-header with-border",
h4(
"Strongest Connections",
class = "box-title",
style = "color: #27ae60;"
)
),
div(
class = "box-body",
DT::dataTableOutput("strongest_connections")
)
)
)
)
),
# ===========================================
# TAB 5: REFERENCES
# ===========================================
tabPanel(
title = "References",
value = "tab_references",
br(),
# Header con info e download
fluidRow(
column(
12,
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"Bibliography",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-tools pull-right",
downloadButton(
"download_references",
"Export References",
class = "btn btn-primary btn-sm",
icon = icon("download")
)
)
),
div(
class = "box-body",
conditionalPanel(
condition = "output.references_available",
# Summary info
div(
style = "background-color: #e8f4f8; padding: 15px; border-radius: 5px; margin-bottom: 20px;",
fluidRow(
column(
3,
div(
style = "text-align: center;",
icon(
"book",
style = "font-size: 24px; color: #2E86AB;"
),
h4(
textOutput("total_refs", inline = TRUE),
style = "color: #2E86AB; margin: 10px 0 5px 0;"
),
p(
"Total References",
style = "color: #666; margin: 0;"
)
)
),
column(
3,
div(
style = "text-align: center;",
icon(
"file-pdf",
style = "font-size: 24px; color: #e74c3c;"
),
h4(
textOutput("pdf_refs", inline = TRUE),
style = "color: #e74c3c; margin: 10px 0 5px 0;"
),
p(
"From PDF",
style = "color: #666; margin: 0;"
)
)
),
column(
3,
div(
style = "text-align: center;",
icon(
"cloud-download-alt",
style = "font-size: 24px; color: #27ae60;"
),
h4(
textOutput(
"crossref_refs",
inline = TRUE
),
style = "color: #27ae60; margin: 10px 0 5px 0;"
),
p(
"From Crossref",
style = "color: #666; margin: 0;"
)
)
),
column(
3,
div(
style = "text-align: center;",
icon(
"database",
style = "font-size: 24px; color: #8e44ad;"
),
h4(
textOutput(
"openalex_refs",
inline = TRUE
),
style = "color: #8e44ad; margin: 10px 0 5px 0;"
),
p(
"From OpenAlex",
style = "color: #666; margin: 0;"
)
)
)
)
),
# Search box
fluidRow(
column(
12,
textInput(
"reference_search",
label = NULL,
placeholder = "Search in references (author, title, year, DOI)...",
width = "100%"
)
)
),
hr(),
# References list
uiOutput("references_html")
),
conditionalPanel(
condition = "!output.references_available",
div(
style = "text-align: center; padding: 60px; color: #999;",
icon(
"book-open",
style = "font-size: 48px; margin-bottom: 20px;"
),
h4(
"No references available",
style = "color: #666;"
),
p(
"References will appear here after the analysis is complete.",
style = "font-size: 14px;"
),
p(
"References can be extracted from the PDF or fetched from Crossref using the document's DOI.",
style = "font-size: 12px; color: #999;"
)
)
)
)
)
)
)
),
# ===========================================
# TAB: BIBLIOAI SUMMARY - UI
# ===========================================
tabPanel(
title = "BiblioAI Summary",
value = "tab_biblioai",
br(),
# Header with API status, Summary Type Selection and Generate Button
fluidRow(
column(
12,
div(
class = "box box-primary",
div(
class = "box-header with-border",
h4(
"AI-Powered Document Summarization",
class = "box-title",
style = "color: #2E86AB;"
),
div(
class = "box-tools pull-right",
uiOutput("gemini_api_status")
)
),
div(
class = "box-body",
fluidRow(
column(
6,
# Summary type selection with grouped options
selectInput(
"summary_type",
label = div(
icon(
"list-alt",
style = "margin-right: 5px;"
),
tags$strong("Summary Type:")
),
choices = list(
"Article Summarization" = list(
"Short Abstract (250 words)" = "short_abstract",
"Narrative Abstract (500-600 words)" = "narrative_abstract",
"IMRaD Structure Summary" = "imrad_summary",
"Thematic Bibliography" = "thematic_bibliography"
),
"Focus on Article Parts" = list(
"Research Questions & Context" = "research_questions",
"Background & Literature" = "background_literature",
"Methods Summary" = "methods_summary",
"Implications & Conclusions" = "implications",
"Tables & Figures List" = "list_tables_figures"
)
),
selected = "short_abstract",
width = "100%"
)
),
column(4), # empty space for future options
column(
2,
# Generate button aligned with select input
div(
style = "margin-top: 25px;",
actionButton(
"generate_summary",
"Generate Summary",
icon = icon("magic"),
class = "btn-success btn-block",
style = "font-weight: bold; padding: 10px;"
)
)
)
),
# Progress indicator
conditionalPanel(
condition = "$('html').hasClass('shiny-busy')",
div(
style = "text-align: center; margin-top: 10px; padding: 10px; background-color: #f0f8f0; border-radius: 4px;",
icon(
"spinner",
class = "fa-spin",
style = "color: #27ae60; font-size: 20px; margin-right: 10px;"
),
span(
"Generating summary with AI...",
style = "color: #27ae60; font-weight: 500;"
)
)
),
# Summary type description
div(
style = "margin-top: 10px;",
uiOutput("summary_type_description")
)
)
)
)
),
# Summary Results - Full Width
fluidRow(
column(
12,
div(
class = "box box-info",
div(
class = "box-header with-border",
h4(
"AI-Generated Summary",
class = "box-title",
style = "color: #3498db;"
),
div(
class = "box-tools pull-right",
conditionalPanel(
condition = "output.summary_available",
downloadButton(
"download_summary",
"Export Summary",
class = "btn btn-info btn-sm",
icon = icon("download")
)
)
)
),
div(
class = "box-body",
style = "min-height: 500px;",
conditionalPanel(
condition = "output.summary_available",
div(
style = "background-color: #f8f9fa; padding: 20px; border-radius: 5px; border: 1px solid #dee2e6;",
# Summary metadata
div(
style = "margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #3498db;",
fluidRow(
column(
6,
tags$strong(
"Summary Type: ",
style = "color: #3498db;"
),
tags$span(
textOutput(
"summary_type_display",
inline = TRUE
),
style = "font-weight: bold;"
)
),
column(
6,
div(
style = "text-align: right;",
tags$strong(
"Generated: ",
style = "color: #3498db;"
),
textOutput(
"summary_timestamp",
inline = TRUE
)
)
)
)
),
# Summary content
div(
style = "line-height: 1.8; color: #333; font-size: 14px; white-space: pre-wrap; word-wrap: break-word;",
uiOutput("summary_content_display")
)
)
),
conditionalPanel(
condition = "!output.summary_available",
div(
style = "text-align: center; padding: 80px 20px; color: #999;",
icon(
"robot",
style = "font-size: 64px; margin-bottom: 20px; color: #bbb;"
),
h4(
"No summary generated yet",
style = "color: #666; margin-bottom: 15px;"
),
p(
"Select your summary type above and click 'Generate Summary' to start.",
style = "font-size: 14px; margin-bottom: 10px;"
),
p(
"Make sure you have uploaded a PDF document first.",
style = "font-size: 12px; color: #999;"
)
)
)
)
)
)
)
)
)
),
# Placeholder when no analysis is done
conditionalPanel(
condition = "!output.analysis_completed && !output.text_extracted",
div(
style = "text-align: center; margin-top: 100px; color: #999;",
icon(
"file-upload",
style = "font-size: 48px; margin-bottom: 20px;"
),
h3(
"Upload a PDF file and start the analysis",
style = "color: #666;"
),
p(
"Select a scientific article in PDF format and configure the analysis parameters to begin.",
style = "font-size: 16px;"
)
)
)
),
# ===========================================
# LEFT PANEL: CONTROLS (column 2)
# ===========================================
column(
2,
# Import Method Selection
div(
class = "box box-info",
div(
class = "box-body",
style = "padding: 15px;",
div(
style = "text-align: center; margin-bottom: 15px;",
icon(
"file-import",
style = "font-size: 28px; color: #2E86AB;"
),
h4(
"Choose Import Method",
style = "color: #2E86AB; margin-top: 10px; margin-bottom: 5px; font-weight: bold;"
)
),
radioButtons(
"import_method",
label = NULL,
choices = list(
"Import PDF File" = "import_pdf",
"Load Saved Text File" = "load_txt"
),
selected = "import_pdf",
width = "100%"
),
helpText(
"Select whether to extract text from a PDF or load a previously saved text file.",
style = "font-size: 11px; color: #666; text-align: center; margin-top: -5px;"
)
)
),
# PDF Import Card - visible only if import_pdf is selected
conditionalPanel(
condition = "input.import_method == 'import_pdf'",
div(
class = "box box-primary",
div(
class = "box-body",
tags$details(
id = "pdf_import_details",
open = NA, # Open by default
tags$summary(
"1. Import PDF File â–¼",
style = "font-weight: bold; cursor: pointer; color: #2E86AB; font-size: 16px; padding: 8px 0;"
),
br(),
fileInput(
"pdf_file",
label = "Choose PDF File",
accept = c(".pdf"),
buttonLabel = "Browse...",
placeholder = "No file selected",
width = "100%"
),
numericInput(
"Columns",
label = "Number of Columns in PDF",
value = NULL,
min = 1,
max = 3,
step = 1,
width = "100%"
),
helpText(
"Specify if the PDF has multiple columns (e.g., 2 for typical academic articles)."
),
# MODIFIED: Citation Type Selection - NO DEFAULT
radioButtons(
"citation_type_import",
label = div(
tags$span(
"Citation Format in PDF:",
style = "color: #d9534f; font-weight: bold;"
),
tags$span(
" *",
style = "color: #d9534f; font-size: 16px;"
),
tags$a(
icon("info-circle"),
href = "#",
onclick = "return false;",
style = "margin-left: 5px; color: #3498db; cursor: help;",
title = "Choose the citation format used in your document. This helps avoid false positives in citation detection."
)
),
choices = list(
"Author-year (Smith, 2020)" = "author_year",
"Numeric brackets [1]" = "numeric_bracketed",
"Numeric superscript¹" = "numeric_superscript"
# ,"All formats (may have false positives)" = "all"
),
selected = character(0), # NO DEFAULT SELECTION
width = "100%"
),
# Warning message when no selection
conditionalPanel(
condition = "!input.citation_type_import || input.citation_type_import == ''",
div(
style = "background-color: #fcf8e3; padding: 8px 12px; border-radius: 4px; border-left: 4px solid #f0ad4e; margin-top: -8px; margin-bottom: 10px;",
icon("exclamation-triangle", style = "color: #8a6d3b;"),
span(
" Please select the citation format before extracting text.",
style = "color: #8a6d3b; margin-left: 8px; font-size: 12px; font-weight: 500;"
)
)
),
# AI Support checkbox - visible only when Gemini API is available
conditionalPanel(
condition = "output.gemini_api_available",
div(
style = "margin-top: 12px; margin-bottom: 12px; background-color: #e8f8f5; padding: 15px; border-radius: 6px; border-left: 4px solid #17a589;",
div(
style = "display: flex; align-items: center; gap: 10px;",
div(
style = "flex-shrink: 0;",
checkboxInput(
"enable_ai_support",
label = NULL,
value = FALSE,
width = "auto"
)
),
div(
style = "display: flex; align-items: center; flex-grow: 1;",
icon(
"robot",
style = "color: #17a589; margin-right: 10px; font-size: 22px;"
),
span(
"Enable AI-Enhanced Extraction",
style = "font-weight: bold; color: #17a589; font-size: 15px;"
)
)
),
helpText(
"Use advanced AI to improve text extraction quality and citation detection accuracy.",
style = "margin-top: 8px; margin-bottom: 0px; margin-left: 35px; font-size: 11px; color: #555;"
)
)
),
# Processing spinner - visible ONLY during PDF text extraction (not during analysis)
conditionalPanel(
condition = "$('html').hasClass('shiny-busy') && (input.extract_text > 0) && !output.text_extracted",
div(
style = "margin-top: 10px; margin-bottom: 10px; background-color: #fff3cd; padding: 15px; border-radius: 5px; border-left: 4px solid #ffc107; text-align: center;",
div(
icon(
"spinner",
class = "fa-spin",
style = "font-size: 24px; color: #ff9800; margin-bottom: 10px;"
),
br(),
tags$strong(
"Extracting Text from PDF...",
style = "color: #856404; font-size: 14px;"
),
br(),
tags$span(
id = "extraction_status_text",
"Please wait while we extract the document content.",
style = "color: #856404; font-size: 12px; font-style: italic;"
)
)
)
),
# File info display
conditionalPanel(
condition = "output.pdf_uploaded",
div(
style = "background-color: #f0f8ff; padding: 10px; border-radius: 5px; margin-top: 10px;",
icon("file-pdf", style = "color: #e74c3c;"),
span(
" PDF uploaded successfully!",
style = "color: #27ae60; font-weight: bold;"
),
br(),
textOutput("pdf_info", inline = TRUE)
)
),
br(),
actionButton(
"extract_text",
"Extract Text from PDF",
icon = icon("file-text"),
class = "btn-info btn-block",
style = "margin-top: 10px;"
)
)
)
)
),
# End of conditionalPanel for import_pdf
# Load Text File Card - visible only if load_txt is selected
conditionalPanel(
condition = "input.import_method == 'load_txt'",
div(
class = "box box-primary",
div(
class = "box-body",
tags$details(
open = NA, # Open by default
tags$summary(
"1. Load Saved Text File â–¼",
style = "font-weight: bold; color: #2E86AB; font-size: 16px; padding: 8px 0; margin-bottom: 15px; display: block;"
),
helpText(
"Load a .txt file that was previously saved from this tool. The file should have DOI and citation format info in the first lines for automatic configuration.",
style = "font-size: 12px; color: #666; margin-bottom: 15px;"
),
fileInput(
"load_text_file",
"Select Text File",
accept = c("text/plain", ".txt"),
buttonLabel = "Browse...",
placeholder = "No file selected",
width = "100%"
),
# File info display for loaded text
conditionalPanel(
condition = "output.text_file_loaded",
div(
style = "background-color: #f0f8ff; padding: 10px; border-radius: 5px; margin-top: 10px;",
icon("file-alt", style = "color: #3498db;"),
span(
" Text file loaded successfully!",
style = "color: #27ae60; font-weight: bold;"
),
br(),
textOutput("text_file_info", inline = TRUE)
)
)
)
)
)
),
# End of conditionalPanel for load_txt
# Analysis Parameters Card
div(
class = "box box-success",
div(
class = "box-body",
tags$details(
tags$summary(
"2. Analysis Parameters â–¼",
style = "font-weight: bold; cursor: pointer; color: #27ae60; font-size: 16px; padding: 8px 0;"
),
br(),
# Citation context window
numericInput(
"window_size",
label = "Context Window Size (words)",
value = 20,
min = 5,
max = 50,
step = 1,
width = "100%"
),
helpText(
"Number of words before and after each citation to extract."
),
# Maximum distance for network
numericInput(
"max_distance",
label = "Max Distance for Network (chars)",
value = 800,
min = 200,
max = 2000,
step = 100,
width = "100%"
),
helpText(
"Maximum character distance between citations to consider as connected."
),
# Advanced options - OPEN BY DEFAULT
tags$details(
open = NA,
tags$summary(
"Advanced Options",
style = "font-weight: bold; cursor: pointer;"
),
br(),
checkboxInput(
"parse_multiple",
"Parse complex multiple citations",
value = TRUE
),
checkboxInput(
"remove_stopwords",
"Remove stopwords from analysis",
value = TRUE
),
textInput(
"custom_stopwords",
"Custom stopwords (comma-separated)",
value = "",
width = "100%"
)
)
)
)
),
# Analysis Button
div(
class = "box box-warning",
div(
class = "box-header with-border",
h4(
"3. Run Analysis",
class = "box-title",
style = "color: #f39c12;"
)
),
div(
class = "box-body",
actionButton(
"run_analysis",
"Start",
icon = icon("chart-line"),
class = "btn-warning btn-block btn-lg",
style = "font-weight: bold; margin-bottom: 10px;"
),
# Progress indicator - ONLY during analysis
conditionalPanel(
condition = "$('html').hasClass('shiny-busy') && (input.run_analysis > 0) && !output.analysis_completed",
div(
style = "text-align: center; margin-top: 15px;",
icon(
"spinner",
class = "fa-spin",
style = "color: #f39c12;"
),
br(),
span(
"Analyzing content...",
style = "color: #f39c12; font-style: italic;"
)
)
),
# Reset button
actionButton(
"reset_analysis",
"Reset",
icon = icon("refresh"),
class = "btn-default btn-block",
style = "margin-top: 10px;"
)
)
)
)
)
# )
), # Close Import PDF File tabPanel
# ===========================================
# MAIN TAB 2: INFO & REFERENCES
# ===========================================
tabPanel(
title = "Info & References",
value = "tab_info_references",
icon = icon("info-circle"),
fluidPage(
fluidRow(
column(1),
column(
10,
br(),
HTML(helpContent()$contentAnalysis)
),
column(1)
)
)
)
), # Close main tabsetPanel
# Modal for Preview (Bootstrap native)
tags$div(
class = "modal fade",
id = "previewModal",
tabindex = "-1",
role = "dialog",
tags$div(
class = "modal-dialog modal-lg",
role = "document",
style = "width: 90%; max-width: 1200px;",
tags$div(
class = "modal-content",
# Header
tags$div(
class = "modal-header",
tags$button(
type = "button",
class = "close",
`data-dismiss` = "modal",
`aria-label` = "Close",
tags$span(`aria-hidden` = "true", HTML("×"))
),
tags$h4(class = "modal-title", "Text & PDF Preview")
),
# Body
tags$div(
class = "modal-body",
style = "max-height: 75vh; overflow-y: auto;",
tabsetPanel(
id = "modal_preview_tabs",
tabPanel(
"Extracted Text",
br(),
div(
style = "margin-bottom: 10px;",
span(
textOutput("text_length_info", inline = TRUE),
style = "font-size: 12px; color: #666;"
)
),
div(
style = paste0(
"background-color: #f8f9fa; padding: 15px; ",
"border: 1px solid #e9ecef; border-radius: 4px; ",
"font-family: 'Consolas', 'Monaco', monospace; ",
"font-size: 13px; line-height: 1.4; ",
"white-space: pre-wrap; word-wrap: break-word;"
),
textOutput("text_preview")
)
),
tabPanel(
"PDF Preview",
br(),
uiOutput("pdf_viewer")
)
)
),
# Footer
tags$div(
class = "modal-footer",
tags$button(
type = "button",
class = "btn btn-default",
`data-dismiss` = "modal",
"Close"
)
)
)
)
),
# Modal to manage OpenAlex Reference Cards
tags$div(
class = "modal fade",
id = "oaDetailsModal",
tabindex = "-1",
role = "dialog",
tags$div(
class = "modal-dialog modal-lg",
role = "document",
style = "width: 90%; max-width: 1000px;",
tags$div(
class = "modal-content",
# Header
tags$div(
class = "modal-header",
style = "background: linear-gradient(135deg, #1a5f7a 0%, #2874a6 100%); padding: 20px 25px;",
tags$button(
type = "button",
class = "close",
`data-dismiss` = "modal",
`aria-label` = "Close",
tags$span(
`aria-hidden` = "true",
style = "color: white !important; font-size: 32px; opacity: 0.8;",
HTML("×")
)
),
tags$h4(
class = "modal-title",
id = "oaModalTitle",
style = "margin: 0; padding-right: 30px; color: white !important; font-weight: 600;",
"Document Details"
)
),
# Body
tags$div(
class = "modal-body",
id = "oaModalBody",
style = "max-height: 70vh; overflow-y: auto; padding: 20px;",
div(
style = "text-align: center; padding: 40px;",
icon(
"spinner",
class = "fa-spin",
style = "font-size: 32px; color: #3498db;"
),
p(
"Loading document details...",
style = "margin-top: 15px; color: #666;"
)
)
),
# Footer
tags$div(
class = "modal-footer",
tags$button(
type = "button",
class = "btn btn-default",
`data-dismiss` = "modal",
"Close"
)
)
)
)
),
tags$style(HTML(
"
#previewModal .modal-header,
#oaDetailsModal .modal-header {
background: linear-gradient(135deg, #1a5f7a 0%, #2874a6 100%) !important;
}
#previewModal .modal-header .modal-title,
#oaDetailsModal .modal-header .modal-title,
#previewModal .modal-header h4,
#oaDetailsModal .modal-header h4 {
color: white !important;
font-weight: 600 !important;
font-size: 18px !important;
line-height: 1.4 !important;
}
#previewModal .modal-header .close,
#oaDetailsModal .modal-header .close,
#previewModal .modal-header .close span,
#oaDetailsModal .modal-header .close span {
color: white !important;
opacity: 0.8 !important;
text-shadow: none !important;
}
#previewModal .modal-header .close:hover,
#oaDetailsModal .modal-header .close:hover {
opacity: 1 !important;
}
/* Responsive for small screens */
@media (max-width: 768px) {
#oaDetailsModal .modal-header .modal-title {
font-size: 16px !important;
}
}
"
)),
tags$style(HTML(
"
/* Responsive layout for statistics boxes */
@media (max-width: 992px) {
.small-box {
margin-bottom: 15px;
}
}
@media (max-width: 768px) {
.small-box h3 {
font-size: 28px !important;
}
.small-box i {
font-size: 32px !important;
}
}
"
)),
# JavaScript for OpenAlex modal
tags$script(HTML(
"
// Function to show OpenAlex details
function showOADetails(refIndex) {
// Reset modal content
$('#oaModalBody').html('<div style=\"text-align: center; padding: 40px;\"><i class=\"fa fa-spinner fa-spin\" style=\"font-size: 32px; color: #3498db;\"></i><p style=\"margin-top: 15px; color: #666;\">Loading document details...</p></div>');
// Send the index to Shiny
Shiny.setInputValue('selected_oa_ref_index', refIndex, {priority: 'event'});
// Open the modal
$('#oaDetailsModal').modal('show');
}
// Custom handler to update modal content
Shiny.addCustomMessageHandler('updateOAModal', function(data) {
$('#oaModalTitle').html(data.title);
$('#oaModalBody').html(data.content);
});
"
)),
tags$script(HTML(
"
// Function to open/close the PDF Import box
Shiny.addCustomMessageHandler('togglePdfImport', function(message) {
var details = document.getElementById('pdf_import_details');
if (details) {
if (message.action === 'close') {
details.removeAttribute('open');
} else if (message.action === 'open') {
details.setAttribute('open', '');
}
}
});
"
))
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.